package holeg.ui.view.canvas; import holeg.model.*; import holeg.ui.controller.Control; import holeg.ui.model.GuiSettings; import holeg.utility.math.vector.Geometry; import holeg.utility.math.vector.Vec2i; import javax.swing.*; import java.awt.*; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.util.HashSet; import java.util.Optional; import java.util.Set; import java.util.logging.Logger; public class Canvas extends JPanel { private static final Logger log = Logger.getLogger(Canvas.class.getName()); private GroupNode groupNode; private final Control control; private final CanvasMouseListener canvasMouseListener = new CanvasMouseListener(); private boolean enabled = true; public Canvas(Control control, GroupNode groupNode) { this.control = control; this.groupNode = groupNode; control.OnGuiSetEnabled.addListener(this::setCanvasEnabled); control.OnSelectionChanged.addListener(this::repaint); // TODO(Tom2022-01-14): remove listener when not needed anymore this.addMouseListener(canvasMouseListener); this.addMouseMotionListener(canvasMouseListener); } public void setGroupNode(GroupNode groupNode){ this.groupNode = groupNode; } public static Rectangle getBoundsOfObject(AbstractCanvasObject obj) { int pictureScale = GuiSettings.getPictureScale(); int pictureScaleDiv2 = GuiSettings.getPictureScaleDiv2(); Vec2i pos = obj.getPosition(); return new Rectangle(pos.getX() - pictureScaleDiv2, pos.getY() - pictureScaleDiv2, pictureScale, pictureScale); } private void setCanvasEnabled(boolean state) { enabled = state; } public GroupNode getGroupNode() { return this.groupNode; } @Override public void paintComponent(java.awt.Graphics g) { super.paintComponent(g); log.info("Draw"); Graphics2D g2d = Rendering.initGraphics2D(g); Rendering.drawSelection(g2d); control.getModel().getEdgesOnCanvas().forEach(edge -> { boolean edgeAinside = edge.getA().getGroupNode().get() == groupNode; boolean edgeBinside = edge.getB().getGroupNode().get() == groupNode; //both if (edgeAinside && edgeBinside) { Rendering.drawEdge(g2d, edge); } //TODO(Tom2022-01-16): oneside //none }); groupNode.getHolonObjects().forEach(hO -> Rendering.drawHolonObject(g2d, hO)); groupNode.getSwitches().forEach(hS -> Rendering.drawSwitchObject(g2d, hS)); groupNode.getGroupNodes().forEach(groupNode -> Rendering.drawGroupNode(g2d, groupNode)); groupNode.getNodes().forEach(node -> Rendering.drawNode(g2d, node)); switch (canvasMouseListener.state) { case BoxSelection -> Rendering.drawSelectionBox(g2d, canvasMouseListener.getRectangleOfSelectionBox()); case EdgeCreation -> Rendering.drawNewEdgeLine(g2d, canvasMouseListener.selectedOnPressed.getPosition(), canvasMouseListener.lastPosition); } } private Optional getObjectAtPosition(Vec2i pos) { return groupNode.getObjectsInThisLayer().filter(obj -> getBoundsOfObject(obj).contains(pos.getX(), pos.getY()) ).findAny(); } //TODO(Tom2022-01-17): checkForReplacement replace with getObjectAtPosition public Optional checkForReplacement(int x, int y) { return Optional.empty(); } /** * Microsoft Windows10 selection & dragging behavior */ private class CanvasMouseListener implements MouseListener, MouseMotionListener { private Vec2i lastPosition = new Vec2i(); private Vec2i pressedPosition = new Vec2i(); private Set selectionBeforeBoxSelection = new HashSet<>(); private State state = State.None; private AbstractCanvasObject selectedOnPressed = null; @Override public void mousePressed(MouseEvent e) { if (!enabled) { return; } log.info(state.toString()); if (!e.isControlDown()) { GuiSettings.getSelectedObjects().clear(); } Vec2i pos = new Vec2i(e.getPoint()); getObjectAtPosition(pos).ifPresentOrElse(obj -> { if (!GuiSettings.getSelectedObjects().contains(obj)) { state = State.Selection; GuiSettings.getSelectedObjects().add(obj); } selectedOnPressed = obj; }, () -> { state = State.BoxSelection; selectionBeforeBoxSelection = Set.copyOf(GuiSettings.getSelectedObjects()); }); control.OnSelectionChanged.broadcast(); lastPosition = pressedPosition = pos; } @Override public void mouseDragged(MouseEvent e) { if (!enabled) { return; } log.info(state.toString()); Vec2i actualPos = new Vec2i(e.getPoint()); switch (state) { case None, Selection -> { // Not handle to small mouse dragging if (!(pressedPosition.getSquaredDistance(actualPos) > GuiSettings.dragThresholdDistance)) { return; } if (SwingUtilities.isLeftMouseButton(e)) { state = State.ObjectDragging; } else if (SwingUtilities.isRightMouseButton(e)) { state = State.EdgeCreation; } } case BoxSelection -> { Rectangle selectionBox = getRectangleOfSelectionBox(); groupNode.getObjectsInThisLayer().forEach(obj -> { Rectangle bounds = getBoundsOfObject(obj); if (selectionBox.intersects(bounds) ^ selectionBeforeBoxSelection.contains(obj)) { control.addSelectedObject(obj); } else { control.removeObjectFromSelection(obj); } }); repaint(); } case ObjectDragging -> { Vec2i delta = actualPos.subtract(lastPosition); GuiSettings.getSelectedObjects().forEach(obj -> obj.getPosition().addAssign(delta)); repaint(); } case EdgeCreation -> repaint(); } lastPosition = actualPos; } @Override public void mouseReleased(MouseEvent e) { if (!enabled) { return; } log.info(state.toString()); switch (state) { case None -> { if (GuiSettings.getSelectedObjects().contains(selectedOnPressed)) { control.removeObjectFromSelection(selectedOnPressed); } else { control.addSelectedObject(selectedOnPressed); } } case EdgeCreation -> { getObjectAtPosition(lastPosition).ifPresentOrElse(obj -> { if(control.addEdgeOnCanvas(new Edge(selectedOnPressed, obj, GuiSettings.maxCapacityForNewCreatedEdges))){ control.calculateStateAndVisualForCurrentTimeStep(); } }, () -> { Node node = new Node("Node"); groupNode.add(node); node.setPosition(new Vec2i(lastPosition)); control.addEdgeOnCanvas(new Edge(selectedOnPressed, node, GuiSettings.maxCapacityForNewCreatedEdges)); control.calculateStateAndVisualForCurrentTimeStep(); }); } } state = State.None; repaint(); } @Override public void mouseClicked(MouseEvent e) { boolean doubleLeftClick = e.getClickCount() % 2 == 0 && SwingUtilities.isLeftMouseButton(e); if (doubleLeftClick) { log.info(state.toString()); getObjectAtPosition(new Vec2i(e.getPoint())).ifPresent(obj -> { if (obj instanceof HolonSwitch sw) { sw.setMode(HolonSwitch.SwitchMode.Manual); sw.flipManualState(); control.calculateStateAndVisualForCurrentTimeStep(); } }); } } Rectangle getRectangleOfSelectionBox() { return Geometry.createRectangleFromCorners(lastPosition, pressedPosition); } @Override public void mouseEntered(MouseEvent e) { } @Override public void mouseExited(MouseEvent e) { } @Override public void mouseMoved(MouseEvent e) { } private enum State { None, BoxSelection, EdgeCreation, ObjectDragging, Selection } } }