View Javadoc

1   /**
2    * This file Copyright (c) 2010-2014 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.viewport.AppsTransitionDelegate;
41  import info.magnolia.ui.vaadin.gwt.client.magnoliashell.viewport.MagnoliaSwipeRecognizer;
42  import info.magnolia.ui.vaadin.gwt.client.magnoliashell.viewport.animation.FadeAnimation;
43  import info.magnolia.ui.vaadin.gwt.client.magnoliashell.viewport.animation.SlideAnimation;
44  import info.magnolia.ui.vaadin.gwt.client.tabsheet.connector.MagnoliaTabSheetConnector;
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  import com.vaadin.client.Util;
70  
71  /**
72   * Client side implementation of Apps viewport.
73   */
74  public class AppsViewportWidget extends ViewportWidget implements HasSwipeHandlers {
75  
76      public static final String APP_INACTIVE_CLASS_NAME = "app-inactive";
77  
78      /**
79       * Listener interface for {@link AppsViewportWidget}.
80       */
81      public interface Listener {
82          void closeCurrentApp();
83  
84          void setCurrentApp(String name);
85      };
86  
87      private static final int SWIPE_OUT_THRESHOLD = 300;
88  
89      private final AppPreloader preloader = new AppPreloader();
90  
91      private boolean isAppClosing = false;
92  
93      private boolean isCurtainVisible = false;
94  
95      private Listener listener;
96  
97      private final TouchDelegate delegate = new TouchDelegate(this);
98  
99      private final FullScreenButton fullScreenButton = new FullScreenButton();
100 
101     private Element curtain = DOM.createDiv();
102 
103     private void closeCurrentApp() {
104         if (!isAppClosing()) {
105             isAppClosing = true;
106             listener.closeCurrentApp();
107         }
108     }
109 
110     private void toggleFullScreen() {
111         String cssClasses = RootPanel.get().getStyleName();
112         boolean isFullScreen = cssClasses.contains("fullscreen");
113         // toggle.
114         setFullScreen(!isFullScreen);
115     }
116 
117     private CloseButton closeButton = new CloseButton();
118 
119     /**
120      * Set the look of the application and the state of the button.
121      */
122     public void setFullScreen(boolean isFullScreen) {
123 
124         if (isFullScreen) {
125             // enable fullscreen
126             RootPanel.get().addStyleName("fullscreen");
127 
128             fullScreenButton.getElement().addClassName("icon-extend-header");
129             fullScreenButton.getElement().removeClassName("icon-collapse-header");
130         } else {
131 
132             // disable fullscreen
133             RootPanel.get().removeStyleName("fullscreen");
134 
135             fullScreenButton.getElement().addClassName("icon-collapse-header");
136             fullScreenButton.getElement().removeClassName("icon-extend-header");
137         }
138     }
139 
140     public AppsViewportWidget(final Listener listener) {
141         super();
142         this.listener = listener;
143         DOM.sinkEvents(getElement(), Event.ONCLICK);
144         curtain.setClassName("v-curtain v-curtain-green");
145         closeButton.addStyleDependentName("app");
146         delegate.addTouchEndHandler(new TouchEndHandler() {
147             @Override
148             public void onTouchEnd(TouchEndEvent event) {
149                 Element target = event.getNativeEvent().getEventTarget().cast();
150                 if (closeButton.getElement().isOrHasChild(target)) {
151                     closeCurrentApp();
152                 } else if (fullScreenButton.getElement().isOrHasChild(target)) {
153                     toggleFullScreen();
154                 }
155             }
156         });
157 
158         bindTouchHandlers();
159 
160     }
161 
162     public void goToNextApp() {
163         if (getWidgetCount() > 1) {
164             processSwipe(-1);
165             switchToApp(DIRECTION.RIGHT_TO_LEFT);
166         }
167     }
168 
169     public void goToPreviousApp() {
170         if (getWidgetCount() > 1) {
171             processSwipe(1);
172             switchToApp(DIRECTION.LEFT_TO_RIGHT);
173         }
174     }
175 
176     /**
177      * Get the app that is currently open.
178      * @return The currently open app or null if none are open.
179      */
180     public Widget getCurrentApp(){
181         if (getWidgetCount() < 1) {
182             return null;
183         }else{
184             for (int w=0; w < getWidgetCount(); w++){
185                 Widget app = getWidget(w);
186                 String style = app.getStyleName();
187                 if (!app.getStyleName().contains("app-inactive")){
188                     return app;
189                 }
190             }
191         }
192         // No app is active.
193         return null;
194     }
195 
196     public Element getCurtain() {
197         return curtain;
198     }
199 
200     public void setCurtainVisible(boolean visible) {
201         if (isCurtainVisible != visible) {
202             this.isCurtainVisible = visible;
203             ((AppsTransitionDelegate) getTransitionDelegate()).setCurtainVisible(isCurtainVisible);
204         }
205     }
206 
207     /* APP CLOSING */
208     @Override
209     public void showChildNoTransition(Widget w) {
210         getElement().appendChild(closeButton.getElement());
211         getElement().appendChild(fullScreenButton.getElement());
212         Widget formerVisible = getVisibleChild();
213         // do not hide app if closing
214         if (formerVisible != null && !isAppClosing()) {
215             formerVisible.getElement().getStyle().setVisibility(Visibility.HIDDEN);
216             formerVisible.addStyleName(APP_INACTIVE_CLASS_NAME);
217         }
218         w.setVisible(true);
219         w.removeStyleName(APP_INACTIVE_CLASS_NAME);
220         w.getElement().getStyle().clearVisibility();
221     }
222 
223     @Override
224     public void removeChild(Widget w) {
225         ((AppsTransitionDelegate) getTransitionDelegate()).removeWidget(w);
226         getElement().removeChild(closeButton.getElement());
227         getElement().removeChild(fullScreenButton.getElement());
228     }
229 
230     @Override
231     public void removeChildNoTransition(Widget w) {
232         super.removeChildNoTransition(w);
233         isAppClosing = false;
234     }
235 
236     public boolean isAppClosing() {
237         return isAppClosing;
238     }
239 
240     /* APP PRELOADER */
241     public void showAppPreloader(final String appName) {
242         preloader.setCaption(appName);
243         preloader.addStyleName("zoom-in");
244         RootPanel.get().add(preloader);
245     }
246 
247     public boolean hasPreloader() {
248         return RootPanel.get().getWidgetIndex(preloader) >= 0;
249     }
250 
251     public void removePreloader() {
252         final FadeAnimation preloaderFadeOut = new FadeAnimation(0d, true);
253         preloaderFadeOut.addCallback(new JQueryCallback() {
254             @Override
255             public void execute(JQueryWrapper query) {
256                 RootPanel.get().remove(preloader);
257             }
258         });
259         preloaderFadeOut.run(500, preloader.getElement());
260     }
261 
262     /* SWIPE GESTURES */
263     private void bindTouchHandlers() {
264         DOM.sinkEvents(getElement(), Event.TOUCHEVENTS);
265         delegate.addTouchHandler(new MagnoliaSwipeRecognizer(delegate, SWIPE_OUT_THRESHOLD));
266         addSwipeStartHandler(new SwipeStartHandler() {
267             @Override
268             public void onSwipeStart(SwipeStartEvent event) {
269                 processSwipe(event.getDistance() * (event.getDirection() == DIRECTION.LEFT_TO_RIGHT ? 1 : -1));
270             }
271         });
272 
273         addSwipeMoveHandler(new SwipeMoveHandler() {
274             @Override
275             public void onSwipeMove(SwipeMoveEvent event) {
276                 processSwipe(event.getDistance() * (event.getDirection() == SwipeEvent.DIRECTION.LEFT_TO_RIGHT ? 1 : -1));
277             }
278         });
279 
280         addSwipeEndHandler(new SwipeEndHandler() {
281             @Override
282             public void onSwipeEnd(SwipeEndEvent event) {
283                 if (getWidgetCount() > 1) {
284                     final SwipeEvent.DIRECTION direction = event.getDirection();
285                     if (event.isDistanceReached()) {
286                         switchToApp(direction);
287                     } else {
288                         final Widget visibleChild = getVisibleChild();
289                         final SlideAnimation slideAnimation = new SlideAnimation(false, true);
290                         slideAnimation.setTargetValue(0);
291                         slideAnimation.run(500, visibleChild.getElement());
292                         slideAnimation.addCallback(new JQueryCallback() {
293                             @Override
294                             public void execute(JQueryWrapper query) {
295                                 // do not trigger transitions
296                                 dropZIndeces();
297                                 Widget next = getNextWidget();
298                                 Widget previous = getPreviousWidget();
299                                 if (next != null) {
300                                     next.getElement().getStyle().setVisibility(Visibility.HIDDEN);
301                                 }
302 
303                                 if (previous != null) {
304                                     previous.getElement().getStyle().setVisibility(Visibility.HIDDEN);
305                                 }
306                             }
307                         });
308                     }
309                 }
310             }
311         });
312 
313         delegate.addTouchCancelHandler(new TouchCancelHandler() {
314             @Override
315             public void onTouchCanceled(TouchCancelEvent event) {
316                 dropZIndeces();
317                 Widget next = getNextWidget();
318                 Widget previous = getPreviousWidget();
319                 if (next != null) {
320                     next.getElement().getStyle().setVisibility(Visibility.HIDDEN);
321                 }
322 
323                 if (previous != null) {
324                     previous.getElement().getStyle().setVisibility(Visibility.HIDDEN);
325                 }
326 
327                 getVisibleChild().getElement().getStyle().clearLeft();
328             }
329         });
330     }
331 
332     private void switchToApp(final SwipeEvent.DIRECTION direction) {
333 
334         final Widget newVisibleWidget = direction == DIRECTION.LEFT_TO_RIGHT ? getPreviousWidget() : getNextWidget();
335         SlideAnimation slideAnimation = new SlideAnimation(false, true);
336         slideAnimation.addCallback(new JQueryCallback() {
337             @Override
338             public void execute(JQueryWrapper query) {
339                 // do not trigger transitions
340                 showChild(newVisibleWidget);
341                 dropZIndeces();
342                 // TODO: Ideally AppsViewport shouldn't need to know about MagnoliaTabSheetConnector - improvement would be to have a mapping from connectors to app names. See MGNLUI-1679.
343                 MagnoliaTabSheetConnector appConnector = (MagnoliaTabSheetConnector) Util.findConnectorFor(newVisibleWidget);
344                 listener.setCurrentApp(appConnector.getState().name);
345             }
346         });
347 
348         Element targetElement = getVisibleChild().getElement();
349         slideAnimation.setTargetValue(getOffsetWidth() * (direction == DIRECTION.LEFT_TO_RIGHT ? 1 : -1));
350         slideAnimation.run(450, targetElement);
351 
352         if (direction == DIRECTION.RIGHT_TO_LEFT && getWidgetCount() > 2) {
353             final SlideAnimation slideNewVisibleToEdgeAnimation = new SlideAnimation(false, true);
354             slideNewVisibleToEdgeAnimation.setTargetValue(0);
355             slideNewVisibleToEdgeAnimation.run(500, newVisibleWidget.getElement());
356         }
357 
358     }
359 
360     private void processSwipe(int translationValue) {
361         if (getWidgetCount() > 1) {
362             JQueryWrapper.select(getVisibleChild()).setCss("-webkit-transform", "translate3d(" + translationValue + "px,0,0)");
363             showCandidateApp(translationValue);
364         }
365     }
366 
367     private void showCandidateApp(int translationValue) {
368         final Widget nextWidget = getNextWidget();
369         final Widget previousWidget = getPreviousWidget();
370         boolean isNext = translationValue < 0;
371         if (isNext) {
372             nextWidget.getElement().getStyle().setZIndex(250);
373             getVisibleChild().getElement().getStyle().setZIndex(251);
374         } else {
375             previousWidget.getElement().getStyle().setZIndex(250);
376             getVisibleChild().getElement().getStyle().setZIndex(251);
377         }
378 
379         if (isNext && getWidgetCount() > 2) {
380             JQueryWrapper.select(nextWidget).setCss("-webkit-transform", "translate3d(" + (translationValue + getVisibleChild().getOffsetWidth()) + "px,0,0)");
381         }
382 
383         nextWidget.getElement().getStyle().setVisibility(isNext || nextWidget == previousWidget ? Visibility.VISIBLE : Visibility.HIDDEN);
384         previousWidget.getElement().getStyle().setVisibility(!isNext || nextWidget == previousWidget ? Visibility.VISIBLE : Visibility.HIDDEN);
385     }
386 
387     private Widget getNextWidget() {
388         int index = getWidgetIndex(getVisibleChild());
389         return getWidget((index + 1) % getWidgetCount());
390     }
391 
392     private Widget getPreviousWidget() {
393         int index = getWidgetIndex(getVisibleChild());
394         int count = getWidgetCount();
395         return getWidget((index + (count - 1)) % count);
396     }
397 
398     private void dropZIndeces() {
399         final Iterator<Widget> it = iterator();
400         while (it.hasNext()) {
401             it.next().getElement().getStyle().clearZIndex();
402         }
403     }
404 
405     @Override
406     public HandlerRegistration addSwipeStartHandler(SwipeStartHandler handler) {
407         return addHandler(handler, SwipeStartEvent.getType());
408     }
409 
410     @Override
411     public HandlerRegistration addSwipeMoveHandler(SwipeMoveHandler handler) {
412         return addHandler(handler, SwipeMoveEvent.getType());
413     }
414 
415     @Override
416     public HandlerRegistration addSwipeEndHandler(SwipeEndHandler handler) {
417         return addHandler(handler, SwipeEndEvent.getType());
418     }
419 
420 }