Canvas.java 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. package holeg.ui.view.canvas;
  2. import holeg.model.*;
  3. import holeg.ui.controller.Control;
  4. import holeg.ui.model.GuiSettings;
  5. import holeg.utility.math.vector.Geometry;
  6. import holeg.utility.math.vector.Vec2i;
  7. import javax.swing.*;
  8. import java.awt.*;
  9. import java.awt.event.MouseEvent;
  10. import java.awt.event.MouseListener;
  11. import java.awt.event.MouseMotionListener;
  12. import java.util.HashSet;
  13. import java.util.Optional;
  14. import java.util.Set;
  15. import java.util.logging.Logger;
  16. public class Canvas extends JPanel {
  17. private static final Logger log = Logger.getLogger(Canvas.class.getName());
  18. private final Control control;
  19. private final CanvasMouseListener canvasMouseListener = new CanvasMouseListener();
  20. private GroupNode groupNode;
  21. private boolean enabled = true;
  22. public Canvas(Control control, GroupNode groupNode) {
  23. this.control = control;
  24. this.groupNode = groupNode;
  25. control.OnGuiSetEnabled.addListener(this::setCanvasEnabled);
  26. control.OnSelectionChanged.addListener(this::repaint);
  27. control.OnCanvasUpdate.addListener(this::repaint);
  28. this.setBackground(Color.WHITE);
  29. this.setPreferredSize(new Dimension(GuiSettings.canvasSize.getX(), GuiSettings.canvasSize.getY()));
  30. this.addMouseListener(canvasMouseListener);
  31. this.addMouseMotionListener(canvasMouseListener);
  32. }
  33. public static Rectangle getBoundsOfObject(AbstractCanvasObject obj) {
  34. int pictureScale = GuiSettings.getPictureScale();
  35. int pictureScaleDiv2 = GuiSettings.getPictureScaleDiv2();
  36. Vec2i pos = obj.getPosition();
  37. return new Rectangle(pos.getX() - pictureScaleDiv2, pos.getY() - pictureScaleDiv2, pictureScale, pictureScale);
  38. }
  39. private void setCanvasEnabled(boolean state) {
  40. enabled = state;
  41. }
  42. public GroupNode getGroupNode() {
  43. return this.groupNode;
  44. }
  45. public void setGroupNode(GroupNode groupNode) {
  46. this.groupNode = groupNode;
  47. }
  48. @Override
  49. public void paintComponent(java.awt.Graphics g) {
  50. super.paintComponent(g);
  51. Graphics2D g2d = Rendering.initGraphics2D(g);
  52. Rendering.drawSelection(g2d);
  53. paintEdges(g2d);
  54. groupNode.getHolonObjects().forEach(hO -> Rendering.drawHolonObject(g2d, hO));
  55. groupNode.getSwitches().forEach(hS -> Rendering.drawSwitchObject(g2d, hS));
  56. groupNode.getGroupNodes().forEach(groupNode -> Rendering.drawGroupNode(g2d, groupNode));
  57. groupNode.getNodes().forEach(node -> Rendering.drawNode(g2d, node));
  58. switch (canvasMouseListener.state) {
  59. case BoxSelection -> Rendering.drawSelectionBox(g2d, canvasMouseListener.getRectangleOfSelectionBox());
  60. case EdgeCreation -> Rendering.drawNewEdgeLine(g2d, canvasMouseListener.selectedOnPressed.getPosition(), canvasMouseListener.lastPosition);
  61. }
  62. }
  63. private void paintEdges(Graphics2D g2d) {
  64. control.getModel().getEdgesOnCanvas().forEach(edge -> {
  65. if (edge.getA().getGroupNode().isEmpty() || edge.getB().getGroupNode().isEmpty()) {
  66. return;
  67. }
  68. boolean aInside = edge.getA().getGroupNode().get() == groupNode;
  69. boolean bInside = edge.getB().getGroupNode().get() == groupNode;
  70. //both
  71. if (aInside && bInside) {
  72. Rendering.drawEdge(g2d, edge, edge.getA(), edge.getB());
  73. } else if (aInside) {
  74. SearchObjectIfParentOfGroupNode(edge.getB()).ifPresentOrElse(
  75. alternative -> Rendering.drawEdge(g2d, edge, edge.getA(), alternative),
  76. () -> Rendering.drawExternConnection(g2d, edge.getA()));
  77. } else if (bInside) {
  78. SearchObjectIfParentOfGroupNode(edge.getA()).ifPresentOrElse(
  79. alternative -> Rendering.drawEdge(g2d, edge, alternative, edge.getB()),
  80. () -> Rendering.drawExternConnection(g2d, edge.getB()));
  81. } else {
  82. Optional<AbstractCanvasObject> alternativeA = SearchObjectIfParentOfGroupNode(edge.getA());
  83. Optional<AbstractCanvasObject> alternativeB = SearchObjectIfParentOfGroupNode(edge.getB());
  84. if (alternativeA.isPresent() && alternativeB.isPresent() && !alternativeA.equals(alternativeB)) {
  85. Rendering.drawEdge(g2d, edge, alternativeA.get(), alternativeB.get());
  86. }
  87. }
  88. //none
  89. });
  90. }
  91. private Optional<AbstractCanvasObject> SearchObjectIfParentOfGroupNode(AbstractCanvasObject current) {
  92. while (current.getGroupNode().isPresent()) {
  93. if (current.getGroupNode().get() == this.groupNode) {
  94. return Optional.of(current);
  95. }
  96. ;
  97. current = current.getGroupNode().get();
  98. }
  99. return Optional.empty();
  100. }
  101. private Optional<AbstractCanvasObject> getObjectAtPosition(Vec2i pos) {
  102. return groupNode.getObjectsInThisLayer().filter(obj ->
  103. getBoundsOfObject(obj).contains(pos.getX(), pos.getY())
  104. ).findAny();
  105. }
  106. //TODO(Tom2022-01-17): checkForReplacement replace with getObjectAtPosition
  107. public Optional<AbstractCanvasObject> checkForReplacement(int x, int y) {
  108. return Optional.empty();
  109. }
  110. /**
  111. * Microsoft Windows10 selection & dragging behavior
  112. */
  113. private class CanvasMouseListener implements MouseListener, MouseMotionListener {
  114. private Vec2i lastPosition = new Vec2i();
  115. private Vec2i pressedPosition = new Vec2i();
  116. private Set<AbstractCanvasObject> selectionBeforeBoxSelection = new HashSet<>();
  117. private State state = State.None;
  118. private AbstractCanvasObject selectedOnPressed = null;
  119. @Override
  120. public void mousePressed(MouseEvent e) {
  121. if (!enabled) {
  122. return;
  123. }
  124. log.finest(state.toString());
  125. if (!e.isControlDown()) {
  126. GuiSettings.getSelectedObjects().clear();
  127. }
  128. Vec2i pos = new Vec2i(e.getPoint());
  129. getObjectAtPosition(pos).ifPresentOrElse(obj -> {
  130. if (!GuiSettings.getSelectedObjects().contains(obj)) {
  131. state = State.Selection;
  132. GuiSettings.getSelectedObjects().add(obj);
  133. }
  134. selectedOnPressed = obj;
  135. }, () -> {
  136. state = State.BoxSelection;
  137. selectionBeforeBoxSelection = Set.copyOf(GuiSettings.getSelectedObjects());
  138. });
  139. control.OnSelectionChanged.broadcast();
  140. lastPosition = pressedPosition = pos;
  141. }
  142. @Override
  143. public void mouseDragged(MouseEvent e) {
  144. if (!enabled) {
  145. return;
  146. }
  147. log.finest(state.toString());
  148. Vec2i actualPos = new Vec2i(e.getPoint());
  149. switch (state) {
  150. case None, Selection -> {
  151. // Not handle to small mouse dragging
  152. if (!(pressedPosition.getSquaredDistance(actualPos) > GuiSettings.dragThresholdDistance)) {
  153. return;
  154. }
  155. if (SwingUtilities.isLeftMouseButton(e)) {
  156. state = State.ObjectDragging;
  157. } else if (SwingUtilities.isRightMouseButton(e) && !(selectedOnPressed instanceof GroupNode)) {
  158. state = State.EdgeCreation;
  159. }
  160. }
  161. case BoxSelection -> {
  162. Rectangle selectionBox = getRectangleOfSelectionBox();
  163. groupNode.getObjectsInThisLayer().forEach(obj -> {
  164. Rectangle bounds = getBoundsOfObject(obj);
  165. if (selectionBox.intersects(bounds) ^ selectionBeforeBoxSelection.contains(obj)) {
  166. control.addObjectToSelection(obj);
  167. } else {
  168. control.removeObjectFromSelection(obj);
  169. }
  170. });
  171. repaint();
  172. }
  173. case ObjectDragging -> {
  174. Vec2i delta = actualPos.subtract(lastPosition);
  175. GuiSettings.getSelectedObjects().forEach(obj -> obj.getPosition().addAssign(delta));
  176. repaint();
  177. }
  178. case EdgeCreation -> repaint();
  179. }
  180. lastPosition = actualPos;
  181. }
  182. @Override
  183. public void mouseReleased(MouseEvent e) {
  184. if (!enabled) {
  185. return;
  186. }
  187. log.finest(state.toString());
  188. switch (state) {
  189. case None -> {
  190. if (GuiSettings.getSelectedObjects().contains(selectedOnPressed)) {
  191. control.removeObjectFromSelection(selectedOnPressed);
  192. } else {
  193. control.addObjectToSelection(selectedOnPressed);
  194. }
  195. }
  196. case EdgeCreation -> getObjectAtPosition(lastPosition).ifPresentOrElse(obj -> {
  197. boolean isGroupNode = obj instanceof GroupNode;
  198. if (!isGroupNode) {
  199. control.addEdgeOnCanvasOrRemoveExisting(new Edge(selectedOnPressed, obj, GuiSettings.maxCapacityForNewCreatedEdges));
  200. }
  201. }, () -> {
  202. Node node = new Node("Node");
  203. groupNode.add(node);
  204. node.setPosition(new Vec2i(lastPosition));
  205. control.addEdgeOnCanvas(new Edge(selectedOnPressed, node, GuiSettings.maxCapacityForNewCreatedEdges));
  206. control.calculateStateForCurrentIteration();
  207. });
  208. }
  209. state = State.None;
  210. repaint();
  211. }
  212. @Override
  213. public void mouseClicked(MouseEvent e) {
  214. boolean doubleLeftClick = e.getClickCount() % 2 == 0 && SwingUtilities.isLeftMouseButton(e);
  215. if (doubleLeftClick) {
  216. log.finest(state.toString());
  217. getObjectAtPosition(new Vec2i(e.getPoint())).ifPresent(obj -> {
  218. if (obj instanceof HolonSwitch sw) {
  219. sw.setMode(HolonSwitch.SwitchMode.Manual);
  220. sw.flipManualState();
  221. control.calculateStateForCurrentIteration();
  222. } else if (obj instanceof GroupNode gNode) {
  223. control.showGroupNode(gNode);
  224. }
  225. });
  226. }
  227. }
  228. Rectangle getRectangleOfSelectionBox() {
  229. return Geometry.createRectangleFromCorners(lastPosition, pressedPosition);
  230. }
  231. @Override
  232. public void mouseEntered(MouseEvent e) {
  233. }
  234. @Override
  235. public void mouseExited(MouseEvent e) {
  236. }
  237. @Override
  238. public void mouseMoved(MouseEvent e) {
  239. }
  240. private enum State {
  241. None, BoxSelection, EdgeCreation, ObjectDragging, Selection
  242. }
  243. }
  244. }