implement resize feature

This commit is contained in:
2026-03-19 22:20:23 +01:00
parent 332ac76a23
commit b34ad6a2e4
9 changed files with 249 additions and 9 deletions

View File

@@ -38,6 +38,10 @@ public class Selection implements Streamable<Shape> {
notifyListeners(); notifyListeners();
} }
public boolean isEmpty() {
return selectedShapes.isEmpty();
}
public List<Shape> getSelectedShapes() { public List<Shape> getSelectedShapes() {
return List.copyOf(selectedShapes); return List.copyOf(selectedShapes);
} }

View File

@@ -34,6 +34,45 @@ public abstract class AbstractShape implements Shape {
getBounds().translate(dx, dy); 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 @Override
public Rectangle getBounds() { public Rectangle getBounds() {
return this.bounds; return this.bounds;

View 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;
}
}

View File

@@ -8,7 +8,7 @@ import java.awt.*;
public class SCircle extends AbstractShape { public class SCircle extends AbstractShape {
private final int radius; private int radius;
private SCircle(int x, int y, int radius) { private SCircle(int x, int y, int radius) {
super(new Rectangle(x, y, radius * 2, radius * 2)); super(new Rectangle(x, y, radius * 2, radius * 2));
@@ -20,6 +20,32 @@ public class SCircle extends AbstractShape {
visitor.visitCircle(this); 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 @Override
public Shape clone() { public Shape clone() {
var color = (ColorAttributes) getAttributes(ColorAttributes.ID); var color = (ColorAttributes) getAttributes(ColorAttributes.ID);

View File

@@ -16,6 +16,54 @@ public class STriangle extends AbstractShape {
visitor.visitTriangle(this); 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 @Override
public Shape clone() { public Shape clone() {
var color = (ColorAttributes) getAttributes(ColorAttributes.ID); var color = (ColorAttributes) getAttributes(ColorAttributes.ID);

View File

@@ -8,6 +8,7 @@ import java.awt.*;
public interface Shape { public interface Shape {
void accept(ShapeVisitor visitor); void accept(ShapeVisitor visitor);
void translate(int dx, int dy); void translate(int dx, int dy);
void resize(ResizeHandle handle, int dx, int dy);
Attributes getAttributes(String key); Attributes getAttributes(String key);
void addAttributes(Attributes attr); void addAttributes(Attributes attr);
Rectangle getBounds(); Rectangle getBounds();

View File

@@ -5,6 +5,7 @@ import org.slf4j.LoggerFactory;
import ovh.gasser.newshapes.HTMLExporter; import ovh.gasser.newshapes.HTMLExporter;
import ovh.gasser.newshapes.Selection; import ovh.gasser.newshapes.Selection;
import ovh.gasser.newshapes.attributes.ColorAttributes; import ovh.gasser.newshapes.attributes.ColorAttributes;
import ovh.gasser.newshapes.shapes.ResizeHandle;
import ovh.gasser.newshapes.shapes.SCollection; import ovh.gasser.newshapes.shapes.SCollection;
import ovh.gasser.newshapes.shapes.SText; import ovh.gasser.newshapes.shapes.SText;
import ovh.gasser.newshapes.shapes.Shape; import ovh.gasser.newshapes.shapes.Shape;
@@ -27,6 +28,10 @@ public class Controller {
private Point lastMousePos; private Point lastMousePos;
private boolean addingText; private boolean addingText;
private boolean resizing;
private ResizeHandle activeHandle;
private Point resizeOrigin;
private boolean resizeMode;
Controller(ShapesView view, SCollection model) { Controller(ShapesView view, SCollection model) {
this.view = view; this.view = view;
@@ -43,6 +48,13 @@ public class Controller {
public void mouseDragged(MouseEvent evt) { public void mouseDragged(MouseEvent evt) {
handleMouseDragged(evt); handleMouseDragged(evt);
} }
@Override
public void mouseReleased(MouseEvent evt) {
resizing = false;
activeHandle = null;
resizeOrigin = null;
}
}; };
this.view.addMouseMotionListener(adapter); this.view.addMouseMotionListener(adapter);
this.view.addMouseListener(adapter); this.view.addMouseListener(adapter);
@@ -55,12 +67,31 @@ public class Controller {
} }
private void handleMouseDragged(MouseEvent evt) { private void handleMouseDragged(MouseEvent evt) {
int dx = evt.getX() - lastMousePos.x; if (resizeMode && resizing && activeHandle != null) {
int dy = evt.getY() - lastMousePos.y; int dx = evt.getX() - resizeOrigin.x;
for (Shape shape : selection) { int dy = evt.getY() - resizeOrigin.y;
shape.translate(dx, dy); 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();
} }
lastMousePos = evt.getPoint();
view.repaint(); view.repaint();
} }
@@ -70,6 +101,21 @@ public class Controller {
return; 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) getTarget(evt, this.model)
.ifPresentOrElse( .ifPresentOrElse(
s -> { s -> {
@@ -85,6 +131,7 @@ public class Controller {
view.repaint(); view.repaint();
} }
<<<<<<< HEAD
public void enterTextMode() { public void enterTextMode() {
addingText = true; addingText = true;
} }
@@ -101,8 +148,32 @@ public class Controller {
view.repaint(); 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) { private void handleKeyPressed(KeyEvent evt) {
switch (evt.getKeyCode()) { switch (evt.getKeyCode()) {
case KeyEvent.VK_R -> toggleResizeMode();
case KeyEvent.VK_DELETE -> deleteSelected(); case KeyEvent.VK_DELETE -> deleteSelected();
case KeyEvent.VK_C -> copySelection(); case KeyEvent.VK_C -> copySelection();
case KeyEvent.VK_A -> changeSelectionColor(); case KeyEvent.VK_A -> changeSelectionColor();
@@ -111,6 +182,14 @@ public class Controller {
} }
} }
private void toggleResizeMode() {
if (!selection.isEmpty()) {
resizeMode = !resizeMode;
view.setResizeMode(resizeMode);
view.repaint();
}
}
private void exportHtml() { private void exportHtml() {
logger.info("Exporting view to html"); logger.info("Exporting view to html");
try { try {

View File

@@ -13,12 +13,16 @@ public class ShapeDraftman implements ShapeVisitor {
private static final ColorAttributes DEFAULT_COLOR_ATTRIBUTES = private static final ColorAttributes DEFAULT_COLOR_ATTRIBUTES =
new ColorAttributes(false, true, Color.BLACK, Color.BLACK); new ColorAttributes(false, true, Color.BLACK, Color.BLACK);
private final Graphics2D g2d; private final Graphics2D g2d;
private boolean resizeMode;
public ShapeDraftman(Graphics graph) { public ShapeDraftman(Graphics graph) {
this.g2d = (Graphics2D) graph; this.g2d = (Graphics2D) graph;
} }
public void setResizeMode(boolean resizeMode) {
this.resizeMode = resizeMode;
}
@Override @Override
public void visitRectangle(SRectangle rect) { public void visitRectangle(SRectangle rect) {
Rectangle r = rect.getBounds(); Rectangle r = rect.getBounds();
@@ -129,8 +133,17 @@ public class ShapeDraftman implements ShapeVisitor {
if ((selAttrs != null) && (selAttrs.selected)){ if ((selAttrs != null) && (selAttrs.selected)){
Rectangle bounds = s.getBounds(); Rectangle bounds = s.getBounds();
this.g2d.setColor(Color.RED); this.g2d.setColor(Color.RED);
this.g2d.drawRect(bounds.x - 5, bounds.y - 5, 5, 5); int handleSize = 5;
this.g2d.drawRect(bounds.x + bounds.width, bounds.y + bounds.height, 5, 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);
}
} }
} }

View File

@@ -12,6 +12,7 @@ public class ShapesView extends JPanel {
private final Shape model; private final Shape model;
private final Controller controller; private final Controller controller;
private ShapeVisitor draftman; private ShapeVisitor draftman;
private boolean resizeMode;
public ShapesView(SCollection model) { public ShapesView(SCollection model) {
this.model = model; this.model = model;
@@ -22,6 +23,7 @@ public class ShapesView extends JPanel {
protected void paintComponent(Graphics g) { protected void paintComponent(Graphics g) {
super.paintComponent(g); super.paintComponent(g);
this.draftman = new ShapeDraftman(g); this.draftman = new ShapeDraftman(g);
((ShapeDraftman) this.draftman).setResizeMode(resizeMode);
model.accept(draftman); model.accept(draftman);
} }
@@ -32,4 +34,8 @@ public class ShapesView extends JPanel {
public void addSelectionChangeListener(SelectionListener listener) { public void addSelectionChangeListener(SelectionListener listener) {
controller.addSelectionChangeListener(listener); controller.addSelectionChangeListener(listener);
} }
public void setResizeMode(boolean resizeMode) {
this.resizeMode = resizeMode;
}
} }