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