001/*
002 * #%L
003 * GwtMaterial
004 * %%
005 * Copyright (C) 2015 - 2017 GwtMaterialDesign
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 * 
011 *      http://www.apache.org/licenses/LICENSE-2.0
012 * 
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package gwt.material.design.addins.client.swipeable;
021
022import com.google.gwt.dom.client.Document;
023import com.google.gwt.dom.client.Element;
024import com.google.gwt.event.shared.HandlerRegistration;
025import com.google.gwt.user.client.ui.UIObject;
026import com.google.gwt.user.client.ui.Widget;
027import gwt.material.design.addins.client.MaterialAddins;
028import gwt.material.design.addins.client.base.constants.AddinsCssName;
029import gwt.material.design.addins.client.gesture.velocity.js.JsTransitionOptions;
030import gwt.material.design.addins.client.gesture.velocity.js.JsVelocity;
031import gwt.material.design.addins.client.gesture.velocity.js.JsVelocityOptions;
032import gwt.material.design.addins.client.swipeable.base.HasSwipeableHandler;
033import gwt.material.design.addins.client.swipeable.events.*;
034import gwt.material.design.client.MaterialDesignBase;
035import gwt.material.design.client.base.JsLoader;
036import gwt.material.design.client.base.MaterialWidget;
037import gwt.material.design.client.constants.Color;
038import gwt.material.design.jquery.client.api.Functions;
039import gwt.material.design.jquery.client.api.JQueryElement;
040
041import static gwt.material.design.addins.client.gesture.hammer.js.JsHammer.$;
042
043/**
044 * A panel that allows any of its nested children to be swiped away.
045 * <p>
046 * <h3>XML Namespace Declaration</h3>
047 * <pre>
048 * {@code
049 * xmlns:ma='urn:import:gwt.material.design.addins.client'
050 * }
051 * </pre>
052 * <p>
053 * <h3>UiBinder Usage:</h3>
054 * <pre>
055 * {
056 * @code
057 * <ma:swipeable.MaterialSwipeablePanel ui:field="swipeablePanel" shadow="1" backgroundColor="white" padding="12">
058 *   <m:MaterialLabel text="You can swipe native components. This is a plain label" backgroundColor="yellow" padding="12" />
059 *   <m:MaterialCard>
060 *     <m:MaterialCardContent>
061 *       <m:MaterialLabel text="This is another Card Component that is swipeable." />
062 *       </m:MaterialCardContent>
063 *     </m:MaterialCard>
064 * </ma:swipeable.MaterialSwipeablePanel>
065 * }
066 * </pre>
067 *
068 * @author kevzlou7979
069 * @author Ben Dol
070 * @see <a href="http://gwtmaterialdesign.github.io/gwt-material-demo/#swipeable">Material Swipeable</a>
071 */
072//@formatter:on
073public class MaterialSwipeablePanel extends MaterialWidget implements JsLoader, HasSwipeableHandler<Widget> {
074
075    static {
076        if (MaterialAddins.isDebug()) {
077            MaterialDesignBase.injectDebugJs(MaterialSwipeableDebugClientBundle.INSTANCE.swipeableJsDebug());
078            MaterialDesignBase.injectCss(MaterialSwipeableDebugClientBundle.INSTANCE.swipeableCssDebug());
079        } else {
080            MaterialDesignBase.injectJs(MaterialSwipeableClientBundle.INSTANCE.swipeableJs());
081            MaterialDesignBase.injectCss(MaterialSwipeableClientBundle.INSTANCE.swipeableCss());
082        }
083    }
084
085    public MaterialSwipeablePanel() {
086        super(Document.get().createDivElement(), AddinsCssName.SWIPEABLE);
087    }
088
089    public MaterialSwipeablePanel(Color backgroundColor) {
090        this();
091        setBackgroundColor(backgroundColor);
092    }
093
094    public MaterialSwipeablePanel(Color backgroundColor, Integer shadow) {
095        this(backgroundColor);
096        setShadow(shadow);
097    }
098
099    @Override
100    protected void onLoad() {
101        super.onLoad();
102
103        load();
104    }
105
106    @Override
107    public void load() {
108        for (Widget w : getChildren()) {
109            if (!w.getStyleName().contains(AddinsCssName.IGNORED)) {
110                load(w.getElement(), w);
111            }
112        }
113    }
114
115    protected void load(Element container, Widget target) {
116        JQueryElement parent = $(container);
117        parent.each((object, element) -> {
118            boolean swipeLeftToRight[] = {false};
119            boolean swipeRightToLeft[] = {false};
120
121            $(element).hammer().bind("pan", (result) -> {
122                int direction = result.gesture.direction;
123                int x = result.gesture.deltaX;
124                double velocityX = result.gesture.velocityX;
125
126                JsVelocity.$(container).velocity(buildVelocityOption(x), buildTransitionOption(50, false, "easeOutQuad", null));
127
128                if (direction == 4 && (x > parent.innerWidth() || velocityX < -0.75)) {
129                    swipeLeftToRight[0] = true;
130                }
131
132                if (direction == 2 && (x < (-1 * parent.innerWidth() / 2) || velocityX > 0.75)) {
133                    swipeRightToLeft[0] = true;
134                }
135
136            }).bind("panend", e -> {
137                // Reset if collection is moved back into original position
138                if (Math.abs(e.gesture.deltaX) < ($(element).innerWidth() / 2)) {
139                    swipeLeftToRight[0] = false;
140                    swipeRightToLeft[0] = false;
141                }
142
143                // Sets final position once pan ended
144                if (swipeLeftToRight[0] || swipeRightToLeft[0]) {
145                    double fullWidth = 0;
146                    if (swipeLeftToRight[0]) {
147                        fullWidth = parent.innerWidth();
148                        SwipeRightEvent.fire(MaterialSwipeablePanel.this, target);
149                    }
150
151                    if (swipeRightToLeft[0]) {
152                        fullWidth = -1 * parent.innerWidth();
153                        SwipeLeftEvent.fire(MaterialSwipeablePanel.this, target);
154                    }
155
156                    Functions.Func completeCallback = () -> {
157                        parent.css("border", "none");
158                        JsVelocity.$(parent).velocity(buildVelocityOption(0, 0), buildTransitionOption(200, false, "easeQuadOut", () -> parent.remove()));
159                    };
160
161                    JsVelocity.$(parent).velocity(buildVelocityOption(fullWidth), buildTransitionOption(100, false, "easeOutQuad", completeCallback));
162                } else {
163                    JsVelocity.$(parent).velocity(buildVelocityOption(0), buildTransitionOption(100, false, "easeQuad", null));
164                }
165            });
166        });
167    }
168
169    @Override
170    protected void onUnload() {
171        super.onUnload();
172
173        unload();
174    }
175
176    @Override
177    public void unload() {
178        for (Widget widget : getChildren()) {
179            JQueryElement element = $(widget.getElement());
180            element.off("pan");
181            element.off("panend");
182        }
183    }
184
185    @Override
186    public void reload() {
187        unload();
188        load();
189    }
190
191    protected JsVelocityOptions buildVelocityOption(double translateX) {
192        JsVelocityOptions option = new JsVelocityOptions();
193        option.translateX = translateX;
194        return option;
195    }
196
197    protected JsVelocityOptions buildVelocityOption(double height, double padding) {
198        JsVelocityOptions option = new JsVelocityOptions();
199        option.height = 0;
200        option.padding = 0;
201        return option;
202    }
203
204    protected JsTransitionOptions buildTransitionOption(int duration, boolean queue, String easing, Functions.Func completeCallback) {
205        JsTransitionOptions option = new JsTransitionOptions();
206        option.duration = duration;
207        option.easing = easing;
208        option.queue = queue;
209        if (completeCallback != null) {
210            option.complete = completeCallback;
211        }
212        return option;
213    }
214
215    /**
216     * Ignore any elements to be swipeable
217     */
218    public void ignore(UIObject object, UIObject... objects) {
219        object.addStyleName(AddinsCssName.IGNORED);
220
221        if (objects != null) {
222            for (UIObject obj : objects) {
223                obj.addStyleName(AddinsCssName.IGNORED);
224            }
225        }
226    }
227
228    /**
229     * Remove Ignore property to any ignored elements
230     */
231    public void removeIgnore(UIObject object, UIObject... objects) {
232        object.removeStyleName(AddinsCssName.IGNORED);
233
234        if (objects != null) {
235            for (UIObject obj : objects) {
236                obj.removeStyleName(AddinsCssName.IGNORED);
237            }
238        }
239    }
240
241    @Override
242    public HandlerRegistration addSwipeLeftHandler(final SwipeLeftEvent.SwipeLeftHandler<Widget> handler) {
243        return addHandler(new SwipeLeftEvent.SwipeLeftHandler<Widget>() {
244            @Override
245            public void onSwipeLeft(SwipeLeftEvent<Widget> event) {
246                if (isEnabled()) {
247                    handler.onSwipeLeft(event);
248                }
249            }
250        }, SwipeLeftEvent.getType());
251    }
252
253    @Override
254    public HandlerRegistration addSwipeRightHandler(final SwipeRightEvent.SwipeRightHandler<Widget> handler) {
255        return addHandler(new SwipeRightEvent.SwipeRightHandler<Widget>() {
256            @Override
257            public void onSwipeRight(SwipeRightEvent<Widget> event) {
258                if (isEnabled()) {
259                    handler.onSwipeRight(event);
260                }
261            }
262        }, SwipeRightEvent.getType());
263    }
264
265    @Override
266    public HandlerRegistration addOnStartSwipeLeftHandler(OnStartSwipeLeftEvent.OnStartSwipeLeftHandler<Widget> handler) {
267        return addHandler(handler, OnStartSwipeLeftEvent.getType());
268    }
269
270    @Override
271    public HandlerRegistration addOnStartSwipeRightHandler(OnStartSwipeRightEvent.OnStartSwipeRightHandler<Widget> handler) {
272        return addHandler(handler, OnStartSwipeRightEvent.getType());
273    }
274
275    @Override
276    public HandlerRegistration addOnEndSwipeLeftHandler(OnEndSwipeLeftEvent.OnEndSwipeLeftHandler<Widget> handler) {
277        return addHandler(handler, OnEndSwipeLeftEvent.getType());
278    }
279
280    @Override
281    public HandlerRegistration addOnEndSwipeRightHandler(OnEndSwipeRightEvent.OnEndSwipeRightHandler<Widget> handler) {
282        return addHandler(handler, OnEndSwipeRightEvent.getType());
283    }
284}