| FadingGroupStateIconDemo.java |
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.hierarchy;
15
16 import org.w3c.dom.Document;
17 import y.anim.AnimationObject;
18 import y.anim.AnimationPlayer;
19 import y.base.Node;
20 import y.base.NodeCursor;
21 import y.io.GraphMLIOHandler;
22 import y.view.GenericNodeRealizer;
23 import y.view.Graph2D;
24 import y.view.Graph2DTraversal;
25 import y.view.Graph2DViewRepaintManager;
26 import y.view.HitInfo;
27 import y.view.NodeRealizer;
28 import y.view.ProxyShapeNodeRealizer;
29 import y.view.ViewMode;
30 import y.view.hierarchy.DefaultHierarchyGraphFactory;
31 import y.view.hierarchy.GroupNodePainter;
32
33 import java.awt.EventQueue;
34 import java.beans.PropertyChangeEvent;
35 import java.beans.PropertyChangeListener;
36 import java.io.IOException;
37 import java.io.InputStream;
38 import java.util.Locale;
39
40 /**
41 * Demonstrates how to use a custom {@link y.view.ViewMode} in order to fade in the group state icon when the mouse is
42 * placed over a group/folder node and fade it out when the mouse leaves a group/folder node.
43 * <p/>
44 * This <code>ViewMode</code> determines whether the mouse moves over a group/folder node and then starts an animation
45 * that changes a style property of the group node realizer which contains the opacity value for the icon.
46 */
47 public class FadingGroupStateIconDemo extends GroupingDemo {
48
49 /**
50 * Overwritten to register a custom view mode that shows/hides the group state icon.
51 */
52 protected void registerViewModes() {
53 super.registerViewModes();
54 view.addViewMode(new FadingIconViewMode());
55 }
56
57 /**
58 * Handles the deserialization of the {@link GroupNodePainter#GROUP_STATE_STYLE_ID} by setting
59 * the opacity to zero for all nodes when loading the graphML file.
60 * @return the <code>GraphMLIOHandler</code>
61 */
62 protected GraphMLIOHandler createGraphMLIOHandler() {
63 return new GraphMLIOHandler(){
64 public void read(Graph2D graph, InputStream in) throws IOException {
65 super.read(graph, in);
66 setTransparentGroupState(graph);
67 }
68
69 public void read(Graph2D graph, Document documentElement) throws IOException {
70 super.read(graph, documentElement);
71 setTransparentGroupState(graph);
72 }
73
74 private void setTransparentGroupState(Graph2D graph) {
75 for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
76 setOpacity(graph.getRealizer(nc.node()), 0);
77 }
78 }
79 };
80 }
81
82 /**
83 * Configures the default group node realizers with an opacity value of 0.
84 */
85 protected void configureDefaultGroupNodeRealizers() {
86 // If the opacity value would be set for the default group node realizer,
87 // all added group nodes would share the same style property instance.
88 // As the default group node realizer here does not have the style property, a
89 // new style property is set for every added node.
90 getHierarchyManager().setGraphFactory(new DefaultHierarchyGraphFactory() {
91 public NodeRealizer createNodeRealizer(Object hint) {
92 final NodeRealizer nr = super.createNodeRealizer(hint);
93 setOpacity(nr, 0);
94 return nr;
95 }
96 });
97 super.configureDefaultGroupNodeRealizers();
98 }
99
100 /**
101 * Sets the opacity of the {@link GroupNodePainter#GROUP_STATE_STYLE_ID} and creates a new style property when
102 * needed. If the realizer is a <code>ProxyShapeNodeRealizer</code> the opacity is set for all its delegates.
103 * @param realizer the realizer the property is set for
104 * @param opacity the opacity
105 */
106 private static void setOpacity(NodeRealizer realizer, float opacity) {
107 if (realizer instanceof ProxyShapeNodeRealizer) {
108 // If the realizer is a proxy the style property is set recursively for all delegates.
109 final ProxyShapeNodeRealizer proxyRealizer = (ProxyShapeNodeRealizer) realizer;
110 for (int i = 0; i < proxyRealizer.realizerCount(); i++) {
111 setOpacity(proxyRealizer.getRealizer(i), opacity);
112 }
113 } else {
114 // Set or create the FadingIconValue
115 GroupNodePainter.GroupStateStyle style = (GroupNodePainter.GroupStateStyle) ((GenericNodeRealizer) realizer).getStyleProperty(
116 GroupNodePainter.GROUP_STATE_STYLE_ID);
117 if (style == null) {
118 style = new GroupNodePainter.GroupStateStyle();
119 ((GenericNodeRealizer) realizer).setStyleProperty(GroupNodePainter.GROUP_STATE_STYLE_ID, style);
120 }
121 style.setOpacity(opacity);
122 }
123 }
124
125 /**
126 * Launches the <code>CustomGroupViewModeDemo</code>.
127 * @param args not used
128 */
129 public static void main(String[] args) {
130 EventQueue.invokeLater(new Runnable() {
131 public void run() {
132 Locale.setDefault(Locale.ENGLISH);
133 initLnF();
134 (new FadingGroupStateIconDemo()).start();
135 }
136 });
137 }
138
139 /**
140 * A custom {@link y.view.ViewMode} which fades in the group state icon when the mouse moves over a group/folder node
141 * and fades out the group state icon when the mouse when the mouse leaves the group/folder node.
142 */
143 public class FadingIconViewMode extends ViewMode {
144
145 /** Player to animate the fading. */
146 private AnimationPlayer player;
147
148 /** Last seen node to be able to unset its property when leaving. */
149 private Node lastSeenNode;
150
151 /** Repaint manager that executes repaints during animations. */
152 private Graph2DViewRepaintManager repaintManager;
153
154 /** Last fade in animation to be able to abort the fading */
155 private FadingIconAnimation lastFadeInAnimation;
156
157 /**
158 * Creates a <code>FadingIconViewMode</code>.
159 */
160 public FadingIconViewMode() {
161 player = new AnimationPlayer(false);
162
163 // As view is not set at creation time listening to the appropriate property change events
164 // allows us to create a repaint manager as soon as the necessary view instance is available.
165 addPropertyChangeListener(new PropertyChangeListener() {
166 public void propertyChange(PropertyChangeEvent e) {
167 if (ACTIVE_VIEW_PROPERTY.equals(e.getPropertyName())) {
168 if (repaintManager != null) {
169 player.removeAnimationListener(repaintManager);
170 }
171 repaintManager = new Graph2DViewRepaintManager(view);
172 player.addAnimationListener(repaintManager);
173 }
174 }
175 });
176 }
177
178 /**
179 * Determines if the mouse moves over a group or folder node and starts the animation to fade
180 * in/out the group state icon.
181 */
182 public void mouseMoved(double x, double y) {
183 final Graph2D graph = view.getGraph2D();
184 if (graph.getHierarchyManager() != null) {
185 final HitInfo hitInfo =
186 view.getHitInfoFactory().createHitInfo(x, y, Graph2DTraversal.NODES, true);
187
188 // determine whether the mouse moves over a group/folder node
189 Node n = null;
190 if (hitInfo.hasHitNodes()) {
191 n = hitInfo.getHitNode();
192 if (graph.getHierarchyManager().isNormalNode(n)) {
193 if (graph.getHierarchyManager().getParentNode(n) == lastSeenNode) {
194 n = lastSeenNode;
195 } else {
196 n = null;
197 }
198 }
199 }
200
201 //fade out
202 if (lastSeenNode != n && lastSeenNode != null && lastSeenNode.getGraph() != null) {
203 if (lastFadeInAnimation != null) {
204 lastFadeInAnimation.setActive(false);
205 }
206
207 final NodeRealizer lastSeenNodeRealizer = ((Graph2D) lastSeenNode.getGraph()).getRealizer(lastSeenNode);
208 player.animate(new FadingIconAnimation(lastSeenNodeRealizer, false));
209
210 lastSeenNode = null;
211 }
212
213 //fade in
214 if (lastSeenNode != n && n != null) {
215 final NodeRealizer hitNodeRealizer = ((Graph2D) n.getGraph()).getRealizer(n);
216
217 final FadingIconAnimation animation = new FadingIconAnimation(hitNodeRealizer, true);
218 player.animate(animation);
219
220 lastFadeInAnimation = animation;
221 lastSeenNode = n;
222 }
223 }
224 }
225
226 /**
227 * An {@link AnimationObject} that animates fading of the group state icon. It sets the transparency value of
228 * the icon using a style property.
229 */
230 private final class FadingIconAnimation implements AnimationObject {
231
232 /** Maximum duration of the fading animation */
233 private static final int FADE_DURATION = 500;
234
235 /** Start value of the {@link GroupNodePainter#GROUP_STATE_STYLE_ID} value. */
236 private float startValue;
237
238 /** Realizer of the group/folder node whose state icon fades. */
239 private NodeRealizer realizer;
240
241 /** Determines if it is a fade in or a fade out animation */
242 private boolean fadeIn;
243
244 /** Determines if the animation is still active */
245 private boolean active;
246
247 /**
248 * Creates a <code>FadingIconAnimation</code> for a given realizer.
249 * @param realizer the realizer
250 * @param fadeIn <code>true</code> if it is a fade in animation, <code>false</code> otherwise.
251 */
252 public FadingIconAnimation(NodeRealizer realizer, boolean fadeIn) {
253 this.realizer = realizer;
254 this.fadeIn = fadeIn;
255 this.active = true;
256 }
257
258 /**
259 * Initializes fading either with transparency zero (fade in) or with the last transparency value (fade out).
260 */
261 public void initAnimation() {
262 if (repaintManager != null) {
263 repaintManager.add(realizer);
264 }
265
266 if (fadeIn) {
267 // Set a new fading icon property with start value 0 = fully transparent
268 setOpacity(realizer, 0);
269 } else {
270 // Start at the transparency value where the last fade in animation ended
271 startValue = getOpacity(realizer);
272 }
273 }
274
275 /**
276 * Calculates the transparency value for the group state icon.
277 * @param time a point in [0.0, 1.0]
278 */
279 public void calcFrame(double time) {
280 if (active) {
281 if (fadeIn) {
282 setOpacity(realizer, (float) time);
283 } else {
284 setOpacity(realizer, startValue * (1 - (float) time));
285 }
286 }
287 }
288
289 public void disposeAnimation() {
290 if (repaintManager != null) {
291 repaintManager.remove(realizer);
292 }
293 }
294
295 /**
296 * Returns the preferred duration of the animation which is shorter if the fade in animation was aborted early.
297 * @return the preferred duration of the animation
298 */
299 public long preferredDuration() {
300 if (fadeIn) {
301 return FADE_DURATION;
302 } else {
303 if (startValue > 0) {
304 // if the fade in animation was aborted, shorten the fade out animation
305 return (long) (FADE_DURATION * startValue);
306 } else {
307 return 0;
308 }
309 }
310 }
311
312 /**
313 * Specifies whether the animation is active or not. If the animation is not active, {@link #calcFrame(double)}
314 * will do nothing.
315 * @param active the state of this animation
316 */
317 public void setActive(boolean active) {
318 this.active = active;
319 }
320
321 /**
322 * Retrieves the value of the {@link GroupNodePainter#GROUP_STATE_STYLE_ID} for the given realizer.
323 * @param realizer the realizer
324 * @return the current value of the style property
325 */
326 private float getOpacity(NodeRealizer realizer) {
327 NodeRealizer r = getDelegateRealizer(realizer);
328 return ((GroupNodePainter.GroupStateStyle) ((GenericNodeRealizer) r).getStyleProperty(
329 GroupNodePainter.GROUP_STATE_STYLE_ID)).getOpacity();
330 }
331
332 /**
333 * Gets the current delegate realizer of the <code>ProxyShapeNodeRealizer</code> of the node or the node's realizer
334 * if no proxy is used.
335 * @param realizer the <code>NodeRealizer</code> that could be a <code>ProxyShapeNodeRealizer</code>
336 * @return the delegate realizer or the given realizer itself
337 */
338 private GenericNodeRealizer getDelegateRealizer(NodeRealizer realizer) {
339 if (realizer instanceof ProxyShapeNodeRealizer) {
340 return (GenericNodeRealizer) ((ProxyShapeNodeRealizer) realizer).getRealizerDelegate();
341 } else {
342 return (GenericNodeRealizer) realizer;
343 }
344 }
345 }
346 }
347 }