View Javadoc
1   /**
2    * This file Copyright (c) 2010-2018 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.ui.vaadin.gwt.client.magnoliashell.viewport.widget;
35  
36  import info.magnolia.ui.vaadin.gwt.client.CloseButton;
37  import info.magnolia.ui.vaadin.gwt.client.FullScreenButton;
38  import info.magnolia.ui.vaadin.gwt.client.jquerywrapper.JQueryCallback;
39  import info.magnolia.ui.vaadin.gwt.client.jquerywrapper.JQueryWrapper;
40  import info.magnolia.ui.vaadin.gwt.client.magnoliashell.ShellState;
41  import info.magnolia.ui.vaadin.gwt.client.magnoliashell.viewport.AppsTransitionDelegate;
42  import info.magnolia.ui.vaadin.gwt.client.magnoliashell.viewport.MagnoliaSwipeRecognizer;
43  import info.magnolia.ui.vaadin.gwt.client.magnoliashell.viewport.animation.FadeAnimation;
44  import info.magnolia.ui.vaadin.gwt.client.magnoliashell.viewport.animation.SlideAnimation;
45  
46  import java.util.Iterator;
47  
48  import com.google.gwt.dom.client.Style.Visibility;
49  import com.google.gwt.event.shared.HandlerRegistration;
50  import com.google.gwt.user.client.DOM;
51  import com.google.gwt.user.client.Element;
52  import com.google.gwt.user.client.Event;
53  import com.google.gwt.user.client.ui.RootPanel;
54  import com.google.gwt.user.client.ui.Widget;
55  import com.googlecode.mgwt.dom.client.event.touch.TouchCancelEvent;
56  import com.googlecode.mgwt.dom.client.event.touch.TouchCancelHandler;
57  import com.googlecode.mgwt.dom.client.event.touch.TouchEndEvent;
58  import com.googlecode.mgwt.dom.client.event.touch.TouchEndHandler;
59  import com.googlecode.mgwt.dom.client.recognizer.swipe.HasSwipeHandlers;
60  import com.googlecode.mgwt.dom.client.recognizer.swipe.SwipeEndEvent;
61  import com.googlecode.mgwt.dom.client.recognizer.swipe.SwipeEndHandler;
62  import com.googlecode.mgwt.dom.client.recognizer.swipe.SwipeEvent;
63  import com.googlecode.mgwt.dom.client.recognizer.swipe.SwipeEvent.DIRECTION;
64  import com.googlecode.mgwt.dom.client.recognizer.swipe.SwipeMoveEvent;
65  import com.googlecode.mgwt.dom.client.recognizer.swipe.SwipeMoveHandler;
66  import com.googlecode.mgwt.dom.client.recognizer.swipe.SwipeStartEvent;
67  import com.googlecode.mgwt.dom.client.recognizer.swipe.SwipeStartHandler;
68  import com.googlecode.mgwt.ui.client.widget.touch.TouchDelegate;
69  
70  /**
71   * Client side implementation of Apps viewport.
72   */
73  public class AppsViewportWidget extends ViewportWidget<AppsTransitionDelegate> implements HasSwipeHandlers {
74  
75      public static final String APP_INACTIVE_CLASS_NAME = "app-inactive";
76  
77      /**
78       * Listener interface for {@link AppsViewportWidget}.
79       */
80      public interface Listener {
81  
82          void closeCurrentApp();
83  
84          void activateApp(Widget appWidget);
85  
86      };
87  
88      private static final int SWIPE_OUT_THRESHOLD = 300;
89  
90      private final AppPreloadern/gwt/client/magnoliashell/viewport/widget/AppPreloader.html#AppPreloader">AppPreloader preloader = new AppPreloader();
91  
92      private boolean isAppClosing = false;
93  
94      private boolean isCurtainVisible = false;
95  
96      private Listener listener;
97  
98      private final TouchDelegate/client/widget/touch/TouchDelegate.html#TouchDelegate">TouchDelegate delegate = new TouchDelegate(this);
99  
100     private final FullScreenButtont/FullScreenButton.html#FullScreenButton">FullScreenButton fullScreenButton = new FullScreenButton();
101 
102     private Element curtain = DOM.createDiv();
103 
104     /**
105      * Checks whether there are enough apps to navigate to next/previous app (> 1), that some app is loaded and displayed and
106      * that no animation is currently in progress.
107      */
108     public boolean readyForAppSwipeOrShortcutNavigation() {
109         return ShellState.get().isAppStarted() && getWidgetCount() > 1 && !getTransitionDelegate().inProgress();
110     }
111 
112     private void closeCurrentApp() {
113         if (!isAppClosing()) {
114             isAppClosing = true;
115             listener.closeCurrentApp();
116             isAppClosing = false; //app closed
117         }
118     }
119 
120     private void toggleFullScreen() {
121         String cssClasses = RootPanel.get().getStyleName();
122         boolean isFullScreen = cssClasses.contains("fullscreen");
123         // toggle.
124         setFullScreen(!isFullScreen);
125     }
126 
127     private CloseButton/gwt/client/CloseButton.html#CloseButton">CloseButton closeButton = new CloseButton();
128 
129     /**
130      * Set the look of the application and the state of the button.
131      */
132     public void setFullScreen(boolean isFullScreen) {
133 
134         if (isFullScreen) {
135             // enable fullscreen
136             RootPanel.get().addStyleName("fullscreen");
137 
138             fullScreenButton.getElement().addClassName("icon-extend-header");
139             fullScreenButton.getElement().removeClassName("icon-collapse-header");
140         } else {
141 
142             // disable fullscreen
143             RootPanel.get().removeStyleName("fullscreen");
144 
145             fullScreenButton.getElement().addClassName("icon-collapse-header");
146             fullScreenButton.getElement().removeClassName("icon-extend-header");
147         }
148     }
149 
150     public AppsViewportWidget(final Listener listener) {
151         super();
152         this.listener = listener;
153         DOM.sinkEvents(getElement(), Event.ONCLICK);
154         curtain.setClassName("v-curtain v-curtain-green");
155         closeButton.addStyleDependentName("app");
156         delegate.addTouchEndHandler(new TouchEndHandler() {
157             @Override
158             public void onTouchEnd(TouchEndEvent event) {
159                 Element target = event.getNativeEvent().getEventTarget().cast();
160                 if (closeButton.getElement().isOrHasChild(target)) {
161                     closeCurrentApp();
162                 } else if (fullScreenButton.getElement().isOrHasChild(target)) {
163                     toggleFullScreen();
164                 }
165             }
166         });
167 
168         bindTouchHandlers();
169 
170     }
171 
172     public void goToNextApp() {
173         if (getWidgetCount() > 1) {
174             processSwipe(-1);
175             switchToApp(DIRECTION.RIGHT_TO_LEFT);
176         }
177     }
178 
179     public void goToPreviousApp() {
180         if (getWidgetCount() > 1) {
181             processSwipe(1);
182             switchToApp(DIRECTION.LEFT_TO_RIGHT);
183         }
184     }
185 
186     /**
187      * Get the app that is currently open.
188      * @return The currently open app or null if none are open.
189      */
190     public Widget getCurrentApp(){
191         if (getWidgetCount() < 1) {
192             return null;
193         }else{
194             for (int w=0; w < getWidgetCount(); w++){
195                 Widget app = getWidget(w);
196                 String style = app.getStyleName();
197                 if (!app.getStyleName().contains("app-inactive")){
198                     return app;
199                 }
200             }
201         }
202         // No app is active.
203         return null;
204     }
205 
206     public Element getCurtain() {
207         return curtain;
208     }
209 
210     public void setCurtainVisible(boolean visible) {
211         if (isCurtainVisible != visible) {
212             this.isCurtainVisible = visible;
213             getTransitionDelegate().setCurtainVisible(isCurtainVisible);
214         }
215     }
216 
217     /* APP CLOSING */
218     @Override
219     public void showChildNoTransition(Widget w) {
220         getElement().appendChild(closeButton.getElement());
221         getElement().appendChild(fullScreenButton.getElement());
222         Widget formerVisible = getVisibleChild();
223         // do not hide app if closing
224         if (formerVisible != null && !isAppClosing()) {
225             formerVisible.getElement().getStyle().setVisibility(Visibility.HIDDEN);
226             formerVisible.addStyleName(APP_INACTIVE_CLASS_NAME);
227         }
228         w.setVisible(true);
229         w.removeStyleName(APP_INACTIVE_CLASS_NAME);
230         w.getElement().getStyle().clearVisibility();
231     }
232 
233     @Override
234     public void removeChild(Widget w) {
235         getTransitionDelegate().removeWidget(w);
236         getElement().removeChild(closeButton.getElement());
237         getElement().removeChild(fullScreenButton.getElement());
238     }
239 
240     @Override
241     public void removeChildNoTransition(Widget w) {
242         super.removeChildNoTransition(w);
243         isAppClosing = false;
244     }
245 
246     public boolean isAppClosing() {
247         return isAppClosing;
248     }
249 
250     /* APP PRELOADER */
251     public void showAppPreloader(final String appName) {
252         preloader.setCaption(appName);
253         preloader.addStyleName("zoom-in");
254         RootPanel.get().add(preloader);
255     }
256 
257     public boolean hasPreloader() {
258         return RootPanel.get().getWidgetIndex(preloader) >= 0;
259     }
260 
261     public void removePreloader() {
262         final FadeAnimationient/magnoliashell/viewport/animation/FadeAnimation.html#FadeAnimation">FadeAnimation preloaderFadeOut = new FadeAnimation(0d, true);
263         preloaderFadeOut.addCallback(new JQueryCallback() {
264             @Override
265             public void execute(JQueryWrapper query) {
266                 RootPanel.get().remove(preloader);
267             }
268         });
269         preloaderFadeOut.run(500, preloader.getElement());
270     }
271 
272     /* SWIPE GESTURES */
273     private void bindTouchHandlers() {
274         DOM.sinkEvents(getElement(), Event.TOUCHEVENTS);
275         delegate.addTouchHandler(new MagnoliaSwipeRecognizer(delegate, SWIPE_OUT_THRESHOLD));
276         addSwipeStartHandler(new SwipeStartHandler() {
277             @Override
278             public void onSwipeStart(SwipeStartEvent event) {
279                 processSwipe(event.getDistance() * (event.getDirection() == DIRECTION.LEFT_TO_RIGHT ? 1 : -1));
280             }
281         });
282 
283         addSwipeMoveHandler(new SwipeMoveHandler() {
284             @Override
285             public void onSwipeMove(SwipeMoveEvent event) {
286                 processSwipe(event.getDistance() * (event.getDirection() == SwipeEvent.DIRECTION.LEFT_TO_RIGHT ? 1 : -1));
287             }
288         });
289 
290         addSwipeEndHandler(new SwipeEndHandler() {
291             @Override
292             public void onSwipeEnd(SwipeEndEvent event) {
293                 if (getWidgetCount() > 1) {
294                     final SwipeEvent.DIRECTION direction = event.getDirection();
295                     if (event.isDistanceReached()) {
296                         switchToApp(direction);
297                     } else {
298                         final Widget visibleChild = getVisibleChild();
299                         final SlideAnimationlient/magnoliashell/viewport/animation/SlideAnimation.html#SlideAnimation">SlideAnimation slideAnimation = new SlideAnimation(false, true);
300                         slideAnimation.setTargetValue(0);
301                         slideAnimation.run(500, visibleChild.getElement());
302                         slideAnimation.addCallback(new JQueryCallback() {
303                             @Override
304                             public void execute(JQueryWrapper query) {
305                                 // do not trigger transitions
306                                 dropZIndeces();
307                                 Widget next = getNextWidget();
308                                 Widget previous = getPreviousWidget();
309                                 if (next != null) {
310                                     next.getElement().getStyle().setVisibility(Visibility.HIDDEN);
311                                 }
312 
313                                 if (previous != null) {
314                                     previous.getElement().getStyle().setVisibility(Visibility.HIDDEN);
315                                 }
316                             }
317                         });
318                     }
319                 }
320             }
321         });
322 
323         delegate.addTouchCancelHandler(new TouchCancelHandler() {
324             @Override
325             public void onTouchCanceled(TouchCancelEvent event) {
326                 dropZIndeces();
327                 Widget next = getNextWidget();
328                 Widget previous = getPreviousWidget();
329                 if (next != null) {
330                     next.getElement().getStyle().setVisibility(Visibility.HIDDEN);
331                 }
332 
333                 if (previous != null) {
334                     previous.getElement().getStyle().setVisibility(Visibility.HIDDEN);
335                 }
336 
337                 getVisibleChild().getElement().getStyle().clearLeft();
338             }
339         });
340     }
341 
342     private void switchToApp(final SwipeEvent.DIRECTION direction) {
343 
344         final Widget newVisibleWidget = direction == DIRECTION.LEFT_TO_RIGHT ? getPreviousWidget() : getNextWidget();
345         SlideAnimationlient/magnoliashell/viewport/animation/SlideAnimation.html#SlideAnimation">SlideAnimation slideAnimation = new SlideAnimation(false, true);
346         slideAnimation.addCallback(new JQueryCallback() {
347             @Override
348             public void execute(JQueryWrapper query) {
349                 // do not trigger transitions
350                 showChild(newVisibleWidget);
351                 dropZIndeces();
352                 listener.activateApp(newVisibleWidget);
353             }
354         });
355 
356         Element targetElement = getVisibleChild().getElement();
357         slideAnimation.setTargetValue(getOffsetWidth() * (direction == DIRECTION.LEFT_TO_RIGHT ? 1 : -1));
358         slideAnimation.run(450, targetElement);
359 
360         if (direction == DIRECTION.RIGHT_TO_LEFT && getWidgetCount() > 2) {
361             final SlideAnimationell/viewport/animation/SlideAnimation.html#SlideAnimation">SlideAnimation slideNewVisibleToEdgeAnimation = new SlideAnimation(false, true);
362             slideNewVisibleToEdgeAnimation.setTargetValue(0);
363             slideNewVisibleToEdgeAnimation.run(500, newVisibleWidget.getElement());
364         }
365 
366     }
367 
368     private void processSwipe(int translationValue) {
369         if (getWidgetCount() > 1) {
370             JQueryWrapper.select(getVisibleChild()).setCss("-webkit-transform", "translate3d(" + translationValue + "px,0,0)");
371             showCandidateApp(translationValue);
372         }
373     }
374 
375     private void showCandidateApp(int translationValue) {
376         final Widget nextWidget = getNextWidget();
377         final Widget previousWidget = getPreviousWidget();
378         boolean isNext = translationValue < 0;
379         if (isNext) {
380             nextWidget.getElement().getStyle().setZIndex(250);
381             getVisibleChild().getElement().getStyle().setZIndex(251);
382         } else {
383             previousWidget.getElement().getStyle().setZIndex(250);
384             getVisibleChild().getElement().getStyle().setZIndex(251);
385         }
386 
387         if (isNext && getWidgetCount() > 2) {
388             JQueryWrapper.select(nextWidget).setCss("-webkit-transform", "translate3d(" + (translationValue + getVisibleChild().getOffsetWidth()) + "px,0,0)");
389         }
390 
391         nextWidget.getElement().getStyle().setVisibility(isNext || nextWidget == previousWidget ? Visibility.VISIBLE : Visibility.HIDDEN);
392         previousWidget.getElement().getStyle().setVisibility(!isNext || nextWidget == previousWidget ? Visibility.VISIBLE : Visibility.HIDDEN);
393     }
394 
395     private Widget getNextWidget() {
396         int index = getWidgetIndex(getVisibleChild());
397         return getWidget((index + 1) % getWidgetCount());
398     }
399 
400     private Widget getPreviousWidget() {
401         int index = getWidgetIndex(getVisibleChild());
402         int count = getWidgetCount();
403         return getWidget((index + (count - 1)) % count);
404     }
405 
406     private void dropZIndeces() {
407         final Iterator<Widget> it = iterator();
408         while (it.hasNext()) {
409             it.next().getElement().getStyle().clearZIndex();
410         }
411     }
412 
413     @Override
414     public HandlerRegistration addSwipeStartHandler(SwipeStartHandler handler) {
415         return addHandler(handler, SwipeStartEvent.getType());
416     }
417 
418     @Override
419     public HandlerRegistration addSwipeMoveHandler(SwipeMoveHandler handler) {
420         return addHandler(handler, SwipeMoveEvent.getType());
421     }
422 
423     @Override
424     public HandlerRegistration addSwipeEndHandler(SwipeEndHandler handler) {
425         return addHandler(handler, SwipeEndEvent.getType());
426     }
427 
428 }