Implement copy action

This commit is contained in:
Thibaud Gasser 2025-02-19 11:27:45 +01:00
parent b4eac668c8
commit aea90f5a93
8 changed files with 58 additions and 14 deletions

View File

@ -3,7 +3,6 @@ package ovh.gasser.newshapes;
import ovh.gasser.newshapes.shapes.SCircle; import ovh.gasser.newshapes.shapes.SCircle;
import ovh.gasser.newshapes.shapes.SCollection; import ovh.gasser.newshapes.shapes.SCollection;
import ovh.gasser.newshapes.shapes.SRectangle; import ovh.gasser.newshapes.shapes.SRectangle;
import ovh.gasser.newshapes.shapes.Shape;
import ovh.gasser.newshapes.ui.ShapesView; import ovh.gasser.newshapes.ui.ShapesView;
import javax.swing.*; import javax.swing.*;
@ -12,7 +11,7 @@ import java.awt.*;
public class App { public class App {
public static final Dimension WIN_SIZE = new Dimension(800, 600); public static final Dimension WIN_SIZE = new Dimension(800, 600);
private Shape model; private SCollection model;
private App() throws HeadlessException { private App() throws HeadlessException {
final JFrame frame = new JFrame("Reactive shapes"); final JFrame frame = new JFrame("Reactive shapes");

View File

@ -48,4 +48,7 @@ public abstract class AbstractShape implements Shape {
public String toString() { public String toString() {
return String.format("x=%d, y=%d, width=%d, height=%d", bounds.x, bounds.y, bounds.width, bounds.height); return String.format("x=%d, y=%d, width=%d, height=%d", bounds.x, bounds.y, bounds.width, bounds.height);
} }
@Override
public abstract Shape clone();
} }

View File

@ -1,6 +1,7 @@
package ovh.gasser.newshapes.shapes; package ovh.gasser.newshapes.shapes;
import ovh.gasser.newshapes.ShapeVisitor; import ovh.gasser.newshapes.ShapeVisitor;
import ovh.gasser.newshapes.attributes.ColorAttributes;
import ovh.gasser.newshapes.attributes.SelectionAttributes; import ovh.gasser.newshapes.attributes.SelectionAttributes;
import java.awt.*; import java.awt.*;
@ -19,13 +20,24 @@ public class SCircle extends AbstractShape {
visitor.visitCircle(this); visitor.visitCircle(this);
} }
@Override
public Shape clone() {
var color = (ColorAttributes) getAttributes(ColorAttributes.ID);
return SCircle.create(super.getBounds().x, super.getBounds().y, this.radius, color.strokedColor);
}
public int getRadius() { public int getRadius() {
return radius; return radius;
} }
public static SCircle create(int x, int y, int radius) { public static SCircle create(int x, int y, int radius) {
return create(x, y, radius, Color.BLACK);
}
public static SCircle create(int x, int y, int radius, Color color) {
var circle = new SCircle(x, y, radius); var circle = new SCircle(x, y, radius);
circle.addAttributes(new SelectionAttributes()); circle.addAttributes(new SelectionAttributes());
circle.addAttributes(new ColorAttributes(false, true, Color.BLACK, color));
return circle; return circle;
} }
} }

View File

