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 final Control control; private final CanvasMouseListener canvasMouseListener = new CanvasMouseListener(); private GroupNode groupNode; 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); control.OnCanvasUpdate.addListener(this::repaint); this.setBackground(Color.WHITE); this.setPreferredSize(new Dimension(GuiSettings.canvasSize.getX(), GuiSettings.canvasSize.getY())); this.addMouseListener(canvasMouseListener); this.addMouseMotionListener(canvasMouseListener); } 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; } public void setGroupNode(GroupNode groupNode) { this.groupNode = groupNode; } @Override public void paintComponent(java.awt.Graphics g) { super.paintComponent(g); Graphics2D g2d = Rendering.initGraphics2D(g); Rendering.drawSelection(g2d); paintEdges(g2d); 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 void paintEdges(Graphics2D g2d) { control.getModel().getEdgesOnCanvas().forEach(edge -> { if (edge.getA().getGroupNode().isEmpty() || edge.getB().getGroupNode().isEmpty()) { return; } boolean aInside = edge.getA().getGroupNode().get() == groupNode; boolean bInside = edge.getB().getGroupNode().get() == groupNode; //both if (aInside && bInside) { Rendering.drawEdge(g2d, edge, edge.getA(), edge.getB()); } else if (aInside) { SearchObjectIfParentOfGroupNode(edge.getB()).ifPresentOrElse( alternative -> Rendering.drawEdge(g2d, edge, edge.getA(), alternative), () -> Rendering.drawExternConnection(g2d, edge.getA())); } else if (bInside) { SearchObjectIfParentOfGroupNode(edge.getA()).ifPresentOrElse( alternative -> Rendering.drawEdge(g2d, edge, alternative, edge.getB()), () -> Rendering.drawExternConnection(g2d, edge.getB())); } else { Optional alternativeA = SearchObjectIfParentOfGroupNode(edge.getA()); Optional alternativeB = SearchObjectIfParentOfGroupNode(edge.getB()); if (alternativeA.isPresent() && alternativeB.isPresent() && !alternativeA.equals(alternativeB)) { Rendering.drawEdge(g2d, edge, alternativeA.get(), alternativeB.get()); } } //none }); } private Optional SearchObjectIfParentOfGroupNode(AbstractCanvasObject current) { while (current.getGroupNode().isPresent()) { if (current.getGroupNode().get() == this.groupNode) { return Optional.of(current); } ; current = current.getGroupNode().get(); } return Optional.empty(); } 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.finest(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.finest(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) && !(selectedOnPressed instanceof GroupNode)) { state = State.EdgeCreation; } } case BoxSelection -> { Rectangle selectionBox = getRectangleOfSelectionBox(); groupNode.getObjectsInThisLayer().forEach(obj -> { Rectangle bounds = getBoundsOfObject(obj); if (selectionBox.intersects(bounds) ^ selectionBeforeBoxSelection.contains(obj)) { control.addObjectToSelection(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.finest(state.toString()); switch (state) { case None -> { if (GuiSettings.getSelectedObjects().contains(selectedOnPressed)) { control.removeObjectFromSelection(selectedOnPressed); } else { control.addObjectToSelection(selectedOnPressed); } } case EdgeCreation -> getObjectAtPosition(lastPosition).ifPresentOrElse(obj -> { boolean isGroupNode = obj instanceof GroupNode; if (!isGroupNode) { control.addEdgeOnCanvasOrRemoveExisting(new Edge(selectedOnPressed, obj, GuiSettings.maxCapacityForNewCreatedEdges)); } }, () -> { Node node = new Node("Node"); groupNode.add(node); node.setPosition(new Vec2i(lastPosition)); control.addEdgeOnCanvas(new Edge(selectedOnPressed, node, GuiSettings.maxCapacityForNewCreatedEdges)); control.calculateStateForCurrentIteration(); }); } state = State.None; repaint(); } @Override public void mouseClicked(MouseEvent e) { boolean doubleLeftClick = e.getClickCount() % 2 == 0 && SwingUtilities.isLeftMouseButton(e); if (doubleLeftClick) { log.finest(state.toString()); getObjectAtPosition(new Vec2i(e.getPoint())).ifPresent(obj -> { if (obj instanceof HolonSwitch sw) { sw.setMode(HolonSwitch.SwitchMode.Manual); sw.flipManualState(); control.calculateStateForCurrentIteration(); } else if (obj instanceof GroupNode gNode) { control.showGroupNode(gNode); } }); } } 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 } } }