1   /****************************************************************************
2    **
3    ** This file is part of yFiles-2.9. 
4    ** 
5    ** yWorks proprietary/confidential. Use is subject to license terms.
6    **
7    ** Redistribution of this file or of an unauthorized byte-code version
8    ** of this file is strictly forbidden.
9    **
10   ** Copyright (c) 2000-2011 by yWorks GmbH, Vor dem Kreuzberg 28, 
11   ** 72070 Tuebingen, Germany. All rights reserved.
12   **
13   ***************************************************************************/
14  package demo.view.viewmode;
15  
16  import demo.view.DemoBase;
17  import demo.view.DemoDefaults;
18  
19  import y.anim.AnimationFactory;
20  import y.anim.AnimationObject;
21  import y.anim.AnimationPlayer;
22  import y.base.Node;
23  import y.base.NodeMap;
24  import y.view.EditMode;
25  import y.view.Graph2DViewActions;
26  import y.view.Graph2DViewRepaintManager;
27  import y.view.HitInfo;
28  import y.view.NodeRealizer;
29  import y.view.ShapeNodeRealizer;
30  import y.view.ViewAnimationFactory;
31  import y.view.ViewMode;
32  import y.view.AutoDragViewMode;
33  import y.view.DefaultGraph2DRenderer;
34  import y.util.DefaultMutableValue2D;
35  import y.util.Value2D;
36  import y.view.YRenderingHints;
37  
38  import javax.swing.ActionMap;
39  import javax.swing.InputMap;
40  import javax.swing.JComponent;
41  import java.awt.Dimension;
42  import java.awt.EventQueue;
43  import java.util.Locale;
44  
45  
46  /**
47   * Demonstrates how to create a custom <code>ViewMode</code> that uses yFiles'
48   * Animation Framework to produce a roll over effect for nodes under the mouse
49   * cursor.
50   *
51   */
52  public class RollOverEffectDemo extends DemoBase {
53  
54    public RollOverEffectDemo() {
55      final DefaultGraph2DRenderer g2dr = new DefaultGraph2DRenderer();
56      g2dr.setDrawEdgesFirst(true);
57      view.setGraph2DRenderer(g2dr);
58      view.setPreferredSize(new Dimension(800, 600));
59      view.getRenderingHints().put(ShapeNodeRealizer.KEY_SLOPPY_RECT_PAINTING,
60          ShapeNodeRealizer.VALUE_SLOPPY_RECT_PAINTING_OFF);
61      view.getRenderingHints().put(YRenderingHints.KEY_SLOPPY_POLYLINE_PAINTING,
62          YRenderingHints.VALUE_SLOPPY_POLYLINE_PAINTING_OFF);
63      loadInitialGraph();
64    }
65  
66    protected void configureDefaultRealizers() {
67      // painting shadows is expensive and therefore not well suited for animations
68      DemoDefaults.registerDefaultNodeConfiguration(false);
69      DemoDefaults.configureDefaultRealizers(view);
70    }
71  
72    /**
73     * Overwritten to register a roll over effect producing view mode.
74     */
75    protected void registerViewModes() {
76      final EditMode editMode = createEditMode();
77      if (editMode != null) {
78        view.addViewMode(editMode);
79      }
80      view.addViewMode(new AutoDragViewMode());
81      view.addViewMode(new RollOverViewMode());
82  
83      // disable label editing shortcut
84      final Graph2DViewActions actions = new Graph2DViewActions(view);
85      ActionMap amap = view.getCanvasComponent().getActionMap();
86      amap.remove(Graph2DViewActions.EDIT_LABEL);
87      InputMap imap = actions.createDefaultInputMap(amap);
88      view.getCanvasComponent().setInputMap(JComponent.WHEN_FOCUSED, imap);
89    }
90  
91    /**
92     * Loads a sample graph.
93     */
94    protected void loadInitialGraph() {
95      loadGraph("resource/rollover.graphml");
96    }
97  
98  
99    public static void main(String[] args) {
100     EventQueue.invokeLater(new Runnable() {
101       public void run() {
102         Locale.setDefault(Locale.ENGLISH);
103         initLnF();
104         (new RollOverEffectDemo()).start();
105       }
106     });
107   }
108 
109   /**
110    * A <code>ViewMode</code> that produces a roll over effect for nodes
111    * under the mouse cursor.
112    */
113   private static final class RollOverViewMode extends ViewMode {
114     /** Animation state constant */
115     private static final int NONE = 0;
116     /** Animation state constant */
117     private static final int MARKED = 1;
118     /** Animation state constant */
119     private static final int UNMARK = 2;
120 
121 
122     /** Preferred duration for roll over effect animations */
123     private static final int PREFERRED_DURATION = 350;
124 
125     /** Scale factor for the roll over effect animations */
126     private static final Value2D SCALE_FACTOR =
127             DefaultMutableValue2D.create(3, 3);
128 
129 
130     /** Stores the last node that was marked with the roll over effect */
131     private Node lastHitNode;
132     /** Stores the original size of nodes */
133     private NodeMap size;
134     /** Stores the animation state of nodes */
135     private NodeMap state;
136 
137     private ViewAnimationFactory factory;
138     private AnimationPlayer player;
139 
140     /**
141      * Triggers a rollover effect for the first node at the specified location.
142      */
143     public void mouseMoved( final double x, final double y ) {
144       final HitInfo hi = getHitInfo(x, y);
145       if (hi.hasHitNodes()) {
146         final Node node = (Node) hi.hitNodes().current();
147         if (node != lastHitNode) {
148           unmark(lastHitNode);
149         }
150         if (state.getInt(node) == NONE) {
151           mark(node);
152           lastHitNode = node;
153         }
154       } else {
155         unmark(lastHitNode);
156         lastHitNode = null;
157       }
158     }
159 
160     /**
161      * Overwritten to initialize/dispose this <code>ViewMode</code>'s
162      * helper data.
163      */
164     public void activate( final boolean b ) {
165       if (b) {
166         factory = new ViewAnimationFactory(new Graph2DViewRepaintManager(view));
167         player = factory.createConfiguredPlayer();
168         size = view.getGraph2D().createNodeMap();
169         state = view.getGraph2D().createNodeMap();
170       } else {
171         view.getGraph2D().disposeNodeMap(state);
172         view.getGraph2D().disposeNodeMap(size);
173         state = null;
174         size = null;
175         player = null;
176         factory = null;
177       }
178       super.activate(b);
179     }
180 
181     /**
182      * Overwritten to take only nodes into account for hit testing.
183      */
184     protected HitInfo getHitInfo( final double x, final double y ) {
185       final HitInfo hi = new HitInfo(view, x, y, true, HitInfo.NODE);
186       setLastHitInfo(hi);
187       return hi;
188     }
189 
190     /**
191      * Triggers a <em>mark</em> animation for the specified node.
192      * Sets the animation state of the given node to <em>MARKED</em>.
193      */
194     protected void mark( final Node node ) {
195       // only start a mark animation if no other animation is playing
196       // for the given node
197       if (state.getInt(node) == NONE) {
198         state.setInt(node, MARKED);
199 
200         final NodeRealizer nr = getGraph2D().getRealizer(node);
201         size.set(node, DefaultMutableValue2D.create(nr.getWidth(), nr.getHeight()));
202         final AnimationObject ao = factory.scale(
203                 nr,
204                 SCALE_FACTOR,
205                 ViewAnimationFactory.APPLY_EFFECT,
206                 PREFERRED_DURATION);
207         player.animate(AnimationFactory.createEasedAnimation(ao));
208       }
209     }
210 
211     /**
212      * Triggers an <em>unmark</em> animation for the specified node.
213      * Sets the animation state of the given node to <em>UNMARKED</em>.
214      */
215     protected void unmark( final Node node ) {
216       if (node == null) {
217         return;
218       }
219 
220       // only start an unmark animation if the node is currently marked
221       // (or in the process of being marked)
222       if (state.getInt(node) == MARKED) {
223         state.setInt(node, UNMARK);
224 
225         final Value2D oldSize = (Value2D) size.get(node);
226         final NodeRealizer nr = getGraph2D().getRealizer(node);
227         final AnimationObject ao = factory.resize(
228                 nr,
229                 oldSize,
230                 ViewAnimationFactory.APPLY_EFFECT,
231                 PREFERRED_DURATION);
232         final AnimationObject eao = AnimationFactory.createEasedAnimation(ao);
233         player.animate(new Reset(eao, node, nr, oldSize));
234       }
235     }
236 
237     /**
238      * Custom animation object that resets node size and state upon disposal.
239      */
240     private final class Reset implements AnimationObject {
241       private AnimationObject ao;
242       private final Node node;
243       private final NodeRealizer nr;
244       private final Value2D oldSize;
245 
246       Reset(
247               final AnimationObject ao,
248               final Node node,
249               final NodeRealizer nr,
250               final Value2D size
251       ) {
252         this.ao = ao;
253         this.node = node;
254         this.nr = nr;
255         this.oldSize = size;
256       }
257 
258       public void initAnimation() {
259         ao.initAnimation();
260       }
261 
262       public void calcFrame( final double time ) {
263         ao.calcFrame(time);
264       }
265 
266       /**
267        * Resets the target node to its original size and its animation state
268        * to <em>NONE</em>.
269        */
270       public void disposeAnimation() {
271         ao.disposeAnimation();
272         nr.setSize(oldSize.getX(), oldSize.getY());
273         size.set(node, null);
274         state.setInt(node, NONE);
275       }
276 
277       public long preferredDuration() {
278         return ao.preferredDuration();
279       }
280     }
281   }
282 }
283