@ -12,6 +12,7 @@ import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Spliterator; import java.util.Spliterator;
import java.util.stream.Collectors;
public class SCollection extends AbstractShape implements Streamable<Shape> { public class SCollection extends AbstractShape implements Streamable<Shape> {
private final static Logger logger = LoggerFactory.getLogger(SCollection.class); private final static Logger logger = LoggerFactory.getLogger(SCollection.class);
@ -43,6 +44,14 @@ public class SCollection extends AbstractShape implements Streamable<Shape> {
} }
} }
@Override
public Shape clone() {
var clonedChildren = children.stream().map(Shape::clone).toArray(Shape[]::new);
var collection = new SCollection(clonedChildren);
collection.addAttributes(new SelectionAttributes());
return collection;
}
@Override @Override
public void setLoc(Point newLoc) { public void setLoc(Point newLoc) {
final Point loc = getBounds().getLocation(); final Point loc = getBounds().getLocation();
@ -59,6 +68,10 @@ public class SCollection extends AbstractShape implements Streamable<Shape> {
return children.spliterator(); return children.spliterator();
} }
public void add(Shape s) {
children.add(s);
}
public void remove(Shape s) { public void remove(Shape s) {
if (!children.remove(s)) { if (!children.remove(s)) {
logger.error("Unable to delete shape: {}", s); logger.error("Unable to delete shape: {}", s);

View File

@ -24,14 +24,19 @@ 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);
}
public static SRectangle create(int x, int y, int width, int height) { public static SRectangle create(int x, int y, int width, int height) {
SRectangle rect = new SRectangle(new Rectangle(x, y, width, height)); return create(x, y, width, height, Color.BLACK);
rect.addAttributes(new SelectionAttributes());
return rect;
} }
public static SRectangle create(int x, int y, int width, int height, Color color) { public static SRectangle create(int x, int y, int width, int height, Color color) {
final SRectangle rect = create(x, y, width, height); SRectangle rect = new SRectangle(new Rectangle(x, y, width, height));
rect.addAttributes(new SelectionAttributes());
rect.addAttributes(new ColorAttributes(false, true, Color.BLACK, color)); rect.addAttributes(new ColorAttributes(false, true, Color.BLACK, color));
return rect; return rect;
} }

View File

@ -12,4 +12,5 @@ public interface Shape {
Attributes getAttributes(String key); Attributes getAttributes(String key);
void addAttributes(Attributes attr); void addAttributes(Attributes attr);
Rectangle getBounds(); Rectangle getBounds();
Shape clone();
} }

View File

@ -15,10 +15,10 @@ import java.util.Optional;
public class Controller { public class Controller {
private final static Logger logger = LoggerFactory.getLogger(Controller.class); private final static Logger logger = LoggerFactory.getLogger(Controller.class);
private final ShapesView view; private final ShapesView view;
private final Shape model; private final SCollection model;
private Selection selection; private Selection selection;
Controller(ShapesView view, Shape model) { Controller(ShapesView view, SCollection model) {
this.view = view; this.view = view;
this.model = model; this.model = model;
@ -53,8 +53,7 @@ public class Controller {
} }
private void handleMousePressed(MouseEvent evt) { private void handleMousePressed(MouseEvent evt) {
var sc = (SCollection) this.model; getTarget(evt, this.model)
getTarget(evt, sc)
.ifPresentOrElse( .ifPresentOrElse(
s -> { s -> {
if (selection != null) resetSelection(); if (selection != null) resetSelection();
@ -73,18 +72,29 @@ public class Controller {
case KeyEvent.VK_DELETE: case KeyEvent.VK_DELETE:
deleteSelected(); deleteSelected();
break; break;
case KeyEvent.VK_C:
copySelection();
break;
default: default:
logger.warn("Pressed unhandled key: {}", evt.getKeyChar()); logger.warn("Pressed unhandled key: {}", evt.getKeyChar());
break; break;
} }
} }
private void copySelection() {
if (selection == null) {
logger.debug("No selection to copy");
return;
}
this.model.add(selection.shape.clone());
view.repaint();
}
private void deleteSelected() { private void deleteSelected() {
if (selection == null) return; if (selection == null) return;
logger.debug("Deleting selected shape(s)"); logger.debug("Deleting selected shape(s)");
var sc = (SCollection) this.model; this.model.remove(selection.shape);
sc.remove(selection.shape); resetSelection();
selection = null;
view.repaint(); view.repaint();
} }

View File

@ -3,6 +3,7 @@ package ovh.gasser.newshapes.ui;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import ovh.gasser.newshapes.ShapeVisitor; import ovh.gasser.newshapes.ShapeVisitor;
import ovh.gasser.newshapes.shapes.SCollection;
import ovh.gasser.newshapes.shapes.Shape; import ovh.gasser.newshapes.shapes.Shape;
import javax.swing.*; import javax.swing.*;
@ -15,7 +16,7 @@ public class ShapesView extends JPanel {
private final Controller controller; private final Controller controller;
private ShapeVisitor draftman; private ShapeVisitor draftman;
public ShapesView(Shape model) { public ShapesView(SCollection model) {
this.model = model; this.model = model;
this.controller = new Controller(this, model); this.controller = new Controller(this, model);
} }