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.pathanimator;
021
022import com.google.gwt.dom.client.Element;
023import com.google.gwt.dom.client.Style;
024import com.google.gwt.user.client.ui.Widget;
025import gwt.material.design.addins.client.MaterialAddins;
026import gwt.material.design.addins.client.pathanimator.js.JsPathAnimator;
027import gwt.material.design.addins.client.pathanimator.js.JsPathAnimatorOptions;
028import gwt.material.design.client.MaterialDesignBase;
029import gwt.material.design.client.base.HasDurationTransition;
030import gwt.material.design.jquery.client.api.Functions;
031
032import static gwt.material.design.jquery.client.api.JQuery.$;
033
034//@formatter:off
035
036/**
037 * Custom component that provides meaningfull transition between two elements to show visual continuity.
038 * <p>
039 * <pre>
040 * {@code
041 *
042 * // CAN BE CALLED AS A HELPER STATIC CONTEXT
043 * MaterialPathAnimator.animate(Element source, Element target, Functions.Func animateCallback);
044 *
045 * // INSTANTIATE THE PUSHPIN COMPONENT
046 * MaterialPathAnimator animator = new MaterialPathAnimator();
047 * animator.setSourceElement(btnSource1.getElement());
048 * animator.setTargetElement(panelTarget1.getElement());
049 * animator.animate();
050 * // Reverse Animate
051 * animator.reverseAnimate();
052 * }
053 * </pre>
054 *
055 * @author kevzlou7979
056 * @see <a href="http://gwtmaterialdesign.github.io/gwt-material-demo/#pathAnimator">Material Path Animator</a>
057 * @see <a href="https://github.com/chinchang/cta.js">CTAJs 0.3.2</a>
058 */
059//@formatter:on
060public class MaterialPathAnimator implements HasDurationTransition {
061
062    static {
063        if (MaterialAddins.isDebug()) {
064            MaterialDesignBase.injectDebugJs(MaterialPathAnimatorDebugClientBundle.INSTANCE.pathanimatorDebugJs());
065        } else {
066            MaterialDesignBase.injectJs(MaterialPathAnimatorClientBundle.INSTANCE.pathanimatorJs());
067        }
068    }
069
070    private Element sourceElement;
071    private Element targetElement;
072    private Functions.Func animateCallback;
073    private Functions.Func reverseCallback;
074    private JsPathAnimatorOptions options = JsPathAnimatorOptions.create();
075
076    public MaterialPathAnimator() {
077    }
078
079    public MaterialPathAnimator(Element sourceElement, Element targetElement) {
080        this.sourceElement = sourceElement;
081        this.targetElement = targetElement;
082    }
083
084    /**
085     * Animate the path animator
086     */
087    public void animate() {
088        $("document").ready(() -> JsPathAnimator.cta(sourceElement, targetElement, options, () -> {
089            if (animateCallback != null) {
090                animateCallback.call();
091            } else {
092                // For default animateCallback when animateCallback is null
093                targetElement.getStyle().setVisibility(Style.Visibility.VISIBLE);
094                targetElement.getStyle().setOpacity(1);
095            }
096        }));
097    }
098
099    /**
100     * Helper method to apply the path animator.
101     *
102     * @param source Source element to apply the Path Animator
103     * @param target Target element to apply the Path Animator
104     */
105    public static void animate(Element source, final Element target) {
106        animate(source, target, null);
107    }
108
109    /**
110     * Helper method to apply the path animator.
111     *
112     * @param source Source widget to apply the Path Animator
113     * @param target Target widget to apply the Path Animator
114     */
115    public static void animate(Widget source, final Widget target) {
116        animate(source.getElement(), target.getElement());
117    }
118
119    /**
120     * Helper method to apply the path animator with callback.
121     *
122     * @param source   Source widget to apply the Path Animator
123     * @param target   Target widget to apply the Path Animator
124     * @param callback The callback method to be called when the path animator is applied
125     */
126    public static void animate(Widget source, Widget target, Functions.Func callback) {
127        animate(source.getElement(), target.getElement(), callback);
128    }
129
130    /**
131     * Helper method to apply the path animator with callback.
132     *
133     * @param sourceElement   Source widget to apply the Path Animator
134     * @param targetElement   Target widget to apply the Path Animator
135     * @param animateCallback The callback method to be called when the path animator is applied
136     */
137    public static void animate(Element sourceElement, Element targetElement, Functions.Func animateCallback) {
138        MaterialPathAnimator animator = new MaterialPathAnimator();
139        animator.setSourceElement(sourceElement);
140        animator.setTargetElement(targetElement);
141        animator.setAnimateCallback(animateCallback);
142        animator.animate();
143    }
144
145    /**
146     * Reverse the Animation
147     */
148    public void reverseAnimate() {
149        $("document").ready(() -> {
150            if (reverseCallback != null) {
151                reverseCallback.call();
152            } else {
153                targetElement.getStyle().setVisibility(Style.Visibility.HIDDEN);
154                targetElement.getStyle().setOpacity(0);
155            }
156            JsPathAnimator.cta(targetElement, sourceElement, options);
157        });
158    }
159
160    /**
161     * Helper method to reverse animate the source element to target element.
162     *
163     * @param sourceElement Source element to apply the Path Animator
164     * @param targetElement Target element to apply the Path Animator
165     */
166    public static void reverseAnimate(final Element sourceElement, final Element targetElement) {
167        reverseAnimate(sourceElement, targetElement, null);
168    }
169
170    /**
171     * Helper method to reverse animate the source element to target element.
172     *
173     * @param source Source widget to apply the Path Animator
174     * @param target Target widget to apply the Path Animator
175     */
176    public static void reverseAnimate(final Widget source, final Widget target) {
177        reverseAnimate(source.getElement(), target.getElement());
178    }
179
180    /**
181     * Helper method to reverse animate the source element to target element with reverse callback.
182     *
183     * @param source          Source widget to apply the Path Animator
184     * @param target          Target widget to apply the Path Animator
185     * @param reverseCallback The reverse callback method to be called when the path animator is applied
186     */
187    public static void reverseAnimate(Widget source, Widget target, Functions.Func reverseCallback) {
188        reverseAnimate(source.getElement(), target.getElement(), reverseCallback);
189    }
190
191    /**
192     * Helper method to reverse animate the source element to target element with reverse callback
193     *
194     * @param sourceElement   Source element to apply the Path Animator
195     * @param targetElement   Target element to apply the Path Animator
196     * @param reverseCallback The reverse callback method to be called when the path animator is applied
197     */
198    public static void reverseAnimate(Element sourceElement, Element targetElement, Functions.Func reverseCallback) {
199        MaterialPathAnimator animator = new MaterialPathAnimator();
200        animator.setSourceElement(sourceElement);
201        animator.setTargetElement(targetElement);
202        animator.setReverseCallback(reverseCallback);
203        animator.reverseAnimate();
204    }
205
206    /**
207     * Get the source element
208     */
209    public Element getSourceElement() {
210        return sourceElement;
211    }
212
213    /**
214     * Set the source element
215     */
216    public void setSourceElement(Element sourceElement) {
217        this.sourceElement = sourceElement;
218    }
219
220    /**
221     * Get the target element
222     */
223    public Element getTargetElement() {
224        return targetElement;
225    }
226
227    /**
228     * Set the target element
229     */
230    public void setTargetElement(Element targetElement) {
231        this.targetElement = targetElement;
232    }
233
234    /**
235     * Get the callback method when the path animator is applied
236     */
237    public Functions.Func getAnimateCallback() {
238        return animateCallback;
239    }
240
241    /**
242     * Set the callback method when the path animator is applied
243     */
244    public void setAnimateCallback(Functions.Func animateCallback) {
245        this.animateCallback = animateCallback;
246    }
247
248    /**
249     * Get the reverse callback method when the path animator is applied
250     */
251    public Functions.Func getReverseCallback() {
252        return reverseCallback;
253    }
254
255    /**
256     * Set the reverse callback method when the path animator is applied
257     */
258    public void setReverseCallback(Functions.Func reverseCallback) {
259        this.reverseCallback = reverseCallback;
260    }
261
262    @Override
263    public void setDuration(int duration) {
264        options.duration = duration / 1000.0;
265    }
266
267    @Override
268    public int getDuration() {
269        return (int) (options.duration * 1000);
270    }
271
272    /**
273     * Duration (in milliseconds) of targetElement to become visible, if hidden initially. The library will automatically try to figure this out from the element's computed styles. Default is 0 seconds.
274     */
275    public void setTargetShowDuration(int targetShowDuration) {
276        options.targetShowDuration = targetShowDuration / 1000.0;
277    }
278
279    public int getTargetShowDuration() {
280        return (int) (options.targetShowDuration * 1000);
281    }
282
283    /**
284     * Extra duration (in milliseconds) of targetElement to provide visual continuity between the animation and the rendering of the targetElement. Default is 1 second
285     */
286    public void setExtraTransitionDuration(int extraTransitionDuration) {
287        options.extraTransitionDuration = extraTransitionDuration / 1000.0;
288    }
289
290    public int getExtraTransitionDuration() {
291        return (int) (options.extraTransitionDuration * 1000);
292    }
293
294    public boolean isRelativeToWindow() {
295        return options.relativeToWindow;
296    }
297
298    /**
299     * Set to true if your target element is fixed positioned in the window. Default is relative to document (works good with normal elements).
300     */
301    public void setRelativeToWindow(boolean relativeToWindow) {
302        options.relativeToWindow = relativeToWindow;
303    }
304}