Files
new-shapes/src/main/java/ovh/gasser/newshapes/ui/ShapeDraftman.java
Thibaud b0e3428696 feat(ui): add box selection feature
- Selection: add addAll() method for bulk shape addition
- Controller: box selection with mouse drag on empty space
- ShapeDraftman: drawSelectionBox() for rubber-band rendering
- ShapesView: currentSelectionBox field and setter
2026-03-26 23:56:01 +01:00

163 lines
6.2 KiB
Java

package ovh.gasser.newshapes.ui;
import ovh.gasser.newshapes.ShapeVisitor;
import ovh.gasser.newshapes.attributes.ColorAttributes;
import ovh.gasser.newshapes.attributes.SelectionAttributes;
import ovh.gasser.newshapes.shapes.*;
import ovh.gasser.newshapes.shapes.Shape;
import java.awt.*;
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();
ColorAttributes colAttrs = (ColorAttributes) rect.getAttributes(ColorAttributes.ID);
if (colAttrs == null){
colAttrs = DEFAULT_COLOR_ATTRIBUTES;
}
if (colAttrs.filled) {
this.g2d.setColor(colAttrs.filledColor);
this.g2d.fillRect(r.x, r.y, r.width, r.height);
}
if (colAttrs.stroked) {
this.g2d.setColor(colAttrs.strokedColor);
this.g2d.drawRect(r.x, r.y, r.width, r.height);
}
drawHandlerIfSelected(rect);
}
@Override
public void visitCollection(SCollection collection) {
for (Shape s: collection) {
s.accept(this);
}
drawHandlerIfSelected(collection);
}
@Override
public void visitCircle(SCircle circle) {
ColorAttributes colAttrs = (ColorAttributes) circle.getAttributes(ColorAttributes.ID);
final Rectangle bounds = circle.getBounds();
if (colAttrs == null) {
colAttrs = DEFAULT_COLOR_ATTRIBUTES;
}
if (colAttrs.filled) {
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);
this.g2d.drawOval(bounds.x, bounds.y, 2 * circle.getRadius(), 2 * circle.getRadius());
}
drawHandlerIfSelected(circle);
}
@Override
public void visitTriangle(STriangle tri) {
ColorAttributes colAttrs = (ColorAttributes) tri.getAttributes(ColorAttributes.ID);
var bounds = tri.getBounds();
var size = Math.min(bounds.width, bounds.height); // Should be the same because we only support equilateral triangles
if (colAttrs == null){
colAttrs = DEFAULT_COLOR_ATTRIBUTES;
}
if (colAttrs.filled) {
this.g2d.setColor(colAttrs.filledColor);
this.g2d.fillPolygon(
new int[]{ bounds.x, bounds.x + (size/2), bounds.x + size },
new int[]{ bounds.y + size, bounds.y, bounds.y + size },
3);
}
if (colAttrs.stroked) {
this.g2d.setColor(colAttrs.strokedColor);
this.g2d.drawPolygon(
new int[]{bounds.x, bounds.x + (size / 2), bounds.x + size},
new int[]{bounds.y + size, bounds.y, bounds.y + size},
3);
}
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);
int handleSize = 10;
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);
}
}
}
public void drawSelectionBox(Rectangle box) {
if (box == null) return;
g2d.setXORMode(Color.WHITE);
g2d.setColor(Color.BLUE);
g2d.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER, 1, new float[]{4, 4}, 0));
g2d.drawRect(box.x, box.y, box.width, box.height);
g2d.setPaintMode();
}
}