Compare commits
4 Commits
43769e82ee
...
fcd68be51b
| Author | SHA1 | Date | |
|---|---|---|---|
| fcd68be51b | |||
| b34ad6a2e4 | |||
| 332ac76a23 | |||
| f8a6d786ee |
5
TODO.md
5
TODO.md
@@ -1,11 +1,8 @@
|
||||
# TODO
|
||||
|
||||

|
||||
|
||||
- [ ] Box selection (drag to select multiple shapes)
|
||||
- [ ] Undo/redo stack
|
||||
- [ ] Copy/paste functionality
|
||||
- [ ] Text shapes
|
||||
- [X] Text shapes
|
||||
- [ ] Resize shapes
|
||||
- [ ] Polygon shapes
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import ovh.gasser.newshapes.shapes.Shape;
|
||||
import ovh.gasser.newshapes.ui.ShapesView;
|
||||
import ovh.gasser.newshapes.ui.listeners.MenuAddListener;
|
||||
import ovh.gasser.newshapes.ui.listeners.MenuEditListener;
|
||||
import ovh.gasser.newshapes.ui.listeners.SelectionListener;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
@@ -39,12 +38,7 @@ public class App {
|
||||
|
||||
this.buildMenuBar(frame, view);
|
||||
|
||||
view.addSelectionChangeListener(new SelectionListener() {
|
||||
@Override
|
||||
public void onSelectionChanged(Iterable<Shape> selectedShapes) {
|
||||
updateMenuState(selectedShapes);
|
||||
}
|
||||
});
|
||||
view.addSelectionChangeListener(this::updateMenuState);
|
||||
}
|
||||
|
||||
private void buildModel() {
|
||||
@@ -79,11 +73,13 @@ public class App {
|
||||
JMenu menuFile = new JMenu("File");
|
||||
JMenuItem addRectItem = new JMenuItem("Add SRectangle");
|
||||
JMenuItem addCircleItem = new JMenuItem("Add SCircle");
|
||||
JMenuItem addTextItem = new JMenuItem("Add Text");
|
||||
JMenuItem htmlExportItem = new JMenuItem("Export to HTML");
|
||||
JMenuItem svgExportItem = new JMenuItem("Export to SVG");
|
||||
JMenuItem exitItem = new JMenuItem("Exit");
|
||||
addRectItem.addActionListener(new MenuAddListener("SRectangle", model, sview));
|
||||
addCircleItem.addActionListener(new MenuAddListener("SCircle", model, sview));
|
||||
addTextItem.addActionListener(evt -> sview.getController().enterTextMode());
|
||||
htmlExportItem.addActionListener(evt -> {
|
||||
try {
|
||||
new HTMLExporter(model).export();
|
||||
@@ -101,6 +97,7 @@ public class App {
|
||||
exitItem.addActionListener(evt -> System.exit(0));
|
||||
menuFile.add(addRectItem);
|
||||
menuFile.add(addCircleItem);
|
||||
menuFile.add(addTextItem);
|
||||
menuFile.addSeparator();
|
||||
menuFile.add(htmlExportItem);
|
||||
menuFile.add(svgExportItem);
|
||||
@@ -132,21 +129,24 @@ public class App {
|
||||
}
|
||||
|
||||
private void updateMenuState(Iterable<Shape> selectedShapes) {
|
||||
boolean hasAttributes = false;
|
||||
boolean hasToggleableShapes = false;
|
||||
boolean allFilled = true;
|
||||
boolean allStroked = true;
|
||||
|
||||
for (Shape s : selectedShapes) {
|
||||
if (s instanceof SText) {
|
||||
continue;
|
||||
}
|
||||
ColorAttributes attrs = (ColorAttributes) s.getAttributes(ColorAttributes.ID);
|
||||
if (attrs != null) {
|
||||
hasAttributes = true;
|
||||
hasToggleableShapes = true;
|
||||
allFilled = allFilled && attrs.filled;
|
||||
allStroked = allStroked && attrs.stroked;
|
||||
}
|
||||
}
|
||||
|
||||
updateMenuItem(editFill, hasAttributes, allFilled);
|
||||
updateMenuItem(editBorder, hasAttributes, allStroked);
|
||||
updateMenuItem(editFill, hasToggleableShapes, allFilled);
|
||||
updateMenuItem(editBorder, hasToggleableShapes, allStroked);
|
||||
}
|
||||
|
||||
private void updateMenuItem(JCheckBoxMenuItem menuItem, boolean hasAttributes, boolean allSelected) {
|
||||
|
||||
@@ -38,6 +38,10 @@ public class Selection implements Streamable<Shape> {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return selectedShapes.isEmpty();
|
||||
}
|
||||
|
||||
public List<Shape> getSelectedShapes() {
|
||||
return List.copyOf(selectedShapes);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package ovh.gasser.newshapes;
|
||||
import ovh.gasser.newshapes.shapes.SCircle;
|
||||
import ovh.gasser.newshapes.shapes.SCollection;
|
||||
import ovh.gasser.newshapes.shapes.SRectangle;
|
||||
import ovh.gasser.newshapes.shapes.SText;
|
||||
import ovh.gasser.newshapes.shapes.STriangle;
|
||||
|
||||
public interface ShapeVisitor {
|
||||
@@ -13,4 +14,6 @@ public interface ShapeVisitor {
|
||||
void visitCircle(SCircle sCircle);
|
||||
|
||||
void visitTriangle(STriangle sTriangle);
|
||||
|
||||
void visitText(SText sText);
|
||||
}
|
||||
|
||||
@@ -34,9 +34,48 @@ public abstract class AbstractShape implements Shape {
|
||||
getBounds().translate(dx, dy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resize(ResizeHandle handle, int dx, int dy) {
|
||||
Rectangle bounds = getBounds();
|
||||
switch (handle) {
|
||||
case E -> bounds.width += dx;
|
||||
case W -> {
|
||||
bounds.x += dx;
|
||||
bounds.width -= dx;
|
||||
}
|
||||
case S -> bounds.height += dy;
|
||||
case N -> {
|
||||
bounds.y += dy;
|
||||
bounds.height -= dy;
|
||||
}
|
||||
case SE -> {
|
||||
bounds.width += dx;
|
||||
bounds.height += dy;
|
||||
}
|
||||
case SW -> {
|
||||
bounds.x += dx;
|
||||
bounds.width -= dx;
|
||||
bounds.height += dy;
|
||||
}
|
||||
case NE -> {
|
||||
bounds.width += dx;
|
||||
bounds.y += dy;
|
||||
bounds.height -= dy;
|
||||
}
|
||||
case NW -> {
|
||||
bounds.x += dx;
|
||||
bounds.width -= dx;
|
||||
bounds.y += dy;
|
||||
bounds.height -= dy;
|
||||
}
|
||||
}
|
||||
if (bounds.width < 1) bounds.width = 1;
|
||||
if (bounds.height < 1) bounds.height = 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rectangle getBounds() {
|
||||
return this.bounds;
|
||||
return new Rectangle(this.bounds);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
24
src/main/java/ovh/gasser/newshapes/shapes/ResizeHandle.java
Normal file
24
src/main/java/ovh/gasser/newshapes/shapes/ResizeHandle.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package ovh.gasser.newshapes.shapes;
|
||||
|
||||
import java.awt.Cursor;
|
||||
|
||||
public enum ResizeHandle {
|
||||
NW(Cursor.NW_RESIZE_CURSOR),
|
||||
N(Cursor.N_RESIZE_CURSOR),
|
||||
NE(Cursor.NE_RESIZE_CURSOR),
|
||||
E(Cursor.E_RESIZE_CURSOR),
|
||||
SE(Cursor.SE_RESIZE_CURSOR),
|
||||
S(Cursor.S_RESIZE_CURSOR),
|
||||
SW(Cursor.SW_RESIZE_CURSOR),
|
||||
W(Cursor.W_RESIZE_CURSOR);
|
||||
|
||||
private final int cursorType;
|
||||
|
||||
ResizeHandle(int cursorType) {
|
||||
this.cursorType = cursorType;
|
||||
}
|
||||
|
||||
public int getCursorType() {
|
||||
return cursorType;
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import java.awt.*;
|
||||
|
||||
public class SCircle extends AbstractShape {
|
||||
|
||||
private final int radius;
|
||||
private int radius;
|
||||
|
||||
private SCircle(int x, int y, int radius) {
|
||||
super(new Rectangle(x, y, radius * 2, radius * 2));
|
||||
@@ -20,10 +20,37 @@ public class SCircle extends AbstractShape {
|
||||
visitor.visitCircle(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resize(ResizeHandle handle, int dx, int dy) {
|
||||
Rectangle bounds = getBounds();
|
||||
int newWidth = bounds.width;
|
||||
int newHeight = bounds.height;
|
||||
|
||||
switch (handle) {
|
||||
case E, W -> newWidth += dx;
|
||||
case N, S -> newHeight += dy;
|
||||
case SE, NW -> {
|
||||
newWidth += dx;
|
||||
newHeight += dy;
|
||||
}
|
||||
case NE, SW -> {
|
||||
newWidth += dx;
|
||||
newHeight += dy;
|
||||
}
|
||||
}
|
||||
|
||||
if (newWidth < 2) newWidth = 2;
|
||||
if (newHeight < 2) newHeight = 2;
|
||||
|
||||
this.radius = Math.max(newWidth, newHeight) / 2;
|
||||
bounds.setSize(this.radius * 2, this.radius * 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape clone() {
|
||||
var color = (ColorAttributes) getAttributes(ColorAttributes.ID);
|
||||
return SCircle.create(super.getBounds().x, super.getBounds().y, this.radius, color.strokedColor);
|
||||
Color strokeColor = color != null ? color.strokedColor : Color.BLACK;
|
||||
return SCircle.create(super.getBounds().x, super.getBounds().y, this.radius, strokeColor);
|
||||
}
|
||||
|
||||
public int getRadius() {
|
||||
|
||||
@@ -39,7 +39,7 @@ public class SCollection extends AbstractShape implements Streamable<Shape> {
|
||||
for (Shape s : children) bounds = bounds.union(s.getBounds());
|
||||
return bounds;
|
||||
} catch (IndexOutOfBoundsException e){
|
||||
logger.error("getBounds(): {e}", e);
|
||||
logger.error("getBounds(): {}", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,8 @@ public class SRectangle extends AbstractShape {
|
||||
@Override
|
||||
public Shape clone() {
|
||||
var color = (ColorAttributes) this.getAttributes(ColorAttributes.ID);
|
||||
return SRectangle.create(super.getBounds().x, super.getBounds().y, getBounds().width, getBounds().height, color.strokedColor);
|
||||
Color strokeColor = color != null ? color.strokedColor : Color.BLACK;
|
||||
return SRectangle.create(super.getBounds().x, super.getBounds().y, getBounds().width, getBounds().height, strokeColor);
|
||||
}
|
||||
|
||||
public static SRectangle create(int x, int y, int width, int height) {
|
||||
|
||||
79
src/main/java/ovh/gasser/newshapes/shapes/SText.java
Normal file
79
src/main/java/ovh/gasser/newshapes/shapes/SText.java
Normal file
@@ -0,0 +1,79 @@
|
||||
package ovh.gasser.newshapes.shapes;
|
||||
|
||||
import ovh.gasser.newshapes.ShapeVisitor;
|
||||
import ovh.gasser.newshapes.attributes.ColorAttributes;
|
||||
import ovh.gasser.newshapes.attributes.SelectionAttributes;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
public class SText extends AbstractShape {
|
||||
public static final String PLACEHOLDER_TEXT = "Text";
|
||||
public static final int DEFAULT_FONT_SIZE = 16;
|
||||
public static final String DEFAULT_FONT_NAME = "SansSerif";
|
||||
public static final int DEFAULT_FONT_STYLE = Font.PLAIN;
|
||||
|
||||
private final String text;
|
||||
private final int fontSize;
|
||||
private final String fontName;
|
||||
private final int fontStyle;
|
||||
|
||||
private SText(int x, int y, String text, int fontSize, String fontName, int fontStyle) {
|
||||
super(new Rectangle(x, y, 0, 0));
|
||||
this.text = normalizeText(text);
|
||||
this.fontSize = fontSize;
|
||||
this.fontName = fontName;
|
||||
this.fontStyle = fontStyle;
|
||||
}
|
||||
|
||||
public static SText create(int x, int y, String text) {
|
||||
var shape = new SText(x, y, text, DEFAULT_FONT_SIZE, DEFAULT_FONT_NAME, DEFAULT_FONT_STYLE);
|
||||
shape.addAttributes(new SelectionAttributes());
|
||||
shape.addAttributes(new ColorAttributes(true, false, Color.BLACK, Color.BLACK));
|
||||
return shape;
|
||||
}
|
||||
|
||||
private static String normalizeText(String input) {
|
||||
if (input == null || input.isBlank()) {
|
||||
return PLACEHOLDER_TEXT;
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public int getFontSize() {
|
||||
return fontSize;
|
||||
}
|
||||
|
||||
public String getFontName() {
|
||||
return fontName;
|
||||
}
|
||||
|
||||
public int getFontStyle() {
|
||||
return fontStyle;
|
||||
}
|
||||
|
||||
public void updateMeasuredBounds(int width, int height) {
|
||||
getBounds().setSize(Math.max(width, 0), Math.max(height, 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(ShapeVisitor visitor) {
|
||||
visitor.visitText(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape clone() {
|
||||
var copy = new SText(getBounds().x, getBounds().y, text, fontSize, fontName, fontStyle);
|
||||
copy.updateMeasuredBounds(getBounds().width, getBounds().height);
|
||||
copy.addAttributes(new SelectionAttributes());
|
||||
|
||||
var attrs = (ColorAttributes) getAttributes(ColorAttributes.ID);
|
||||
if (attrs != null) {
|
||||
copy.addAttributes(new ColorAttributes(attrs.filled, attrs.stroked, attrs.filledColor, attrs.strokedColor));
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
@@ -16,10 +16,60 @@ public class STriangle extends AbstractShape {
|
||||
visitor.visitTriangle(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resize(ResizeHandle handle, int dx, int dy) {
|
||||
Rectangle bounds = getBounds();
|
||||
int delta = Math.max(Math.abs(dx), Math.abs(dy));
|
||||
|
||||
boolean shrink = switch (handle) {
|
||||
case SE -> (dx < 0 || dy < 0);
|
||||
case NW -> (dx > 0 || dy > 0);
|
||||
case NE -> (dx < 0);
|
||||
case SW -> (dx > 0);
|
||||
case E, W -> (dx < 0);
|
||||
case N, S -> (dy < 0);
|
||||
default -> false;
|
||||
};
|
||||
|
||||
int sizeChange = shrink ? -delta : delta;
|
||||
|
||||
switch (handle) {
|
||||
case SE, E, W -> {
|
||||
bounds.width += sizeChange;
|
||||
bounds.height += sizeChange;
|
||||
}
|
||||
case NW -> {
|
||||
bounds.x -= sizeChange;
|
||||
bounds.width += sizeChange;
|
||||
bounds.y -= sizeChange;
|
||||
bounds.height += sizeChange;
|
||||
}
|
||||
case NE -> {
|
||||
bounds.y -= sizeChange;
|
||||
bounds.width += sizeChange;
|
||||
bounds.height += sizeChange;
|
||||
}
|
||||
case SW -> {
|
||||
bounds.x -= sizeChange;
|
||||
bounds.width += sizeChange;
|
||||
bounds.height += sizeChange;
|
||||
}
|
||||
case N, S -> {
|
||||
bounds.width += sizeChange;
|
||||
bounds.height += sizeChange;
|
||||
}
|
||||
}
|
||||
|
||||
if (bounds.width < 1) bounds.width = 1;
|
||||
if (bounds.height < 1) bounds.height = 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape clone() {
|
||||
var color = (ColorAttributes) getAttributes(ColorAttributes.ID);
|
||||
return STriangle.create(super.getBounds().x, super.getBounds().y, super.getBounds().height, color.strokedColor, color.filledColor);
|
||||
Color strokeColor = color != null ? color.strokedColor : Color.BLACK;
|
||||
Color fillColor = color != null ? color.filledColor : Color.BLACK;
|
||||
return STriangle.create(super.getBounds().x, super.getBounds().y, super.getBounds().height, fillColor, strokeColor);
|
||||
}
|
||||
|
||||
public static STriangle create(int x, int y, int size, Color filledColor, Color strokedColor) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.awt.*;
|
||||
public interface Shape {
|
||||
void accept(ShapeVisitor visitor);
|
||||
void translate(int dx, int dy);
|
||||
void resize(ResizeHandle handle, int dx, int dy);
|
||||
Attributes getAttributes(String key);
|
||||
void addAttributes(Attributes attr);
|
||||
Rectangle getBounds();
|
||||
|
||||
@@ -5,10 +5,13 @@ import org.slf4j.LoggerFactory;
|
||||
import ovh.gasser.newshapes.HTMLExporter;
|
||||
import ovh.gasser.newshapes.Selection;
|
||||
import ovh.gasser.newshapes.attributes.ColorAttributes;
|
||||
import ovh.gasser.newshapes.shapes.ResizeHandle;
|
||||
import ovh.gasser.newshapes.shapes.SCollection;
|
||||
import ovh.gasser.newshapes.shapes.SText;
|
||||
import ovh.gasser.newshapes.shapes.Shape;
|
||||
import ovh.gasser.newshapes.ui.listeners.SelectionListener;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.KeyAdapter;
|
||||
import java.awt.event.KeyEvent;
|
||||
@@ -24,6 +27,11 @@ public class Controller {
|
||||
private final Selection selection;
|
||||
|
||||
private Point lastMousePos;
|
||||
private boolean addingText;
|
||||
private boolean resizing;
|
||||
private ResizeHandle activeHandle;
|
||||
private Point resizeOrigin;
|
||||
private boolean resizeMode;
|
||||
|
||||
Controller(ShapesView view, SCollection model) {
|
||||
this.view = view;
|
||||
@@ -40,6 +48,13 @@ public class Controller {
|
||||
public void mouseDragged(MouseEvent evt) {
|
||||
handleMouseDragged(evt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent evt) {
|
||||
resizing = false;
|
||||
activeHandle = null;
|
||||
resizeOrigin = null;
|
||||
}
|
||||
};
|
||||
this.view.addMouseMotionListener(adapter);
|
||||
this.view.addMouseListener(adapter);
|
||||
@@ -52,16 +67,55 @@ public class Controller {
|
||||
}
|
||||
|
||||
private void handleMouseDragged(MouseEvent evt) {
|
||||
if (resizeMode && resizing && activeHandle != null) {
|
||||
int dx = evt.getX() - resizeOrigin.x;
|
||||
int dy = evt.getY() - resizeOrigin.y;
|
||||
for (Shape shape : selection) {
|
||||
shape.resize(activeHandle, dx, dy);
|
||||
}
|
||||
resizeOrigin = evt.getPoint();
|
||||
} else if (resizeMode && !selection.isEmpty()) {
|
||||
lastMousePos = evt.getPoint();
|
||||
ResizeHandle handle = getHandleAt(evt.getPoint());
|
||||
if (handle != null) {
|
||||
resizing = true;
|
||||
activeHandle = handle;
|
||||
resizeOrigin = evt.getPoint();
|
||||
}
|
||||
} else {
|
||||
resizing = false;
|
||||
activeHandle = null;
|
||||
int dx = evt.getX() - lastMousePos.x;
|
||||
int dy = evt.getY() - lastMousePos.y;
|
||||
for (Shape shape : selection) {
|
||||
shape.translate(dx, dy);
|
||||
}
|
||||
lastMousePos = evt.getPoint();
|
||||
}
|
||||
view.repaint();
|
||||
}
|
||||
|
||||
private void handleMousePressed(MouseEvent evt) {
|
||||
if (addingText) {
|
||||
placeTextAt(evt.getPoint());
|
||||
return;
|
||||
}
|
||||
|
||||
resizing = false;
|
||||
activeHandle = null;
|
||||
resizeOrigin = null;
|
||||
|
||||
if (resizeMode && !selection.isEmpty()) {
|
||||
ResizeHandle handle = getHandleAt(evt.getPoint());
|
||||
if (handle != null) {
|
||||
resizing = true;
|
||||
activeHandle = handle;
|
||||
resizeOrigin = evt.getPoint();
|
||||
view.repaint();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
getTarget(evt, this.model)
|
||||
.ifPresentOrElse(
|
||||
s -> {
|
||||
@@ -77,8 +131,49 @@ public class Controller {
|
||||
view.repaint();
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
public void enterTextMode() {
|
||||
addingText = true;
|
||||
}
|
||||
|
||||
private void placeTextAt(Point point) {
|
||||
String input = JOptionPane.showInputDialog(view, "Enter text:", "Add text", JOptionPane.PLAIN_MESSAGE);
|
||||
addingText = false;
|
||||
if (input == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
model.add(SText.create(point.x, point.y, input));
|
||||
resetSelection();
|
||||
view.repaint();
|
||||
}
|
||||
|
||||
public void enterTextMode() {
|
||||
addingText = true;
|
||||
}
|
||||
|
||||
private ResizeHandle getHandleAt(Point point) {
|
||||
final int handleSize = 5;
|
||||
for (Shape shape : selection) {
|
||||
Rectangle bounds = shape.getBounds();
|
||||
int cx = bounds.x + bounds.width / 2;
|
||||
int cy = bounds.y + bounds.height / 2;
|
||||
|
||||
if (point.x < cx && point.y < cy) return ResizeHandle.NW;
|
||||
if (point.x > cx && point.y < cy) return ResizeHandle.NE;
|
||||
if (point.x < cx && point.y > cy) return ResizeHandle.SW;
|
||||
if (point.x > cx && point.y > cy) return ResizeHandle.SE;
|
||||
if (point.y < cy) return ResizeHandle.N;
|
||||
if (point.y > cy) return ResizeHandle.S;
|
||||
if (point.x < cx) return ResizeHandle.W;
|
||||
if (point.x > cx) return ResizeHandle.E;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void handleKeyPressed(KeyEvent evt) {
|
||||
switch (evt.getKeyCode()) {
|
||||
case KeyEvent.VK_R -> toggleResizeMode();
|
||||
case KeyEvent.VK_DELETE -> deleteSelected();
|
||||
case KeyEvent.VK_C -> copySelection();
|
||||
case KeyEvent.VK_A -> changeSelectionColor();
|
||||
@@ -87,6 +182,14 @@ public class Controller {
|
||||
}
|
||||
}
|
||||
|
||||
private void toggleResizeMode() {
|
||||
if (!selection.isEmpty()) {
|
||||
resizeMode = !resizeMode;
|
||||
view.setResizeMode(resizeMode);
|
||||
view.repaint();
|
||||
}
|
||||
}
|
||||
|
||||
private void exportHtml() {
|
||||
logger.info("Exporting view to html");
|
||||
try {
|
||||
|
||||
@@ -13,12 +13,16 @@ public class ShapeDraftman implements ShapeVisitor {
|
||||
private static final ColorAttributes DEFAULT_COLOR_ATTRIBUTES =
|
||||
new ColorAttributes(false, true, Color.BLACK, Color.BLACK);
|
||||
private final Graphics2D g2d;
|
||||
|
||||
private boolean resizeMode;
|
||||
|
||||
public ShapeDraftman(Graphics graph) {
|
||||
this.g2d = (Graphics2D) graph;
|
||||
}
|
||||
|
||||
public void setResizeMode(boolean resizeMode) {
|
||||
this.resizeMode = resizeMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitRectangle(SRectangle rect) {
|
||||
Rectangle r = rect.getBounds();
|
||||
@@ -56,8 +60,10 @@ public class ShapeDraftman implements ShapeVisitor {
|
||||
this.g2d.setColor(colAttrs.filledColor);
|
||||
this.g2d.fillOval(bounds.x, bounds.y, 2 * circle.getRadius(), 2 * circle.getRadius());
|
||||
}
|
||||
if (colAttrs.stroked) this.g2d.setColor(colAttrs.strokedColor);
|
||||
if (colAttrs.stroked) {
|
||||
this.g2d.setColor(colAttrs.strokedColor);
|
||||
this.g2d.drawOval(bounds.x, bounds.y, 2 * circle.getRadius(), 2 * circle.getRadius());
|
||||
}
|
||||
|
||||
drawHandlerIfSelected(circle);
|
||||
}
|
||||
@@ -88,13 +94,58 @@ public class ShapeDraftman implements ShapeVisitor {
|
||||
drawHandlerIfSelected(tri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitText(SText text) {
|
||||
ColorAttributes colAttrs = (ColorAttributes) text.getAttributes(ColorAttributes.ID);
|
||||
|
||||
Font previousFont = g2d.getFont();
|
||||
Color previousColor = g2d.getColor();
|
||||
Font textFont = new Font(text.getFontName(), Font.PLAIN, text.getFontSize());
|
||||
g2d.setFont(textFont);
|
||||
g2d.setColor(resolveTextColor(colAttrs));
|
||||
|
||||
FontMetrics metrics = g2d.getFontMetrics(textFont);
|
||||
int width = metrics.stringWidth(text.getText());
|
||||
int height = metrics.getHeight();
|
||||
text.updateMeasuredBounds(width, height);
|
||||
|
||||
Rectangle bounds = text.getBounds();
|
||||
g2d.drawString(text.getText(), bounds.x, bounds.y + metrics.getAscent());
|
||||
|
||||
g2d.setFont(previousFont);
|
||||
g2d.setColor(previousColor);
|
||||
drawHandlerIfSelected(text);
|
||||
}
|
||||
|
||||
private Color resolveTextColor(ColorAttributes attrs) {
|
||||
if (attrs == null) {
|
||||
return Color.BLACK;
|
||||
}
|
||||
if (attrs.filledColor != null) {
|
||||
return attrs.filledColor;
|
||||
}
|
||||
if (attrs.strokedColor != null) {
|
||||
return attrs.strokedColor;
|
||||
}
|
||||
return Color.BLACK;
|
||||
}
|
||||
|
||||
private void drawHandlerIfSelected(Shape s) {
|
||||
SelectionAttributes selAttrs = (SelectionAttributes) s.getAttributes(SelectionAttributes.ID);
|
||||
if ((selAttrs != null) && (selAttrs.selected)){
|
||||
Rectangle bounds = s.getBounds();
|
||||
this.g2d.setColor(Color.RED);
|
||||
this.g2d.drawRect(bounds.x - 5, bounds.y - 5, 5, 5);
|
||||
this.g2d.drawRect(bounds.x + bounds.width, bounds.y + bounds.height, 5, 5);
|
||||
int handleSize = 5;
|
||||
this.g2d.drawRect(bounds.x - handleSize, bounds.y - handleSize, handleSize, handleSize);
|
||||
this.g2d.drawRect(bounds.x + bounds.width, bounds.y + bounds.height, handleSize, handleSize);
|
||||
if (resizeMode) {
|
||||
this.g2d.drawRect(bounds.x + bounds.width, bounds.y - handleSize, handleSize, handleSize);
|
||||
this.g2d.drawRect(bounds.x - handleSize, bounds.y + bounds.height, handleSize, handleSize);
|
||||
this.g2d.drawRect(bounds.x + bounds.width / 2 - handleSize / 2, bounds.y - handleSize, handleSize, handleSize);
|
||||
this.g2d.drawRect(bounds.x + bounds.width / 2 - handleSize / 2, bounds.y + bounds.height, handleSize, handleSize);
|
||||
this.g2d.drawRect(bounds.x - handleSize, bounds.y + bounds.height / 2 - handleSize / 2, handleSize, handleSize);
|
||||
this.g2d.drawRect(bounds.x + bounds.width, bounds.y + bounds.height / 2 - handleSize / 2, handleSize, handleSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ public class ShapesView extends JPanel {
|
||||
private final Shape model;
|
||||
private final Controller controller;
|
||||
private ShapeVisitor draftman;
|
||||
private boolean resizeMode;
|
||||
|
||||
public ShapesView(SCollection model) {
|
||||
this.model = model;
|
||||
@@ -22,6 +23,7 @@ public class ShapesView extends JPanel {
|
||||
protected void paintComponent(Graphics g) {
|
||||
super.paintComponent(g);
|
||||
this.draftman = new ShapeDraftman(g);
|
||||
((ShapeDraftman) this.draftman).setResizeMode(resizeMode);
|
||||
model.accept(draftman);
|
||||
}
|
||||
|
||||
@@ -32,4 +34,8 @@ public class ShapesView extends JPanel {
|
||||
public void addSelectionChangeListener(SelectionListener listener) {
|
||||
controller.addSelectionChangeListener(listener);
|
||||
}
|
||||
|
||||
public void setResizeMode(boolean resizeMode) {
|
||||
this.resizeMode = resizeMode;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import org.slf4j.LoggerFactory;
|
||||
import ovh.gasser.newshapes.attributes.ColorAttributes;
|
||||
import ovh.gasser.newshapes.attributes.SelectionAttributes;
|
||||
import ovh.gasser.newshapes.shapes.SCollection;
|
||||
import ovh.gasser.newshapes.shapes.SText;
|
||||
import ovh.gasser.newshapes.shapes.Shape;
|
||||
import ovh.gasser.newshapes.ui.Controller;
|
||||
import ovh.gasser.newshapes.ui.ShapesView;
|
||||
@@ -61,6 +62,10 @@ public class MenuEditListener implements ActionListener {
|
||||
logger.warn("No color attributes: {}", s);
|
||||
continue;
|
||||
}
|
||||
if (s instanceof SText) {
|
||||
s.addAttributes(new ColorAttributes(currentColAttrs.filled, currentColAttrs.stroked, filledColor, filledColor));
|
||||
continue;
|
||||
}
|
||||
s.addAttributes(new ColorAttributes(true, currentColAttrs.stroked, filledColor, currentColAttrs.strokedColor));
|
||||
}
|
||||
}
|
||||
@@ -74,6 +79,10 @@ public class MenuEditListener implements ActionListener {
|
||||
logger.warn("No color attributes: {}", s);
|
||||
continue;
|
||||
}
|
||||
if (s instanceof SText) {
|
||||
s.addAttributes(new ColorAttributes(currentColAttrs.filled, currentColAttrs.stroked, strockedColor, strockedColor));
|
||||
continue;
|
||||
}
|
||||
s.addAttributes(new ColorAttributes(currentColAttrs.filled, true, currentColAttrs.filledColor, strockedColor));
|
||||
}
|
||||
}
|
||||
@@ -85,6 +94,7 @@ public class MenuEditListener implements ActionListener {
|
||||
for (Shape s : model) {
|
||||
SelectionAttributes selAttrs = (SelectionAttributes) s.getAttributes(SelectionAttributes.ID);
|
||||
if ((selAttrs == null) || (!selAttrs.selected)) continue;
|
||||
if (s instanceof SText) continue;
|
||||
ColorAttributes colAttrs = (ColorAttributes) s.getAttributes(ColorAttributes.ID);
|
||||
if (colAttrs == null) {
|
||||
logger.warn("No color attributes: {}", s);
|
||||
@@ -102,6 +112,7 @@ public class MenuEditListener implements ActionListener {
|
||||
for (Shape s : model) {
|
||||
SelectionAttributes selAttrs = (SelectionAttributes) s.getAttributes(SelectionAttributes.ID);
|
||||
if ((selAttrs == null) || (!selAttrs.selected)) continue;
|
||||
if (s instanceof SText) continue;
|
||||
ColorAttributes colAttrs = (ColorAttributes) s.getAttributes(ColorAttributes.ID);
|
||||
if (colAttrs == null) {
|
||||
logger.warn("No color attributes: {}", s);
|
||||
|
||||
@@ -7,10 +7,7 @@ import ovh.gasser.newshapes.shapes.Shape;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Arrays;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class HTMLDraftman implements ShapeVisitor {
|
||||
|
||||
@@ -87,6 +84,27 @@ public class HTMLDraftman implements ShapeVisitor {
|
||||
cssOutput.write(strBuilder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitText(SText text) {
|
||||
int id = text.hashCode();
|
||||
htmlOutput.printf("<div id=\"txt%d\">%s</div>\n", id, text.getText());
|
||||
|
||||
ColorAttributes attrs = (ColorAttributes) text.getAttributes(ColorAttributes.ID);
|
||||
String color = formatCSSColor(resolveTextColor(attrs));
|
||||
|
||||
cssOutput.printf("#txt%d{\n", id);
|
||||
cssOutput.println(" position: absolute;");
|
||||
cssOutput.printf(" top:%dpx;%n", text.getBounds().y);
|
||||
cssOutput.printf(" left:%dpx;%n", text.getBounds().x);
|
||||
cssOutput.printf(" font-family:%s;%n", quoteCssString(text.getFontName()));
|
||||
cssOutput.printf(" font-size:%dpx;%n", text.getFontSize());
|
||||
cssOutput.printf(" font-style:%s;%n", (text.getFontStyle() & Font.ITALIC) != 0 ? "italic" : "normal");
|
||||
cssOutput.printf(" font-weight:%s;%n", (text.getFontStyle() & Font.BOLD) != 0 ? "bold" : "normal");
|
||||
cssOutput.printf(" color:%s;%n", color);
|
||||
cssOutput.println(" white-space: nowrap;");
|
||||
cssOutput.println("}");
|
||||
}
|
||||
|
||||
private String attributesToCss(Shape shape) {
|
||||
ColorAttributes attrs = (ColorAttributes) shape.getAttributes(ColorAttributes.ID);
|
||||
String strokedColor = "#ffffff";
|
||||
@@ -123,4 +141,21 @@ public class HTMLDraftman implements ShapeVisitor {
|
||||
visitCollection(model);
|
||||
htmlOutput.println(FOOTER_TEMPLATE);
|
||||
}
|
||||
|
||||
private String quoteCssString(String raw) {
|
||||
return "\"" + raw.replace("\\", "\\\\").replace("\"", "\\\"") + "\"";
|
||||
}
|
||||
|
||||
private Color resolveTextColor(ColorAttributes attrs) {
|
||||
if (attrs == null) {
|
||||
return Color.BLACK;
|
||||
}
|
||||
if (attrs.filledColor != null) {
|
||||
return attrs.filledColor;
|
||||
}
|
||||
if (attrs.strokedColor != null) {
|
||||
return attrs.strokedColor;
|
||||
}
|
||||
return Color.BLACK;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import ovh.gasser.newshapes.attributes.ColorAttributes;
|
||||
import ovh.gasser.newshapes.shapes.SCircle;
|
||||
import ovh.gasser.newshapes.shapes.SCollection;
|
||||
import ovh.gasser.newshapes.shapes.SRectangle;
|
||||
import ovh.gasser.newshapes.shapes.SText;
|
||||
import ovh.gasser.newshapes.shapes.STriangle;
|
||||
|
||||
import java.awt.*;
|
||||
@@ -71,6 +72,26 @@ public class SVGDraftman implements ShapeVisitor {
|
||||
this.output.printf("<polygon points=\"%s\" style=\"%s\" />\n", points, style);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitText(SText sText) {
|
||||
Rectangle bounds = sText.getBounds();
|
||||
ColorAttributes attrs = (ColorAttributes) sText.getAttributes(ColorAttributes.ID);
|
||||
String color = colorToHex(resolveTextColor(attrs));
|
||||
String fontStyle = (sText.getFontStyle() & Font.ITALIC) != 0 ? "italic" : "normal";
|
||||
String fontWeight = (sText.getFontStyle() & Font.BOLD) != 0 ? "bold" : "normal";
|
||||
this.output.printf(
|
||||
"<text x=\"%d\" y=\"%d\" font-family=\"%s\" font-size=\"%d\" font-style=\"%s\" font-weight=\"%s\" fill=\"%s\">%s</text>\n",
|
||||
bounds.x,
|
||||
bounds.y + sText.getFontSize(),
|
||||
sText.getFontName(),
|
||||
sText.getFontSize(),
|
||||
fontStyle,
|
||||
fontWeight,
|
||||
color,
|
||||
sText.getText()
|
||||
);
|
||||
}
|
||||
|
||||
public void generateSVG(SCollection model) {
|
||||
output.println(String.format(SVG_PRELUDE, App.WIN_SIZE.width, App.WIN_SIZE.height));
|
||||
visitCollection(model);
|
||||
@@ -99,4 +120,17 @@ public class SVGDraftman implements ShapeVisitor {
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
private Color resolveTextColor(ColorAttributes attrs) {
|
||||
if (attrs == null) {
|
||||
return Color.BLACK;
|
||||
}
|
||||
if (attrs.filledColor != null) {
|
||||
return attrs.filledColor;
|
||||
}
|
||||
if (attrs.strokedColor != null) {
|
||||
return attrs.strokedColor;
|
||||
}
|
||||
return Color.BLACK;
|
||||
}
|
||||
}
|
||||
|
||||
27
style.css
27
style.css
@@ -1,7 +1,7 @@
|
||||
.triangle110717522{
|
||||
.triangle204514374{
|
||||
position: absolute;
|
||||
top: 162px;
|
||||
left: 367px;
|
||||
top: 169px;
|
||||
left: 372px;
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
border: 0 solid transparent;
|
||||
@@ -9,35 +9,35 @@
|
||||
border-right-width: 30.0px;
|
||||
border-bottom: 50px solid #ffff00;
|
||||
}
|
||||
#rec209793789{
|
||||
#rec664997016{
|
||||
position:absolute;
|
||||
top:10px;
|
||||
left:10px;
|
||||
width:40px;
|
||||
height:60px;
|
||||
background:#ffffff;border:1px solid #ff0000; }
|
||||
#rec365617083{
|
||||
#rec2139424642{
|
||||
position:absolute;
|
||||
top:10px;
|
||||
left:70px;
|
||||
width:40px;
|
||||
height:60px;
|
||||
background:#ffffff;border:1px solid #000000; }
|
||||
#rec1121327988{
|
||||
#rec609258999{
|
||||
position:absolute;
|
||||
top:200px;
|
||||
left:100px;
|
||||
width:40px;
|
||||
height:60px;
|
||||
background:#ffffff;border:1px solid #ff00ff; }
|
||||
#rec256914054{
|
||||
#rec759898031{
|
||||
position:absolute;
|
||||
top:200px;
|
||||
left:150px;
|
||||
width:40px;
|
||||
height:60px;
|
||||
background:#ffffff;border:1px solid #ff00ff; }
|
||||
.circle172224331{
|
||||
.circle305332562{
|
||||
|
||||
position: absolute;
|
||||
top:250px;
|
||||
@@ -52,3 +52,14 @@ background:#ffffff;border:1px solid #ff00ff; }
|
||||
background:#ffffff;border:1px solid #000000;
|
||||
}
|
||||
|
||||
#txt1172155777{
|
||||
position: absolute;
|
||||
top:125px;
|
||||
left:216px;
|
||||
font-family:"SansSerif";
|
||||
font-size:16px;
|
||||
font-style:normal;
|
||||
font-weight:normal;
|
||||
color:#9999ff;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user