1
28 package demo.layout;
29
30 import demo.view.DemoBase;
31 import y.anim.AnimationFactory;
32 import y.anim.AnimationObject;
33 import y.anim.AnimationPlayer;
34 import y.anim.CompositeAnimationObject;
35 import y.base.Edge;
36 import y.base.Node;
37 import y.layout.AbstractLayoutStage;
38 import y.layout.BufferedLayouter;
39 import y.layout.CanonicMultiStageLayouter;
40 import y.layout.ComponentLayouter;
41 import y.layout.FixNodeLayoutStage;
42 import y.layout.GraphLayout;
43 import y.layout.LayoutGraph;
44 import y.layout.LayoutStage;
45 import y.layout.Layouter;
46 import y.layout.OrientationLayouter;
47 import y.layout.PartitionLayouter;
48 import y.layout.PortConstraint;
49 import y.layout.PortConstraintKeys;
50 import y.layout.circular.CircularLayouter;
51 import y.layout.hierarchic.IncrementalHierarchicLayouter;
52 import y.layout.hierarchic.incremental.RoutingStyle;
53 import y.layout.hierarchic.incremental.SimplexNodePlacer;
54 import y.layout.organic.SmartOrganicLayouter;
55 import y.layout.orthogonal.CompactOrthogonalLayouter;
56 import y.layout.orthogonal.OrthogonalLayouter;
57 import y.layout.radial.RadialLayouter;
58 import y.layout.router.OrganicEdgeRouter;
59 import y.layout.router.polyline.EdgeRouter;
60 import y.layout.seriesparallel.SeriesParallelLayouter;
61 import y.layout.tree.BalloonLayouter;
62 import y.layout.tree.DendrogramPlacer;
63 import y.layout.tree.GenericTreeLayouter;
64 import y.layout.tree.TreeLayouter;
65 import y.layout.tree.TreeReductionStage;
66 import y.util.DataProviderAdapter;
67 import y.view.CreateEdgeMode;
68 import y.view.Drawable;
69 import y.view.EditMode;
70 import y.view.Graph2D;
71 import y.view.Graph2DLayoutExecutor;
72 import y.view.Graph2DView;
73 import y.view.Graph2DViewMouseWheelZoomListener;
74 import y.view.GraphicsContext;
75 import y.view.LayoutMorpher;
76 import y.view.ShapeNodeRealizer;
77 import y.view.ViewAnimationFactory;
78 import y.view.ViewMode;
79 import y.view.YRenderingHints;
80
81 import javax.swing.JMenuBar;
82 import javax.swing.JToolBar;
83 import javax.swing.Timer;
84 import java.awt.AlphaComposite;
85 import java.awt.BasicStroke;
86 import java.awt.Color;
87 import java.awt.Composite;
88 import java.awt.Dimension;
89 import java.awt.EventQueue;
90 import java.awt.Font;
91 import java.awt.FontMetrics;
92 import java.awt.Graphics2D;
93 import java.awt.Insets;
94 import java.awt.Rectangle;
95 import java.awt.Stroke;
96 import java.awt.event.ActionEvent;
97 import java.awt.event.ActionListener;
98 import java.awt.font.FontRenderContext;
99 import java.awt.font.TextLayout;
100 import java.awt.geom.AffineTransform;
101 import java.awt.geom.GeneralPath;
102 import java.awt.geom.Rectangle2D;
103 import java.util.Locale;
104
105
120 public class LayoutDemo extends DemoBase {
121 private static final int DURATION_CYCLE = 6000;
122 private static final int DURATION_LAYOUT_CHANGE = DURATION_CYCLE / 8;
123 private static final int DURATION_INITIAL_DELAY = 2000;
124 static final int CONFIG_INCREMENTAL_HIERARCHIC_LAYOUT = 0;
125 static final int CONFIG_INCREMENTAL_HIERARCHIC_LAYOUT_LEFT_TO_RIGHT = 1;
126 static final int CONFIG_INCREMENTAL_HIERARCHIC_LAYOUT_EDGE_GROUPING = 2;
127 static final int CONFIG_SMART_ORGANIC_LAYOUT = 0;
128 static final int CONFIG_SMART_ORGANIC_LAYOUT_CLUSTER = 1;
129 static final int CONFIG_ORTHOGONAL_LAYOUT = 0;
130 static final int CONFIG_ORTHOGONAL_LAYOUT_FACE_MAXIMIZATION = 1;
131 static final int CONFIG_ORTHOGONAL_LAYOUT_COMPACT = 2;
132 static final int CONFIG_TREE_LAYOUT = 0;
133 static final int CONFIG_BALLOON_LAYOUT = 1;
134 static final int CONFIG_CIRCULAR_LAYOUT = 0;
135 static final int CONFIG_CIRCULAR_LAYOUT_ONE_CIRCLE = 1;
136
137 private final EditMode editMode;
138 private final Timer timer;
139 private final AnimationPlayer player;
140 private JMenuBar menuBar;
141 private JToolBar toolBar;
142 private boolean isVideoMode;
143 private final PlayButton playButton;
144 private final RelayoutButton relayoutButton;
145 private BufferedLayouter layouter;
146 private Graph2DViewMouseWheelZoomListener wheelZoomListener;
147
148 public LayoutDemo() {
149 this(null);
150 }
151
152 public LayoutDemo(final String helpFile) {
153 addHelpPane(helpFile);
154 view.setPreferredSize(new Dimension(1200, 900));
155
156 wheelZoomListener = new Graph2DViewMouseWheelZoomListener();
157 wheelZoomListener.setCenterZooming(false);
158
159 editMode = createEditMode();
160 isVideoMode = true;
161
162 final Layouter[] layouts = {
164 createHierarchicLayouter(CONFIG_INCREMENTAL_HIERARCHIC_LAYOUT),
165 createSmartOrganicLayouter(CONFIG_SMART_ORGANIC_LAYOUT),
166 createOrthogonalLayouter(CONFIG_ORTHOGONAL_LAYOUT),
167 createTreeLayouter(CONFIG_TREE_LAYOUT),
168 createCircularLayouter(CONFIG_CIRCULAR_LAYOUT),
169 createHierarchicLayouter(CONFIG_INCREMENTAL_HIERARCHIC_LAYOUT_LEFT_TO_RIGHT),
170 createSmartOrganicLayouter(CONFIG_SMART_ORGANIC_LAYOUT_CLUSTER),
171 createOrthogonalLayouter(CONFIG_ORTHOGONAL_LAYOUT_FACE_MAXIMIZATION),
172 createTreeLayouter(CONFIG_BALLOON_LAYOUT),
173 createHierarchicLayouter(CONFIG_INCREMENTAL_HIERARCHIC_LAYOUT_EDGE_GROUPING),
174 createCircularLayouter(CONFIG_CIRCULAR_LAYOUT_ONE_CIRCLE),
175 createDendrogramLayouter(),
176 createOrthogonalLayouter(CONFIG_ORTHOGONAL_LAYOUT_COMPACT),
177 createRadialLayouter(),
178 createSeriesParallelLayouter()
179 };
180
181 final String[] titles = {
183 "Hierarchic Layout",
184 "Organic Layout",
185 "Orthogonal Layout",
186 "Tree Layout",
187 "Circular Layout",
188 "Hierarchic Layout - Left to Right",
189 "Organic Layout - Clustering",
190 "Orthogonal Layout - Face Maximization",
191 "Balloon Layout",
192 "Hierarchic Layout - Edge Grouping",
193 "Circular Layout - One Circle",
194 "Dendrogram Layout",
195 "Compact Orthogonal Layout",
196 "Radial Layout",
197 "Series-Parallel Layout"
198 };
199
200 loadGraph("resource/layoutdemograph.graphml");
202
203 view.getGraph2D().addDataProvider(FixNodeLayoutStage.FIXED_NODE_DPKEY, new DataProviderAdapter() {
205 public boolean getBool(Object dataHolder) {
206 return dataHolder instanceof Node;
207 }
208 });
209
210 final LayoutTitle title = new LayoutTitle(DURATION_LAYOUT_CHANGE, view);
212 view.addDrawable(title);
213
214 playButton = new PlayButton();
216 view.addDrawable(playButton);
217 relayoutButton = new RelayoutButton();
219
220 player = new ViewAnimationFactory(view).createConfiguredPlayer();
222 player.setBlocking(false);
223
224 timer = new Timer(DURATION_INITIAL_DELAY, new ActionListener() {
226 private int index;
227
228 public void actionPerformed(ActionEvent e) {
229 final CompositeAnimationObject concurrency = AnimationFactory.createConcurrency();
231 title.setNextText(titles[index]);
232 concurrency.addAnimation(title);
233 layouter = new BufferedLayouter(new FixNodeLayoutStage(layouts[index]));
234 final GraphLayout graphLayout = layouter.calcLayout(view.getGraph2D());
235 final LayoutMorpher morpher = new LayoutMorpher(view, graphLayout);
236 morpher.setPreferredDuration(DURATION_LAYOUT_CHANGE);
237 concurrency.addAnimation(morpher);
238
239 player.animate(concurrency);
241
242 index = (index + 1) % layouts.length;
244 }
245 });
246 timer.setDelay(DURATION_CYCLE);
247 timer.start();
248 }
249
250
253 protected EditMode createEditMode() {
254 final EditMode mode = new EditMode() {
255 protected void paperClicked(final Graph2D graph, final double x, final double y, final boolean modifierSet) {
256 if (!playButton.getBounds2D().contains(x, y)
257 && !relayoutButton.getBounds2D().contains(x, y)) {
258 super.paperClicked(graph, x, y, modifierSet);
259 }
260 }
261 };
262
263 if (mode.getCreateEdgeMode() instanceof CreateEdgeMode) {
264 ((CreateEdgeMode) mode.getCreateEdgeMode()).setIndicatingTargetNode(true);
265 }
266 return mode;
267 }
268
269
274 private static Layouter createHierarchicLayouter(int configuration) {
275 final IncrementalHierarchicLayouter layouter = new IncrementalHierarchicLayouter();
276 switch (configuration) {
277 case CONFIG_INCREMENTAL_HIERARCHIC_LAYOUT:
278 ((SimplexNodePlacer) layouter.getNodePlacer()).setBaryCenterModeEnabled(true);
279 layouter.getEdgeLayoutDescriptor().setRoutingStyle(new RoutingStyle(RoutingStyle.EDGE_STYLE_ORTHOGONAL));
280 break;
281 case CONFIG_INCREMENTAL_HIERARCHIC_LAYOUT_LEFT_TO_RIGHT:
282 ((SimplexNodePlacer) layouter.getNodePlacer()).setBaryCenterModeEnabled(true);
283 layouter.getEdgeLayoutDescriptor().setRoutingStyle(new RoutingStyle(RoutingStyle.EDGE_STYLE_OCTILINEAR));
284 layouter.setLayoutOrientation(OrientationLayouter.LEFT_TO_RIGHT);
285 break;
286 case CONFIG_INCREMENTAL_HIERARCHIC_LAYOUT_EDGE_GROUPING:
287 ((SimplexNodePlacer) layouter.getNodePlacer()).setBaryCenterModeEnabled(true);
288 layouter.getEdgeLayoutDescriptor().setRoutingStyle(new RoutingStyle(RoutingStyle.EDGE_STYLE_ORTHOGONAL));
289 layouter.setLayoutOrientation(OrientationLayouter.BOTTOM_TO_TOP);
290 layouter.setAutomaticEdgeGroupingEnabled(true);
291 break;
292 }
293 return layouter;
294 }
295
296
301 private static Layouter createSmartOrganicLayouter(int configuration) {
302 final SmartOrganicLayouter layouter = new SmartOrganicLayouter();
303 switch (configuration) {
304 case CONFIG_SMART_ORGANIC_LAYOUT:
305 layouter.setPreferredEdgeLength(40);
306 layouter.setNodeSizeAware(true);
307 break;
308 case CONFIG_SMART_ORGANIC_LAYOUT_CLUSTER:
309 layouter.setPreferredEdgeLength(60);
310 layouter.setNodeSizeAware(true);
311 layouter.setAutoClusteringEnabled(true);
312 layouter.setAutoClusteringQuality(0.5);
313 layouter.setLayoutOrientation(OrientationLayouter.RIGHT_TO_LEFT);
314 }
315 return layouter;
316 }
317
318
323 private static Layouter createOrthogonalLayouter(int configuration) {
324 final OrthogonalLayouter ol;
325 switch (configuration) {
326 default:
327 case CONFIG_ORTHOGONAL_LAYOUT:
328 ol = new OrthogonalLayouter();
329 ol.setUseRandomization(false);
330 ol.setPerceivedBendsOptimizationEnabled(true);
331 ol.setGrid(10);
332 return ol;
333 case CONFIG_ORTHOGONAL_LAYOUT_FACE_MAXIMIZATION:
334 ol = new OrthogonalLayouter();
335 ol.setUseRandomization(false);
336 ol.setUseFaceMaximization(true);
337 ol.setLayoutOrientation(OrientationLayouter.LEFT_TO_RIGHT);
338 ol.setGrid(10);
339 return ol;
340 case CONFIG_ORTHOGONAL_LAYOUT_COMPACT:
341 final CompactOrthogonalLayouter col = new CompactOrthogonalLayouter();
342 col.setGridSpacing(10);
343 ol = (OrthogonalLayouter) col.getCoreLayouter();
344 ol.setLayoutStyle(OrthogonalLayouter.NORMAL_STYLE);
345 final PartitionLayouter.ComponentPartitionPlacer placer = (PartitionLayouter.ComponentPartitionPlacer) col.getPartitionPlacer();
346 placer.getComponentLayouter().setStyle(ComponentLayouter.STYLE_PACKED_COMPACT_RECTANGLE);
347 return col;
348 }
349 }
350
351
356 private static Layouter createTreeLayouter(int configuration) {
357 final Layouter layouter;
358 switch (configuration) {
359 default:
360 case CONFIG_TREE_LAYOUT:
361 layouter = new TreeLayouter();
362 break;
363 case CONFIG_BALLOON_LAYOUT:
364 layouter = new BalloonLayouter();
365 }
366 final TreeReductionStage treeReductionStage = new TreeReductionStage();
367 treeReductionStage.setNonTreeEdgeRouter(new OrganicEdgeRouter());
368 ((CanonicMultiStageLayouter) layouter).appendStage(treeReductionStage);
369 return layouter;
370 }
371
372
376 private static Layouter createDendrogramLayouter() {
377 final GenericTreeLayouter layouter = new GenericTreeLayouter();
378 layouter.setDefaultNodePlacer(new DendrogramPlacer());
379 EdgeRouter nonTreeEdgeRouter = new EdgeRouter();
380 nonTreeEdgeRouter.setSphereOfAction(EdgeRouter.ROUTE_SELECTED_EDGES);
381 final TreeReductionStage treeReductionStage = new TreeReductionStage();
382 treeReductionStage.setNonTreeEdgeRouter(nonTreeEdgeRouter);
383 treeReductionStage.setNonTreeEdgeSelectionKey(EdgeRouter.SELECTED_EDGES);
384 layouter.appendStage(treeReductionStage);
385
386 LayoutStage stage = new AbstractLayoutStage(new EdgeRouter()) {
390 public boolean canLayout(LayoutGraph graph) {
391 return canLayoutCore(graph);
392 }
393
394 public void doLayout(LayoutGraph graph) {
395
400 graph.addDataProvider(PortConstraintKeys.SOURCE_PORT_CONSTRAINT_KEY, new DataProviderAdapter() {
402 public Object get(Object dataHolder) {
403 return PortConstraint.create(PortConstraint.SOUTH, true);
404 }
405 });
406 graph.addDataProvider(PortConstraintKeys.TARGET_PORT_CONSTRAINT_KEY, new DataProviderAdapter() {
408 public Object get(Object dataHolder) {
409 return PortConstraint.create(PortConstraint.NORTH, true);
410 }
411 });
412 graph.addDataProvider(PortConstraintKeys.SOURCE_GROUPID_KEY, new DataProviderAdapter() {
414 public Object get(Object dataHolder) {
415 return ((Edge) dataHolder).source();
416 }
417 });
418
419 doLayoutCore(graph);
421
422 graph.removeDataProvider(PortConstraintKeys.SOURCE_PORT_CONSTRAINT_KEY);
424 graph.removeDataProvider(PortConstraintKeys.TARGET_PORT_CONSTRAINT_KEY);
425 graph.removeDataProvider(PortConstraintKeys.SOURCE_GROUPID_KEY);
426 }
427 };
428 layouter.appendStage(stage);
429 return layouter;
430 }
431
432
437 private static Layouter createCircularLayouter(int configuration) {
438 final CircularLayouter layouter = new CircularLayouter();
439 switch (configuration) {
440 case CONFIG_CIRCULAR_LAYOUT:
441 break;
442 case CONFIG_CIRCULAR_LAYOUT_ONE_CIRCLE:
443 layouter.setLayoutStyle(CircularLayouter.SINGLE_CYCLE);
444 }
445 return layouter;
446 }
447
448
451 private static Layouter createRadialLayouter() {
452 return new RadialLayouter();
453 }
454
455
458 private static Layouter createSeriesParallelLayouter() {
459 final SeriesParallelLayouter layouter = new SeriesParallelLayouter();
460 layouter.setGeneralGraphHandlingEnabled(true);
461 return layouter;
462 }
463
464
467 protected JMenuBar createMenuBar() {
468 menuBar = super.createMenuBar();
469 menuBar.setVisible(false);
470 return menuBar;
471 }
472
473
476 protected JToolBar createToolBar() {
477 toolBar = super.createToolBar();
478 toolBar.setVisible(false);
479 return toolBar;
480 }
481
482
485 protected void configureDefaultRealizers() {
486 final ShapeNodeRealizer realizer = new ShapeNodeRealizer(ShapeNodeRealizer.ROUND_RECT);
487 realizer.setSize(37.5, 37.5);
488 realizer.setFillColor(new Color(205, 2, 2));
489 realizer.removeLabel(0);
490 view.getGraph2D().setDefaultNodeRealizer(realizer);
491 }
492
493
496 protected void registerViewModes() {
497 view.addViewMode(new ViewMode() {
498
504 public void mouseClicked(final double x, final double y) {
505 if (playButton.getBounds2D().contains(x, y)) {
506 playButton.action();
507 } else if (relayoutButton.isVisible() && relayoutButton.getBounds2D().contains(x, y)) {
508 relayoutButton.action();
509 }
510 }
511
512
519 public void mouseMoved(final double x, final double y) {
520 playButton.setHovered(playButton.getBounds2D().contains(x, y));
521 relayoutButton.setHovered(relayoutButton.getBounds2D().contains(x, y));
522 }
523 });
524 }
525
526
530 protected void registerViewListeners() {
531 }
532
533
536 public static void main(final String[] args) {
537 EventQueue.invokeLater(new Runnable() {
538 public void run() {
539 Locale.setDefault(Locale.ENGLISH);
540 initLnF();
541 (new LayoutDemo("resource/layoutdemohelp.html")).start();
542 }
543 });
544 }
545
546
553 private static class LayoutTitle implements Drawable, AnimationObject {
554 private static final Color COLOR_BACKGROUND = new Color(0, 139, 139);
555 private static final Color COLOR_TEXT = Color.WHITE;
556 private static final Font FONT_TEXT = new Font("Arial", Font.PLAIN, 36);
557 private static final Insets INSETS_TEXT = new Insets(15, 15, 15, 15);
558 private static final int INSET = 10;
559 private static final int ARC = 30;
560
561 private final Graph2DView view;
562 private final long preferredDuration;
563
564 private String text;
565 private String nextText;
566 private float opacity;
567
568 private LayoutTitle(long preferredDuration, Graph2DView view) {
569 this.preferredDuration = preferredDuration;
570 this.view = view;
571 }
572
573 public void paint(Graphics2D graphics) {
574 final Color oldColor = graphics.getColor();
576 final Font oldFont = graphics.getFont();
577 final AffineTransform oldTransform = graphics.getTransform();
578 final Composite oldComposite = graphics.getComposite();
579 try {
580 if (text != null && text.length() > 0) {
581 final GraphicsContext context = YRenderingHints.getGraphicsContext(graphics);
583 graphics.setTransform(context.getViewTransform());
584
585 graphics.setFont(FONT_TEXT);
587 final FontMetrics fontMetrics = graphics.getFontMetrics();
588 final int stringWidth = (int) fontMetrics.getStringBounds(text, graphics).getWidth();
589 final int stringHeight = fontMetrics.getMaxAscent() + fontMetrics.getMaxDescent();
590
591 final int x = INSET;
593 final int y = INSET;
594 final int width = stringWidth + INSETS_TEXT.left + INSETS_TEXT.right;
595 final int height = stringHeight + INSETS_TEXT.top + INSETS_TEXT.bottom;
596
597 graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity));
599 graphics.setColor(COLOR_BACKGROUND);
600 graphics.fillRoundRect(x, y, width, height, ARC, ARC);
601
602 graphics.setComposite(oldComposite);
604 graphics.setColor(COLOR_TEXT);
605 graphics.drawString(text, x + INSETS_TEXT.left, y + INSETS_TEXT.top + fontMetrics.getMaxAscent());
606 }
607 } finally {
608 graphics.setColor(oldColor);
610 graphics.setFont(oldFont);
611 graphics.setTransform(oldTransform);
612 graphics.setComposite(oldComposite);
613 }
614 }
615
616 public Rectangle getBounds() {
617 final TextLayout textLayout =
618 new TextLayout(text, FONT_TEXT, new FontRenderContext(FONT_TEXT.getTransform(), true, true));
619 final double backgroundWidth = textLayout.getBounds().getWidth() + INSETS_TEXT.left + INSETS_TEXT.right;
620 final double backgroundHeight = textLayout.getBounds().getHeight() + INSETS_TEXT.top + INSETS_TEXT.bottom;
621
622 final int minX = (int) Math.floor(view.toWorldCoordX(INSET));
623 final int maxX = (int) Math.ceil(view.toWorldCoordX((int) (INSET + backgroundWidth)));
624 final int minY = (int) Math.floor(view.toWorldCoordY(INSET));
625 final int maxY = (int) Math.ceil(view.toWorldCoordY((int) (INSET + backgroundHeight)));
626 return new Rectangle(minX, minY, maxX - minX, maxY - minY);
627 }
628
629 public void setNextText(String text) {
630 nextText = text;
631 }
632
633 public void initAnimation() {
634 opacity = 0;
635 }
636
637 public void calcFrame(double time) {
638 if (time < 0.5) {
639 if (text != null) {
640 opacity = (float) (1 - 2 *time);
641 }
642 } else if (time > 0.5) {
643 if (!nextText.equals(text)) {
644 text = nextText;
645 }
646 opacity = (float) (2 * time -1);
647 }
648 }
649
650 public void disposeAnimation() {
651 }
652
653 public long preferredDuration() {
654 return preferredDuration;
655 }
656 }
657
658
662 private class PlayButton implements Drawable {
663 private static final int HEIGHT = 70;
664 private static final int WIDTH = 100;
665 private static final int INSET = 10;
666 private static final int ARC = 30;
667
668 private boolean isHovered;
669
670
675 boolean isHovered() {
676 return isHovered;
677 }
678
679
685 void setHovered(final boolean isHovered) {
686 if (this.isHovered != isHovered) {
687 view.updateView();
688 }
689 this.isHovered = isHovered;
690 }
691
692 public Rectangle getBounds() {
693 final Rectangle2D r = getBounds2D();
694 final int minX = (int) Math.floor(r.getX());
695 final int maxX = (int) Math.ceil(r.getMaxX());
696 final int minY = (int) Math.floor(r.getY());
697 final int maxY = (int) Math.ceil(r.getMaxY());
698 return new Rectangle(minX, minY, maxX - minX, maxY - minY);
699 }
700
701
707 public Rectangle2D getBounds2D() {
708 final double minX = view.toWorldCoordX(INSET);
709 final double maxX = view.toWorldCoordX(INSET + WIDTH);
710 final int y = view.getHeight() - INSET;
711 final double minY = view.toWorldCoordY(y - HEIGHT);
712 final double maxY = view.toWorldCoordY(y);
713 return new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY);
714 }
715
716 public void paint(final Graphics2D graphics) {
717 final Color oldColor = graphics.getColor();
718 final AffineTransform oldTransform = graphics.getTransform();
719
720 final GraphicsContext context = YRenderingHints.getGraphicsContext(graphics);
722 graphics.setTransform(context.getViewTransform());
723
724 final int x = INSET;
726 final int y = view.getHeight() - INSET - HEIGHT;
727 final int w = WIDTH;
728 final int h = HEIGHT;
729
730 graphics.setColor(isHovered() ? Color.GRAY.brighter() : Color.GRAY);
732 graphics.fillRoundRect(x, y, WIDTH, HEIGHT, ARC, ARC);
733
734 graphics.setColor(Color.WHITE);
736 if (!isVideoMode) {
737 final GeneralPath icon = new GeneralPath();
738 icon.moveTo(x + w / 3, y + h / 5);
739 icon.lineTo(x + w / 3, y + 4 * h / 5);
740 icon.lineTo(x + 2 * w / 3, y + h / 2);
741 icon.closePath();
742 graphics.fill(icon);
743 } else {
744 graphics.fillRect(x + w / 3, y + h / 5, w / 9, 3 * h / 5);
745 graphics.fillRect(x + 5 * w / 9, y + h / 5, w / 9, 3 * h / 5);
746 }
747
748 graphics.setColor(oldColor);
749 graphics.setTransform(oldTransform);
750 }
751
752
755 void action() {
756 if (isVideoMode) {
757 view.addViewMode(editMode);
758 wheelZoomListener.addToCanvas(view);
759 menuBar.setVisible(true);
760 toolBar.setVisible(true);
761 view.addDrawable(relayoutButton);
762 timer.stop();
763 } else {
764 view.removeViewMode(editMode);
765 wheelZoomListener.removeFromCanvas(view);
766 view.getGraph2D().unselectAll();
767 menuBar.setVisible(false);
768 toolBar.setVisible(false);
769 view.removeDrawable(relayoutButton);
770 timer.start();
771 }
772 isVideoMode = !isVideoMode;
773 }
774 }
775
776
780 private class RelayoutButton implements Drawable {
781 private static final int HEIGHT = 70;
782 private static final int WIDTH = 100;
783 private static final int INSET = 10;
784 private static final int ARC = 30;
785
786 private final BasicStroke strokeArc = new BasicStroke(10);
787 private boolean isHovered;
788
789
794 boolean isHovered() {
795 return isHovered;
796 }
797
798
804 void setHovered(final boolean isHovered) {
805 if (this.isHovered != isHovered) {
806 view.updateView();
807 }
808 this.isHovered = isHovered;
809 }
810
811 public Rectangle getBounds() {
812 final Rectangle2D r = getBounds2D();
813 final int minX = (int) Math.floor(r.getX());
814 final int maxX = (int) Math.ceil(r.getMaxX());
815 final int minY = (int) Math.floor(r.getY());
816 final int maxY = (int) Math.ceil(r.getMaxY());
817 return new Rectangle(minX, minY, maxX - minX, maxY - minY);
818 }
819
820
826 public Rectangle2D getBounds2D() {
827 final double minX = view.toWorldCoordX(2 * INSET + PlayButton.WIDTH);
828 final double maxX = view.toWorldCoordX(2 * INSET + PlayButton.WIDTH + WIDTH);
829 final int y = view.getHeight() - INSET;
830 final double minY = view.toWorldCoordY(y - HEIGHT);
831 final double maxY = view.toWorldCoordY(y);
832 return new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY);
833 }
834
835 public void paint(final Graphics2D graphics) {
836 final Color oldColor = graphics.getColor();
837 final AffineTransform oldTransform = graphics.getTransform();
838 final Stroke oldStroke = graphics.getStroke();
839
840 final GraphicsContext context = YRenderingHints.getGraphicsContext(graphics);
842 graphics.setTransform(context.getViewTransform());
843
844 final int x = 2 * INSET + PlayButton.WIDTH;
846 final int y = view.getHeight() - INSET - HEIGHT;
847 final int w = WIDTH;
848 final int h = HEIGHT;
849
850 graphics.setColor(isHovered() ? Color.GRAY.brighter() : Color.GRAY);
852 graphics.fillRoundRect(x, y, WIDTH, HEIGHT, ARC, ARC);
853
854 graphics.setColor(Color.WHITE);
856 graphics.setStroke(strokeArc);
857 final int iconSize = w / 3;
858 final int iconX = x + (w - iconSize) / 2;
859 final int iconY = y + (h - iconSize) / 2;
860 graphics.drawArc(iconX, iconY, iconSize, iconSize, 135, 270);
861
862 final GeneralPath arrow = new GeneralPath();
863 arrow.moveTo((float) (iconX + iconSize * 0.6), (float) (iconY - iconSize * 0.1));
864 arrow.lineTo((float) (iconX + iconSize), (float) (iconY - iconSize * 0.1));
865 arrow.lineTo((float) (iconX + iconSize * 0.6), (float) (iconY + iconSize * 0.3));
866 arrow.closePath();
867 graphics.fill(arrow);
868
869 graphics.setColor(oldColor);
870 graphics.setTransform(oldTransform);
871 graphics.setStroke(oldStroke);
872 }
873
874 boolean isVisible() {
875 return !isVideoMode;
876 }
877
878
881 void action() {
882 if (layouter != null) {
883 final Graph2DLayoutExecutor executor = new Graph2DLayoutExecutor();
884 final LayoutMorpher morpher = executor.getLayoutMorpher();
885 morpher.setPreferredDuration(DURATION_LAYOUT_CHANGE);
886 morpher.setKeepZoomFactor(true);
887 executor.doLayout(view, layouter);
888 }
889 }
890 }
891 }
892