Canvas.java 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  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. private AbstractCanvasObject checkForReplacement;
  30. public Canvas(Control control, GroupNode groupNode) {
  31. this.control = control;
  32. this.groupNode = groupNode;
  33. control.OnGuiSetEnabled.addListener(this::setCanvasEnabled);
  34. control.OnSelectionChanged.addListener(this::repaint);
  35. control.OnCanvasUpdate.addListener(this::repaint);
  36. this.setBackground(Color.WHITE);
  37. this.setPreferredSize(new Dimension(GuiSettings.canvasSize.getX(), GuiSettings.canvasSize.getY()));
  38. this.addMouseListener(canvasMouseListener);
  39. this.addMouseMotionListener(canvasMouseListener);
  40. initPopupMenu();
  41. }
  42. private void initPopupMenu() {
  43. componentPopupMenu.add(deleteMenu);
  44. componentPopupMenu.addSeparator();
  45. componentPopupMenu.add(groupMenu);
  46. componentPopupMenu.add(ungroupMenu);
  47. componentPopupMenu.addSeparator();
  48. componentPopupMenu.add(templateMenu);
  49. deleteMenu.addActionListener(clicked -> {
  50. control.deleteCanvasObjects(GuiSettings.getSelectedObjects());
  51. control.clearSelection();
  52. });
  53. groupMenu.addActionListener(clicked -> {
  54. control.group();
  55. });
  56. ungroupMenu.addActionListener(clicked -> {
  57. control.ungroup();
  58. });
  59. templateMenu.addActionListener(clicked -> {
  60. GuiSettings.getSelectedObjects().stream().findAny().ifPresent(obj -> {
  61. HolonObject hObject = (HolonObject)obj;
  62. CreateTemplatePopUp templateDialog = new CreateTemplatePopUp(hObject, (JFrame) SwingUtilities.getWindowAncestor(this), control);
  63. });
  64. });
  65. }
  66. public static Rectangle getBoundsOfObject(AbstractCanvasObject obj) {
  67. int pictureScale = GuiSettings.getPictureScale();
  68. int pictureScaleDiv2 = GuiSettings.getPictureScaleDiv2();
  69. Vec2i pos = obj.getPosition();
  70. return new Rectangle(pos.getX() - pictureScaleDiv2, pos.getY() - pictureScaleDiv2, pictureScale, pictureScale);
  71. }
  72. private void setCanvasEnabled(boolean state) {
  73. enabled = state;
  74. }
  75. public GroupNode getGroupNode() {
  76. return this.groupNode;
  77. }
  78. public void setGroupNode(GroupNode groupNode) {
  79. this.groupNode = groupNode;
  80. }
  81. @Override
  82. public void paintComponent(java.awt.Graphics g) {
  83. super.paintComponent(g);
  84. Graphics2D g2d = Rendering.initGraphics2D(g);
  85. Rendering.drawSelection(g2d);
  86. paintEdges(g2d);
  87. groupNode.getHolonObjects().forEach(hO -> Rendering.drawHolonObject(g2d, hO));
  88. groupNode.getSwitches().forEach(hS -> Rendering.drawSwitchObject(g2d, hS));
  89. groupNode.getGroupNodes().forEach(groupNode -> Rendering.drawGroupNode(g2d, groupNode));
  90. groupNode.getNodes().forEach(node -> Rendering.drawNode(g2d, node));
  91. switch (canvasMouseListener.state) {
  92. case BoxSelection -> Rendering.drawSelectionBox(g2d, canvasMouseListener.getRectangleOfSelectionBox());
  93. case EdgeCreation -> Rendering.drawNewEdgeLine(g2d, canvasMouseListener.selectedOnPressed.getPosition(), canvasMouseListener.lastPosition);
  94. }
  95. if(canvasMouseListener.canBeReplaced){
  96. Rendering.drawReplacementSymbol(g2d, canvasMouseListener.selectedOnPressed);
  97. }
  98. }
  99. private void paintEdges(Graphics2D g2d) {
  100. control.getModel().getEdgesOnCanvas().forEach(edge -> {
  101. if (edge.getA().getGroupNode().isEmpty() || edge.getB().getGroupNode().isEmpty()) {
  102. return;
  103. }
  104. boolean aInside = edge.getA().getGroupNode().get() == groupNode;
  105. boolean bInside = edge.getB().getGroupNode().get() == groupNode;
  106. //both
  107. if (aInside && bInside) {
  108. Rendering.drawEdge(g2d, edge, edge.getA(), edge.getB());
  109. } else if (aInside) {
  110. SearchObjectIfParentOfGroupNode(edge.getB()).ifPresentOrElse(
  111. alternative -> Rendering.drawEdge(g2d, edge, edge.getA(), alternative),
  112. () -> Rendering.drawExternConnection(g2d, edge.getA()));
  113. } else if (bInside) {
  114. SearchObjectIfParentOfGroupNode(edge.getA()).ifPresentOrElse(
  115. alternative -> Rendering.drawEdge(g2d, edge, alternative, edge.getB()),
  116. () -> Rendering.drawExternConnection(g2d, edge.getB()));
  117. } else {
  118. Optional<AbstractCanvasObject> alternativeA = SearchObjectIfParentOfGroupNode(edge.getA());
  119. Optional<AbstractCanvasObject> alternativeB = SearchObjectIfParentOfGroupNode(edge.getB());
  120. if (alternativeA.isPresent() && alternativeB.isPresent() && !alternativeA.equals(alternativeB)) {
  121. Rendering.drawEdge(g2d, edge, alternativeA.get(), alternativeB.get());
  122. }
  123. }
  124. //none
  125. });
  126. }
  127. private Optional<AbstractCanvasObject> SearchObjectIfParentOfGroupNode(AbstractCanvasObject current) {
  128. while (current.getGroupNode().isPresent()) {
  129. if (current.getGroupNode().get() == this.groupNode) {
  130. return Optional.of(current);
  131. }
  132. ;
  133. current = current.getGroupNode().get();
  134. }
  135. return Optional.empty();
  136. }
  137. private Optional<AbstractCanvasObject> getObjectAtPosition(Vec2i pos) {
  138. return groupNode.getObjectsInThisLayer().filter(obj ->
  139. getBoundsOfObject(obj).contains(pos.getX(), pos.getY())
  140. ).findAny();
  141. }
  142. /**
  143. * Microsoft Windows10 selection & dragging behavior
  144. */
  145. private class CanvasMouseListener implements MouseListener, MouseMotionListener {
  146. private Vec2i lastPosition = new Vec2i();
  147. private Vec2i pressedPosition = new Vec2i();
  148. private Set<AbstractCanvasObject> selectionBeforeBoxSelection = new HashSet<>();
  149. private State state = State.None;
  150. private AbstractCanvasObject selectedOnPressed = null;
  151. private boolean canBeReplaced = false;
  152. @Override
  153. public void mousePressed(MouseEvent e) {
  154. if (!enabled) {
  155. return;
  156. }
  157. log.finest(state.toString());
  158. if (!e.isControlDown()) {
  159. GuiSettings.getSelectedObjects().clear();
  160. }
  161. Vec2i pos = new Vec2i(e.getPoint());
  162. getObjectAtPosition(pos).ifPresentOrElse(obj -> {
  163. if (!GuiSettings.getSelectedObjects().contains(obj)) {
  164. state = State.Selection;
  165. GuiSettings.getSelectedObjects().add(obj);
  166. }
  167. selectedOnPressed = obj;
  168. }, () -> {
  169. state = State.BoxSelection;
  170. selectionBeforeBoxSelection = Set.copyOf(GuiSettings.getSelectedObjects());
  171. });
  172. control.OnSelectionChanged.broadcast();
  173. lastPosition = pressedPosition = pos;
  174. }
  175. @Override
  176. public void mouseDragged(MouseEvent e) {
  177. if (!enabled) {
  178. return;
  179. }
  180. log.finest(state.toString());
  181. Vec2i actualPos = new Vec2i(e.getPoint());
  182. switch (state) {
  183. case Selection -> {
  184. // Not handle to small mouse dragging
  185. if (!(pressedPosition.getSquaredDistance(actualPos) > GuiSettings.dragThresholdDistance)) {
  186. return;
  187. }
  188. if (SwingUtilities.isLeftMouseButton(e)) {
  189. state = State.ObjectDragging;
  190. } else if (SwingUtilities.isRightMouseButton(e) && !(selectedOnPressed instanceof GroupNode)) {
  191. state = State.EdgeCreation;
  192. }
  193. }
  194. case BoxSelection -> {
  195. Rectangle selectionBox = getRectangleOfSelectionBox();
  196. groupNode.getObjectsInThisLayer().forEach(obj -> {
  197. Rectangle bounds = getBoundsOfObject(obj);
  198. if (selectionBox.intersects(bounds) ^ selectionBeforeBoxSelection.contains(obj)) {
  199. GuiSettings.getSelectedObjects().add(obj);
  200. } else {
  201. GuiSettings.getSelectedObjects().remove(obj);
  202. }
  203. });
  204. repaint();
  205. }
  206. case ObjectDragging -> {
  207. Vec2i delta = actualPos.subtract(lastPosition);
  208. GuiSettings.getSelectedObjects().forEach(obj -> obj.setPosition(boundsToCanvas(obj.getPosition().add(delta))));
  209. canBeReplaced = checkForReplacement(actualPos).isPresent();
  210. repaint();
  211. }
  212. case EdgeCreation -> repaint();
  213. }
  214. lastPosition = actualPos;
  215. }
  216. private Optional<AbstractCanvasObject> checkForReplacement(Vec2i pos){
  217. return groupNode.getObjectsInThisLayer().filter(obj -> obj != selectedOnPressed &&
  218. getBoundsOfObject(obj).contains(pos.getX(), pos.getY())
  219. ).findAny();
  220. }
  221. @Override
  222. public void mouseReleased(MouseEvent e) {
  223. if (!enabled) {
  224. return;
  225. }
  226. log.info(state.toString());
  227. switch (state) {
  228. case None -> {
  229. if(SwingUtilities.isRightMouseButton(e)){
  230. preparePopupMenu();
  231. componentPopupMenu.show(Canvas.this, e.getX(), e.getY());
  232. }else {
  233. if (GuiSettings.getSelectedObjects().contains(selectedOnPressed)) {
  234. control.removeObjectFromSelection(selectedOnPressed);
  235. } else {
  236. control.addObjectToSelection(selectedOnPressed);
  237. }
  238. }
  239. }
  240. case Selection, BoxSelection -> {
  241. control.OnSelectionChanged.broadcast();
  242. if(SwingUtilities.isRightMouseButton(e)){
  243. preparePopupMenu();
  244. componentPopupMenu.show(Canvas.this, e.getX(), e.getY());
  245. }
  246. }
  247. case EdgeCreation -> getObjectAtPosition(lastPosition).ifPresentOrElse(obj -> {
  248. boolean isGroupNode = obj instanceof GroupNode;
  249. if (!isGroupNode) {
  250. control.addEdgeOnCanvasOrRemoveExisting(new Edge(selectedOnPressed, obj, GuiSettings.maxCapacityForNewCreatedEdges));
  251. }
  252. }, () -> {
  253. Node node = new Node("Node");
  254. groupNode.add(node);
  255. Geometry.Circle detectionCircle = new Geometry.Circle(new Vec2f(lastPosition), 15f);
  256. node.setPosition(new Vec2i(boundsToCanvas(lastPosition)));
  257. for(Edge edge : control.getModel().getEdgesOnCanvas()) {
  258. if(edge.getA().getGroupNode().isEmpty() || edge.getB().getGroupNode().isEmpty() ||
  259. edge.getA().getGroupNode().get() != groupNode || edge.getB().getGroupNode().get() != groupNode){
  260. continue;
  261. }
  262. Optional<Vec2f> pos = Geometry.getProjectionOnSegmentIfInRange(new Geometry.Line(new Vec2f(edge.getA().getPosition())
  263. , new Vec2f(edge.getB().getPosition())), detectionCircle);
  264. if(pos.isPresent()){
  265. Vec2f position = pos.get();
  266. node.setPosition(new Vec2i((int)position.getX(), (int)position.getY()));
  267. splitEdge(edge, node);
  268. break;
  269. }
  270. }
  271. control.addEdgeOnCanvas(new Edge(selectedOnPressed, node, GuiSettings.maxCapacityForNewCreatedEdges));
  272. control.calculateStateForCurrentIteration();
  273. });
  274. case ObjectDragging -> {
  275. checkForReplacement(new Vec2i(e.getPoint())).ifPresent(obj -> {
  276. control.replaceCanvasObject(obj, selectedOnPressed);
  277. });
  278. }
  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. private Vec2i boundsToCanvas(Vec2i pos){
  326. Vec2i position = new Vec2i(pos);
  327. position.clampX(GuiSettings.getPictureScaleDiv2(), GuiSettings.canvasSize.getX() - GuiSettings.getPictureScaleDiv2());
  328. position.clampY(GuiSettings.getPictureScaleDiv2(), GuiSettings.canvasSize.getY() - GuiSettings.getPictureScaleDiv2());
  329. return position;
  330. }
  331. Rectangle getRectangleOfSelectionBox() {
  332. return Geometry.createRectangleFromCorners(lastPosition, pressedPosition);
  333. }
  334. public void splitEdge(Edge edge, Node node){
  335. AbstractCanvasObject end = edge.getB();
  336. edge.setB(node);
  337. Edge additional = new Edge(node, end, edge.maxCapacity);
  338. control.getModel().addEdgeOnCanvas(additional);
  339. }
  340. @Override
  341. public void mouseEntered(MouseEvent e) {
  342. }
  343. @Override
  344. public void mouseExited(MouseEvent e) {
  345. }
  346. @Override
  347. public void mouseMoved(MouseEvent e) {
  348. }
  349. private enum State {
  350. None, BoxSelection, EdgeCreation, ObjectDragging, Selection
  351. }
  352. }
  353. }