Canvas.java 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  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.ui.view.dialog.CreateTemplatePopUp;
  6. import holeg.utility.math.vector.Geometry;
  7. import holeg.utility.math.vector.Vec2f;
  8. import holeg.utility.math.vector.Vec2i;
  9. import javax.swing.*;
  10. import java.awt.*;
  11. import java.awt.event.MouseEvent;
  12. import java.awt.event.MouseListener;
  13. import java.awt.event.MouseMotionListener;
  14. import java.util.HashSet;
  15. import java.util.Optional;
  16. import java.util.Set;
  17. import java.util.logging.Logger;
  18. public class Canvas extends JPanel {
  19. private static final Logger log = Logger.getLogger(Canvas.class.getName());
  20. private final Control control;
  21. private final CanvasMouseListener canvasMouseListener = new CanvasMouseListener();
  22. private GroupNode groupNode;
  23. private boolean enabled = true;
  24. private final JPopupMenu componentPopupMenu = new JPopupMenu();
  25. private final JMenuItem groupMenu = new JMenuItem("Group");
  26. private final JMenuItem ungroupMenu = new JMenuItem("Ungroup");
  27. private final JMenuItem deleteMenu = new JMenuItem("Delete");
  28. private final JMenuItem templateMenu = new JMenuItem("Template");
  29. public Canvas(Control control, GroupNode groupNode) {
  30. this.control = control;
  31. this.groupNode = groupNode;
  32. control.OnGuiSetEnabled.addListener(this::setCanvasEnabled);
  33. control.OnSelectionChanged.addListener(this::repaint);
  34. control.OnCanvasUpdate.addListener(this::repaint);
  35. this.setBackground(Color.WHITE);
  36. this.setPreferredSize(new Dimension(GuiSettings.canvasSize.getX(), GuiSettings.canvasSize.getY()));
  37. this.addMouseListener(canvasMouseListener);
  38. this.addMouseMotionListener(canvasMouseListener);
  39. initPopupMenu();
  40. }
  41. private void initPopupMenu() {
  42. componentPopupMenu.add(deleteMenu);
  43. componentPopupMenu.addSeparator();
  44. componentPopupMenu.add(groupMenu);
  45. componentPopupMenu.add(ungroupMenu);
  46. componentPopupMenu.addSeparator();
  47. componentPopupMenu.add(templateMenu);
  48. deleteMenu.addActionListener(clicked -> {
  49. control.deleteCanvasObjects(GuiSettings.getSelectedObjects());
  50. control.clearSelection();
  51. });
  52. groupMenu.addActionListener(clicked -> control.group());
  53. ungroupMenu.addActionListener(clicked -> control.ungroup());
  54. templateMenu.addActionListener(clicked -> GuiSettings.getSelectedObjects().stream().findAny().ifPresent(obj -> {
  55. HolonObject hObject = (HolonObject)obj;
  56. new CreateTemplatePopUp(hObject, (JFrame) SwingUtilities.getWindowAncestor(this), control);
  57. }));
  58. }
  59. public static Rectangle getBoundsOfObject(AbstractCanvasObject obj) {
  60. int pictureScale = GuiSettings.getPictureScale();
  61. int pictureScaleDiv2 = GuiSettings.getPictureScaleDiv2();
  62. Vec2i pos = obj.getPosition();
  63. return new Rectangle(pos.getX() - pictureScaleDiv2, pos.getY() - pictureScaleDiv2, pictureScale, pictureScale);
  64. }
  65. private void setCanvasEnabled(boolean state) {
  66. enabled = state;
  67. }
  68. public GroupNode getGroupNode() {
  69. return this.groupNode;
  70. }
  71. public void setGroupNode(GroupNode groupNode) {
  72. this.groupNode = groupNode;
  73. }
  74. @Override
  75. public void paintComponent(java.awt.Graphics g) {
  76. super.paintComponent(g);
  77. Graphics2D g2d = Rendering.initGraphics2D(g);
  78. Rendering.drawSelection(g2d);
  79. paintEdges(g2d);
  80. groupNode.getHolonObjects().forEach(hO -> Rendering.drawHolonObject(g2d, hO));
  81. groupNode.getSwitches().forEach(hS -> Rendering.drawSwitchObject(g2d, hS));
  82. groupNode.getGroupNodes().forEach(groupNode -> Rendering.drawGroupNode(g2d, groupNode));
  83. groupNode.getNodes().forEach(node -> Rendering.drawNode(g2d, node));
  84. switch (canvasMouseListener.state) {
  85. case BoxSelection -> Rendering.drawSelectionBox(g2d, canvasMouseListener.getRectangleOfSelectionBox());
  86. case EdgeCreation -> Rendering.drawNewEdgeLine(g2d, canvasMouseListener.selectedOnPressed.getPosition(), canvasMouseListener.lastPosition);
  87. }
  88. if(canvasMouseListener.canBeReplaced){
  89. Rendering.drawReplacementSymbol(g2d, canvasMouseListener.selectedOnPressed);
  90. }
  91. }
  92. private void paintEdges(Graphics2D g2d) {
  93. control.getModel().getEdgesOnCanvas().forEach(edge -> {
  94. if (edge.getA().getGroupNode().isEmpty() || edge.getB().getGroupNode().isEmpty()) {
  95. return;
  96. }
  97. boolean aInside = edge.getA().getGroupNode().get() == groupNode;
  98. boolean bInside = edge.getB().getGroupNode().get() == groupNode;
  99. //both
  100. if (aInside && bInside) {
  101. Rendering.drawEdge(g2d, edge, edge.getA(), edge.getB());
  102. } else if (aInside) {
  103. SearchObjectIfParentOfGroupNode(edge.getB()).ifPresentOrElse(
  104. alternative -> Rendering.drawEdge(g2d, edge, edge.getA(), alternative),
  105. () -> Rendering.drawExternConnection(g2d, edge.getA()));
  106. } else if (bInside) {
  107. SearchObjectIfParentOfGroupNode(edge.getA()).ifPresentOrElse(
  108. alternative -> Rendering.drawEdge(g2d, edge, alternative, edge.getB()),
  109. () -> Rendering.drawExternConnection(g2d, edge.getB()));
  110. } else {
  111. Optional<AbstractCanvasObject> alternativeA = SearchObjectIfParentOfGroupNode(edge.getA());
  112. Optional<AbstractCanvasObject> alternativeB = SearchObjectIfParentOfGroupNode(edge.getB());
  113. if (alternativeA.isPresent() && alternativeB.isPresent() && !alternativeA.equals(alternativeB)) {
  114. Rendering.drawEdge(g2d, edge, alternativeA.get(), alternativeB.get());
  115. }
  116. }
  117. //none
  118. });
  119. }
  120. private Optional<AbstractCanvasObject> SearchObjectIfParentOfGroupNode(AbstractCanvasObject current) {
  121. while (current.getGroupNode().isPresent()) {
  122. if (current.getGroupNode().get() == this.groupNode) {
  123. return Optional.of(current);
  124. }
  125. current = current.getGroupNode().get();
  126. }
  127. return Optional.empty();
  128. }
  129. private Optional<AbstractCanvasObject> getObjectAtPosition(Vec2i pos) {
  130. return groupNode.getObjectsInThisLayer().filter(obj ->
  131. getBoundsOfObject(obj).contains(pos.getX(), pos.getY())
  132. ).findAny();
  133. }
  134. /**
  135. * Microsoft Windows10 selection & dragging behavior
  136. */
  137. private class CanvasMouseListener implements MouseListener, MouseMotionListener {
  138. private Vec2i lastPosition = new Vec2i();
  139. private Vec2i pressedPosition = new Vec2i();
  140. private Set<AbstractCanvasObject> selectionBeforeBoxSelection = new HashSet<>();
  141. private State state = State.None;
  142. private AbstractCanvasObject selectedOnPressed = null;
  143. private boolean canBeReplaced = false;
  144. @Override
  145. public void mousePressed(MouseEvent e) {
  146. if (!enabled) {
  147. return;
  148. }
  149. log.finest(state.toString());
  150. if (!e.isControlDown()) {
  151. GuiSettings.getSelectedObjects().clear();
  152. }
  153. Vec2i pos = new Vec2i(e.getPoint());
  154. getObjectAtPosition(pos).ifPresentOrElse(obj -> {
  155. if (!GuiSettings.getSelectedObjects().contains(obj)) {
  156. state = State.Selection;
  157. GuiSettings.getSelectedObjects().add(obj);
  158. }
  159. selectedOnPressed = obj;
  160. }, () -> {
  161. state = State.BoxSelection;
  162. selectionBeforeBoxSelection = Set.copyOf(GuiSettings.getSelectedObjects());
  163. });
  164. control.OnSelectionChanged.broadcast();
  165. lastPosition = pressedPosition = pos;
  166. }
  167. @Override
  168. public void mouseDragged(MouseEvent e) {
  169. if (!enabled) {
  170. return;
  171. }
  172. log.finest(state.toString());
  173. Vec2i actualPos = new Vec2i(e.getPoint());
  174. switch (state) {
  175. case Selection -> {
  176. // Not handle to small mouse dragging
  177. if (!(pressedPosition.getSquaredDistance(actualPos) > GuiSettings.dragThresholdDistance)) {
  178. return;
  179. }
  180. if (SwingUtilities.isLeftMouseButton(e)) {
  181. state = State.ObjectDragging;
  182. } else if (SwingUtilities.isRightMouseButton(e) && !(selectedOnPressed instanceof GroupNode)) {
  183. state = State.EdgeCreation;
  184. }
  185. }
  186. case BoxSelection -> {
  187. Rectangle selectionBox = getRectangleOfSelectionBox();
  188. groupNode.getObjectsInThisLayer().forEach(obj -> {
  189. Rectangle bounds = getBoundsOfObject(obj);
  190. if (selectionBox.intersects(bounds) ^ selectionBeforeBoxSelection.contains(obj)) {
  191. GuiSettings.getSelectedObjects().add(obj);
  192. } else {
  193. GuiSettings.getSelectedObjects().remove(obj);
  194. }
  195. });
  196. repaint();
  197. }
  198. case ObjectDragging -> {
  199. Vec2i delta = actualPos.subtract(lastPosition);
  200. GuiSettings.getSelectedObjects().forEach(obj -> obj.setPosition(boundsToCanvas(obj.getPosition().add(delta))));
  201. canBeReplaced = checkForReplacement(actualPos).isPresent();
  202. repaint();
  203. }
  204. case EdgeCreation -> repaint();
  205. }
  206. lastPosition = actualPos;
  207. }
  208. private Optional<AbstractCanvasObject> checkForReplacement(Vec2i pos){
  209. return groupNode.getObjectsInThisLayer().filter(obj -> obj != selectedOnPressed &&
  210. getBoundsOfObject(obj).contains(pos.getX(), pos.getY())
  211. ).findAny();
  212. }
  213. @Override
  214. public void mouseReleased(MouseEvent e) {
  215. if (!enabled) {
  216. return;
  217. }
  218. log.info(state.toString());
  219. switch (state) {
  220. case None -> {
  221. if(SwingUtilities.isRightMouseButton(e)){
  222. preparePopupMenu();
  223. componentPopupMenu.show(Canvas.this, e.getX(), e.getY());
  224. }else {
  225. if (GuiSettings.getSelectedObjects().contains(selectedOnPressed)) {
  226. control.removeObjectFromSelection(selectedOnPressed);
  227. } else {
  228. control.addObjectToSelection(selectedOnPressed);
  229. }
  230. }
  231. }
  232. case Selection, BoxSelection -> {
  233. control.OnSelectionChanged.broadcast();
  234. if(SwingUtilities.isRightMouseButton(e)){
  235. preparePopupMenu();
  236. componentPopupMenu.show(Canvas.this, e.getX(), e.getY());
  237. }
  238. }
  239. case EdgeCreation -> getObjectAtPosition(lastPosition).ifPresentOrElse(obj -> {
  240. boolean isGroupNode = obj instanceof GroupNode;
  241. if (!isGroupNode) {
  242. control.addEdgeOnCanvasOrRemoveExisting(new Edge(selectedOnPressed, obj, GuiSettings.maxCapacityForNewCreatedEdges));
  243. }
  244. }, () -> {
  245. Node node = new Node("Node");
  246. groupNode.add(node);
  247. final float splitDetectionDistance = 15f;
  248. Geometry.Circle detectionCircle = new Geometry.Circle(new Vec2f(lastPosition), splitDetectionDistance);
  249. node.setPosition(new Vec2i(boundsToCanvas(lastPosition)));
  250. for(Edge edge : control.getModel().getEdgesOnCanvas()) {
  251. if(edge.getA().getGroupNode().isEmpty() || edge.getB().getGroupNode().isEmpty() ||
  252. edge.getA().getGroupNode().get() != groupNode || edge.getB().getGroupNode().get() != groupNode){
  253. continue;
  254. }
  255. Optional<Vec2f> pos = Geometry.getProjectionOnSegmentIfInRange(new Geometry.Line(new Vec2f(edge.getA().getPosition())
  256. , new Vec2f(edge.getB().getPosition())), detectionCircle);
  257. if(pos.isPresent()){
  258. Vec2f position = pos.get();
  259. node.setPosition(new Vec2i((int)position.getX(), (int)position.getY()));
  260. splitEdge(edge, node);
  261. break;
  262. }
  263. }
  264. control.addEdgeOnCanvas(new Edge(selectedOnPressed, node, GuiSettings.maxCapacityForNewCreatedEdges));
  265. control.calculateStateForCurrentIteration();
  266. });
  267. case ObjectDragging -> checkForReplacement(new Vec2i(e.getPoint())).ifPresent(obj -> control.replaceCanvasObject(obj, selectedOnPressed));
  268. }
  269. canBeReplaced = false;
  270. state = State.None;
  271. repaint();
  272. }
  273. private void preparePopupMenu(){
  274. int count = GuiSettings.getSelectedObjects().size();
  275. boolean isAGroupNodeSelected = GuiSettings.getSelectedObjects().stream().anyMatch(obj -> obj instanceof GroupNode);
  276. switch (count){
  277. case 0 -> {
  278. groupMenu.setEnabled(false);
  279. ungroupMenu.setEnabled(false);
  280. deleteMenu.setEnabled(false);
  281. templateMenu.setEnabled(false);
  282. }
  283. case 1 -> {
  284. deleteMenu.setEnabled(true);
  285. boolean isSelectedObjectAHolonObject = GuiSettings.getSelectedObjects().stream().anyMatch(obj -> obj instanceof HolonObject);
  286. templateMenu.setEnabled(isSelectedObjectAHolonObject);
  287. groupMenu.setEnabled(true);
  288. ungroupMenu.setEnabled(isAGroupNodeSelected);
  289. }
  290. default -> {
  291. deleteMenu.setEnabled(true);
  292. templateMenu.setEnabled(false);
  293. groupMenu.setEnabled(true);
  294. ungroupMenu.setEnabled(isAGroupNodeSelected);
  295. }
  296. }
  297. }
  298. @Override
  299. public void mouseClicked(MouseEvent e) {
  300. boolean doubleLeftClick = e.getClickCount() % 2 == 0 && SwingUtilities.isLeftMouseButton(e);
  301. if (doubleLeftClick) {
  302. log.finest(state.toString());
  303. getObjectAtPosition(new Vec2i(e.getPoint())).ifPresent(obj -> {
  304. if (obj instanceof HolonSwitch sw) {
  305. sw.setMode(HolonSwitch.SwitchMode.Manual);
  306. sw.flipManualState();
  307. control.calculateStateForCurrentIteration();
  308. } else if (obj instanceof GroupNode gNode) {
  309. control.showGroupNode(gNode);
  310. }
  311. });
  312. }
  313. }
  314. private Vec2i boundsToCanvas(Vec2i pos){
  315. Vec2i position = new Vec2i(pos);
  316. position.clampX(GuiSettings.getPictureScaleDiv2(), GuiSettings.canvasSize.getX() - GuiSettings.getPictureScaleDiv2());
  317. position.clampY(GuiSettings.getPictureScaleDiv2(), GuiSettings.canvasSize.getY() - GuiSettings.getPictureScaleDiv2());
  318. return position;
  319. }
  320. Rectangle getRectangleOfSelectionBox() {
  321. return Geometry.createRectangleFromCorners(lastPosition, pressedPosition);
  322. }
  323. public void splitEdge(Edge edge, Node node){
  324. AbstractCanvasObject end = edge.getB();
  325. edge.setB(node);
  326. Edge additional = new Edge(node, end, edge.maxCapacity);
  327. control.getModel().addEdgeOnCanvas(additional);
  328. }
  329. @Override
  330. public void mouseEntered(MouseEvent e) {
  331. }
  332. @Override
  333. public void mouseExited(MouseEvent e) {
  334. }
  335. @Override
  336. public void mouseMoved(MouseEvent e) {
  337. }
  338. private enum State {
  339. None, BoxSelection, EdgeCreation, ObjectDragging, Selection
  340. }
  341. }
  342. }