Skip to content

Method: lambda$dismissCurrentPresentation$1()

1: /*
2: * *********************************************************************************************************************
3: *
4: * blueMarine II: Semantic Media Centre
5: * http://tidalwave.it/projects/bluemarine2
6: *
7: * Copyright (C) 2015 - 2021 by Tidalwave s.a.s. (http://tidalwave.it)
8: *
9: * *********************************************************************************************************************
10: *
11: * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
12: * the License. You may obtain a copy of the License at
13: *
14: * http://www.apache.org/licenses/LICENSE-2.0
15: *
16: * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
17: * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
18: * specific language governing permissions and limitations under the License.
19: *
20: * *********************************************************************************************************************
21: *
22: * git clone https://bitbucket.org/tidalwave/bluemarine2-src
23: * git clone https://github.com/tidalwave-it/bluemarine2-src
24: *
25: * *********************************************************************************************************************
26: */
27: package it.tidalwave.bluemarine2.ui.commons.flowcontroller.impl.javafx;
28:
29: import javax.annotation.Nonnull;
30: import javax.annotation.Nullable;
31: import java.util.Stack;
32: import java.lang.annotation.Annotation;
33: import java.lang.reflect.InvocationTargetException;
34: import java.lang.reflect.Method;
35: import javafx.animation.KeyFrame;
36: import javafx.animation.KeyValue;
37: import javafx.animation.Timeline;
38: import javafx.scene.Node;
39: import javafx.scene.layout.StackPane;
40: import javafx.util.Duration;
41: import javafx.application.Platform;
42: import it.tidalwave.bluemarine2.ui.commons.OnActivate;
43: import it.tidalwave.bluemarine2.ui.commons.OnDeactivate;
44: import it.tidalwave.bluemarine2.ui.commons.flowcontroller.FlowController;
45: import lombok.Getter;
46: import lombok.RequiredArgsConstructor;
47: import lombok.Setter;
48: import lombok.ToString;
49: import lombok.extern.slf4j.Slf4j;
50:
51: /***********************************************************************************************************************
52: *
53: * A JavaFX implementation of {@link FlowController}.
54: *
55: * This implementation is very basic and suitable to simple applications without memory criticalities. It keeps a
56: stack of previous Presentations, without disposing their resources. A more efficient implementation should be similar
57: to the Android Activity life-cycle, which can jettison resources of past activities and recreate them on demand. But
58: it would require life-cycle methods on the node interfaces.
59: *
60: * @stereotype Controller
61: *
62: * @author Fabrizio Giudici
63: *
64: **********************************************************************************************************************/
65: @Slf4j
66: public class JavaFxFlowController implements FlowController
67: {
68: @RequiredArgsConstructor @Getter @ToString
69: static class NodeAndControl
70: {
71: @Nonnull
72: private final Node node;
73:
74: @Nullable
75: private final Object control;
76: }
77:
78: @Getter @Setter
79: private StackPane contentPane;
80:
81: // TODO: this implementation keeps all the history in the stack, thus wasting some memory.
82: private final Stack<NodeAndControl> presentationStack = new Stack<>();
83:
84: /*******************************************************************************************************************
85: *
86: * {@inheritDoc}
87: *
88: ******************************************************************************************************************/
89: @Override
90: public void showPresentation (@Nonnull final Object presentation)
91: {
92: showPresentationImpl(presentation, null);
93: }
94:
95: /*******************************************************************************************************************
96: *
97: * {@inheritDoc}
98: *
99: ******************************************************************************************************************/
100: @Override
101: public void showPresentation (@Nonnull final Object presentation, @Nonnull final Object control)
102: {
103: showPresentationImpl(presentation, control);
104: }
105:
106: /*******************************************************************************************************************
107: *
108: * {@inheritDoc}
109: *
110: ******************************************************************************************************************/
111: private void showPresentationImpl (@Nonnull final Object presentation, @Nullable final Object control)
112: {
113: log.info("showPresentationImpl({}, {})", presentation, control);
114:
115: // TODO: use an aspect - should be already done in some other project
116: // FIXME: should not be needed, presentations should already run in JavaFX thread
117: Platform.runLater(() ->
118: {
119: final Node newNode = (Node)presentation;
120:
121: if (presentationStack.isEmpty())
122: {
123: contentPane.getChildren().add(newNode);
124: }
125: else
126: {
127: final Node oldNode = presentationStack.peek().getNode();
128: slide(newNode, oldNode, +1);
129: }
130:
131: presentationStack.push(new NodeAndControl(newNode, control));
132: notifyActivated(control);
133: });
134: }
135:
136: /*******************************************************************************************************************
137: *
138: * {@inheritDoc}
139: *
140: ******************************************************************************************************************/
141: @Override
142: public void dismissCurrentPresentation()
143: {
144: log.info("dismissCurrentPresentation()");
145:
146: if (presentationStack.size() < 2)
147: {
148: // TODO: should ask for user confirmation
149: powerOff();
150: }
151: else
152: {
153: Platform.runLater(() ->
154: {
155: log.debug(">>>> presentationStack: {}", presentationStack);
156: final Node oldNode = presentationStack.pop().getNode();
157: final Node newNode = presentationStack.peek().getNode();
158: slide(newNode, oldNode, -1);
159: notifyActivated(presentationStack.peek().getControl());
160: });
161: }
162: }
163:
164: /*******************************************************************************************************************
165: *
166: * {@inheritDoc}
167: *
168: ******************************************************************************************************************/
169: public void tryToDismissCurrentPresentation()
170: {
171: log.info("tryToDismissCurrentPresentation()");
172:
173: try
174: {
175: canDeactivate(presentationStack.peek().getControl(), this::dismissCurrentPresentation);
176: }
177: catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
178: {
179: log.error("", e);
180: }
181: }
182:
183: /*******************************************************************************************************************
184: *
185: * {@inheritDoc}
186: *
187: ******************************************************************************************************************/
188: @Override
189: public void powerOff()
190: {
191: log.info("Shutting down...");
192: // TODO: fire a PowerOff event and wait for collaboration completion
193: // TODO: in this case, the responsibility to fire PowerOn should be moved here
194: // Platform.exit();
195: System.exit(0); // needed, otherwise Spring won't necessarily shut down
196: }
197:
198: /*******************************************************************************************************************
199: *
200: *
201: *
202: ******************************************************************************************************************/
203: private void notifyActivated (@Nullable final Object control)
204: {
205: try
206: {
207: log.debug("notifyActivated({})", control);
208: final Method method = findAnnotatedMethod(control, OnActivate.class);
209:
210: if (method != null)
211: {
212: // FIXME: should run in a background process
213: method.invoke(control);
214: }
215: }
216: catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
217: {
218: log.error("", e);
219: throw new RuntimeException(e);
220: }
221: }
222:
223: /*******************************************************************************************************************
224: *
225: * If a Presentation Control is passed, it is inspected for a method annotated with {@link OnDeactivate}. If found,
226: * it is called. If it returns {@code false}, this method returns {@code false}.
227: *
228: * @param control the Presentation Control
229: *
230: ******************************************************************************************************************/
231: private void canDeactivate (@Nullable final Object control, @Nonnull final Runnable runnable)
232: throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
233: {
234: log.debug("canDeactivate({})", control);
235: final Method method = findAnnotatedMethod(control, OnDeactivate.class);
236:
237: if (method == null)
238: {
239: runnable.run();
240: }
241: else
242: {
243: // FIXME: should run in a background process
244: if (method.invoke(control).equals(OnDeactivate.Result.PROCEED))
245: {
246: runnable.run();
247: }
248: }
249: }
250:
251: /*******************************************************************************************************************
252: *
253: *
254: *
255: ******************************************************************************************************************/
256: @Nullable
257: private static Method findAnnotatedMethod (@Nullable final Object object,
258: @Nonnull final Class<? extends Annotation> annotationClass)
259: throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
260: {
261: log.debug("findAnnotatedMethod({}, {})", object, annotationClass);
262:
263: if (object != null)
264: {
265: for (final Method method : object.getClass().getDeclaredMethods())
266: {
267: if (method.getAnnotation(annotationClass) != null)
268: {
269: log.debug(">>>> found {} annotated method on {}", annotationClass, object);
270: method.setAccessible(true);
271: return method;
272: }
273: }
274: }
275:
276: return null;
277: }
278:
279: /*******************************************************************************************************************
280: *
281: * Starts a "slide in/out" animation moving an old {@link Node} out and a new {@link Node} in, with a given
282: * direction.
283: *
284: * @param newNode the {@code Node} to move in
285: * @param oldNode the {@code Node} to move out
286: * @param direction +1 for "forward" direction, -1 for "backward" direction
287: *
288: ******************************************************************************************************************/
289: private void slide (@Nonnull final Node newNode, @Nonnull final Node oldNode, final int direction)
290: {
291: contentPane.getChildren().add(newNode);
292: final double height = contentPane.getHeight();
293: final KeyFrame start = new KeyFrame(Duration.ZERO,
294: new KeyValue(newNode.translateYProperty(), height * direction),
295: new KeyValue(oldNode.translateYProperty(), 0));
296: final KeyFrame end = new KeyFrame(Duration.millis(200),
297: new KeyValue(newNode.translateYProperty(), 0),
298: new KeyValue(oldNode.translateYProperty(), -height * direction));
299: final Timeline slideAnimation = new Timeline(start, end);
300: slideAnimation.setOnFinished(event -> contentPane.getChildren().remove(oldNode));
301: slideAnimation.play();
302: }
303: }