Canvas.java 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  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. g2d.setColor(Color.gray);
  79. g2d.fillRect(0,0,getWidth(), getHeight());
  80. g2d.setColor(Color.white);
  81. g2d.fillRect(0,0,GuiSettings.canvasSize.getX(), GuiSettings.canvasSize.getY());
  82. Rendering.drawSelection(g2d);
  83. paintEdges(g2d);
  84. groupNode.getHolonObjects().forEach(hO -> Rendering.drawHolonObject(g2d, hO));
  85. groupNode.getSwitches().forEach(hS -> Rendering.drawSwitchObject(g2d, hS));
  86. groupNode.getGroupNodes().forEach(groupNode -> Rendering.drawGroupNode(g2d, groupNode));
  87. groupNode.getNodes().forEach(node -> Rendering.drawNode(g2d, node));
  88. switch (canvasMouseListener.state) {
  89. case BoxSelection -> Rendering.drawSelectionBox(g2d, canvasMouseListener.getRectangleOfSelectionBox());
  90. case EdgeCreation -> Rendering.drawNewEdgeLine(g2d, canvasMouseListener.selectedOnPressed.getPosition(), canvasMouseListener.lastPosition);
  91. }
  92. if(canvasMouseListener.canBeReplaced){
  93. Rendering.drawReplacementSymbol(g2d, canvasMouseListener.selectedOnPressed);
  94. }
  95. }
  96. private void paintEdges(Graphics2D g2d) {
  97. control.getModel().getEdgesOnCanvas().forEach(edge -> {
  98. if (edge.getA().getGroupNode().isEmpty() || edge.getB().getGroupNode().isEmpty()) {
  99. return;
  100. }
  101. boolean aInside = edge.getA().getGroupNode().get() == groupNode;
  102. boolean bInside = edge.getB().getGroupNode().get() == groupNode;
  103. //both
  104. if (aInside && bInside) {
  105. Rendering.drawEdge(g2d, edge, edge.getA(), edge.getB());
  106. } else if (aInside) {
  107. SearchObjectIfParentOfGroupNode(edge.getB()).ifPresentOrElse(
  108. alternative -> Rendering.drawEdge(g2d, edge, edge.getA(), alternative),
  109. () -> Rendering.drawExternConnection(g2d, edge.getA()));
  110. } else if (bInside) {
  111. SearchObjectIfParentOfGroupNode(edge.getA()).ifPresentOrElse(
  112. alternative -> Rendering.drawEdge(g2d, edge, alternative, edge.getB()),
  113. () -> Rendering.drawExternConnection(g2d, edge.getB()));
  114. } else {
  115. Optional<AbstractCanvasObject> alternativeA = SearchObjectIfParentOfGroupNode(edge.getA());
  116. Optional<AbstractCanvasObject> alternativeB = SearchObjectIfParentOfGroupNode(edge.getB());
  117. if (alternativeA.isPresent() && alternativeB.isPresent() && !alternativeA.equals(alternativeB)) {
  118. Rendering.drawEdge(g2d, edge, alternativeA.get(), alternativeB.get());
  119. }
  120. }
  121. //none
  122. });
  123. }
  124. private Optional<AbstractCanvasObject> SearchObjectIfParentOfGroupNode(AbstractCanvasObject current) {
  125. while (current.getGroupNode().isPresent()) {
  126. if (current.getGroupNode().get() == this.groupNode) {
  127. return Optional.of(current);
  128. }
  129. current = current.getGroupNode().get();
  130. }
  131. return Optional.empty();
  132. }
  133. private Optional<AbstractCanvasObject> getObjectAtPosition(Vec2i pos) {
  134. return groupNode.getObjectsInThisLayer().filter(obj ->
  135. getBoundsOfObject(obj).contains(pos.getX(), pos.getY())
  136. ).findAny();
  137. }
  138. public static Vec2i boundsToCanvas(Vec2i pos){
  139. Vec2i position = new Vec2i(pos);
  140. position.clampX(GuiSettings.getPictureScaleDiv2(), GuiSettings.canvasSize.getX() - GuiSettings.getPictureScaleDiv2());
  141. position.clampY(GuiSettings.getPictureScaleDiv2(), GuiSettings.canvasSize.getY() - GuiSettings.getPictureScaleDiv2());
  142. return position;
  143. }
  144. /**
  145. * Microsoft Windows10 selection & dragging behavior
  146. */
  147. private class CanvasMouseListener implements MouseListener, MouseMotionListener {
  148. private Vec2i lastPosition = new Vec2i();
  149. private Vec2i pressedPosition = new Vec2i();
  150. private Set<AbstractCanvasObject> selectionBeforeBoxSelection = new HashSet<>();
  151. private State state = State.None;
  152. private AbstractCanvasObject selectedOnPressed = null;
  153. private boolean canBeReplaced = false;
  154. @Override
  155. public void mousePressed(MouseEvent e) {
  156. if (!enabled) {
  157. return;
  158. }
  159. log.finest(state.toString());
  160. Vec2i pos = new Vec2i(e.getPoint());
  161. getObjectAtPosition(pos).ifPresentOrElse(obj -> {
  162. if (!e.isControlDown() && !GuiSettings.getSelectedObjects().contains(obj)) {
  163. GuiSettings.getSelectedObjects().clear();
  164. }
  165. state = State.Selection;
  166. GuiSettings.getSelectedObjects().add(obj);
  167. selectedOnPressed = obj;
  168. }, () -> {
  169. if (!e.isControlDown()) {
  170. GuiSettings.getSelectedObjects().clear();
  171. }
  172. state = State.BoxSelection;
  173. selectionBeforeBoxSelection = Set.copyOf(GuiSettings.getSelectedObjects());
  174. });
  175. control.OnSelectionChanged.broadcast();
  176. lastPosition = pressedPosition = pos;
  177. }
  178. @Override
  179. public void mouseDragged(MouseEvent e) {
  180. if (!enabled) {
  181. return;
  182. }
  183. log.finest(state.toString());
  184. Vec2i actualPos = new Vec2i(e.getPoint());
  185. switch (state) {
  186. case Selection -> {
  187. // Not handle to small mouse dragging
  188. if (!(pressedPosition.getSquaredDistance(actualPos) > GuiSettings.dragThresholdDistance)) {
  189. return;
  190. }
  191. if (SwingUtilities.isLeftMouseButton(e)) {
  192. state = State.ObjectDragging;
  193. } else if (SwingUtilities.isRightMouseButton(e) && !(selectedOnPressed instanceof GroupNode)) {
  194. state = State.EdgeCreation;
  195. }
  196. }
  197. case BoxSelection -> {
  198. Rectangle selectionBox = getRectangleOfSelectionBox();
  199. groupNode.getObjectsInThisLayer().forEach(obj -> {
  200. Rectangle bounds = getBoundsOfObject(obj);
  201. if (selectionBox.intersects(bounds) ^ selectionBeforeBoxSelection.contains(obj)) {
  202. GuiSettings.getSelectedObjects().add(obj);
  203. } else {
  204. GuiSettings.getSelectedObjects().remove(obj);
  205. }
  206. });
  207. repaint();
  208. }
  209. case ObjectDragging -> {
  210. Vec2i delta = actualPos.subtract(lastPosition);
  211. GuiSettings.getSelectedObjects().forEach(obj -> obj.setPosition(boundsToCanvas(obj.getPosition().add(delta))));
  212. canBeReplaced = checkForReplacement(actualPos).isPresent();
  213. repaint();
  214. }
  215. case EdgeCreation -> repaint();
  216. }
  217. lastPosition = actualPos;
  218. }
  219. private Optional<AbstractCanvasObject> checkForReplacement(Vec2i pos){
  220. return groupNode.getObjectsInThisLayer().filter(obj -> obj != selectedOnPressed &&
  221. getBoundsOfObject(obj).contains(pos.getX(), pos.getY())
  222. ).findAny();
  223. }
  224. @Override
  225. public void mouseReleased(MouseEvent e) {
  226. if (!enabled) {
  227. return;
  228. }
  229. log.info(state.toString());
  230. switch (state) {
  231. case None -> {
  232. if(SwingUtilities.isRightMouseButton(e)){
  233. preparePopupMenu();
  234. componentPopupMenu.show(Canvas.this, e.getX(), e.getY());
  235. }else {
  236. if (GuiSettings.getSelectedObjects().contains(selectedOnPressed)) {
  237. control.removeObjectFromSelection(selectedOnPressed);
  238. } else {
  239. control.addObjectToSelection(selectedOnPressed);
  240. }
  241. }
  242. }
  243. case Selection, BoxSelection -> {
  244. control.OnSelectionChanged.broadcast();
  245. if(SwingUtilities.isRightMouseButton(e)){
  246. preparePopupMenu();
  247. componentPopupMenu.show(Canvas.this, e.getX(), e.getY());
  248. }
  249. }
  250. case EdgeCreation -> getObjectAtPosition(lastPosition).ifPresentOrElse(obj -> {
  251. boolean isGroupNode = obj instanceof GroupNode;
  252. if (!isGroupNode) {
  253. control.addEdgeOnCanvasOrRemoveExisting(new Edge(selectedOnPressed, obj, GuiSettings.maxCapacityForNewCreatedEdges));
  254. }
  255. }, () -> {
  256. Node node = new Node("Node");
  257. groupNode.add(node);
  258. final float splitDetectionDistance = 15f;
  259. Geometry.Circle detectionCircle = new Geometry.Circle(new Vec2f(lastPosition), splitDetectionDistance);
  260. node.setPosition(new Vec2i(boundsToCanvas(lastPosition)));
  261. for(Edge edge : control.getModel().getEdgesOnCanvas()) {
  262. if(edge.getA().getGroupNode().isEmpty() || edge.getB().getGroupNode().isEmpty() ||
  263. edge.getA().getGroupNode().get() != groupNode || edge.getB().getGroupNode().get() != groupNode){
  264. continue;
  265. }
  266. Optional<Vec2f> pos = Geometry.getProjectionOnSegmentIfInRange(new Geometry.Line(new Vec2f(edge.getA().getPosition())
  267. , new Vec2f(edge.getB().getPosition())), detectionCircle);
  268. if(pos.isPresent()){
  269. Vec2f position = pos.get();
  270. node.setPosition(new Vec2i((int)position.getX(), (int)position.getY()));
  271. splitEdge(edge, node);
  272. break;
  273. }
  274. }
  275. control.addEdgeOnCanvas(new Edge(selectedOnPressed, node, GuiSettings.maxCapacityForNewCreatedEdges));
  276. control.calculateStateForCurrentIteration();
  277. });
  278. case ObjectDragging -> checkForReplacement(new Vec2i(e.getPoint())).ifPresent(obj -> control.replaceCanvasObject(obj, selectedOnPressed));
  279. }
  280. canBeReplaced = false;
  281. state = State.None;
  282. repaint();
  283. }
  284. private void preparePopupMenu(){
  285. int count = GuiSettings.getSelectedObjects().size();
  286. boolean isAGroupNodeSelected = GuiSettings.getSelectedObjects().stream().anyMatch(obj -> obj instanceof GroupNode);
  287. switch (count){
  288. case 0 -> {
  289. groupMenu.setEnabled(false);
  290. ungroupMenu.setEnabled(false);
  291. deleteMenu.setEnabled(false);
  292. templateMenu.setEnabled(false);
  293. }
  294. case 1 -> {
  295. deleteMenu.setEnabled(true);
  296. boolean isSelectedObjectAHolonObject = GuiSettings.getSelectedObjects().stream().anyMatch(obj -> obj instanceof HolonObject);
  297. templateMenu.setEnabled(isSelectedObjectAHolonObject);
  298. groupMenu.setEnabled(true);
  299. ungroupMenu.setEnabled(isAGroupNodeSelected);
  300. }
  301. default -> {
  302. deleteMenu.setEnabled(true);
  303. templateMenu.setEnabled(false);
  304. groupMenu.setEnabled(true);
  305. ungroupMenu.setEnabled(isAGroupNodeSelected);
  306. }
  307. }
  308. }
  309. @Override
  310. public void mouseClicked(MouseEvent e) {
  311. boolean doubleLeftClick = e.getClickCount() % 2 == 0 && SwingUtilities.isLeftMouseButton(e);
  312. if (doubleLeftClick) {
  313. log.finest(state.toString());
  314. getObjectAtPosition(new Vec2i(e.getPoint())).ifPresent(obj -> {
  315. if (obj instanceof HolonSwitch sw) {
  316. sw.setMode(HolonSwitch.SwitchMode.Manual);
  317. sw.flipManualState();
  318. control.calculateStateForCurrentIteration();
  319. } else if (obj instanceof GroupNode gNode) {
  320. control.showGroupNode(gNode);
  321. }
  322. });
  323. }
  324. }
  325. Rectangle getRectangleOfSelectionBox() {
  326. return Geometry.createRectangleFromCorners(lastPosition, pressedPosition);
  327. }
  328. public void splitEdge(Edge edge, Node node){
  329. AbstractCanvasObject end = edge.getB();
  330. edge.setB(node);
  331. Edge additional = new Edge(node, end, edge.maxCapacity);
  332. control.getModel().addEdgeOnCanvas(additional);
  333. }
  334. @Override
  335. public void mouseEntered(MouseEvent e) {
  336. }
  337. @Override
  338. public void mouseExited(MouseEvent e) {
  339. }
  340. @Override
  341. public void mouseMoved(MouseEvent e) {
  342. }
  343. private enum State {
  344. None, BoxSelection, EdgeCreation, ObjectDragging, Selection
  345. }
  346. }
  347. }