001package org.gwtbootstrap3.extras.animate.client.ui;
002
003/*
004 * #%L
005 * GwtBootstrap3
006 * %%
007 * Copyright (C) 2013 - 2014 GwtBootstrap3
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 * 
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 * 
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import com.google.gwt.core.client.Scheduler;
024import com.google.gwt.dom.client.Element;
025import com.google.gwt.dom.client.StyleInjector;
026import com.google.gwt.user.client.ui.UIObject;
027import org.gwtbootstrap3.extras.animate.client.ui.constants.Animation;
028
029import java.util.ArrayList;
030
031/**
032 * Utility class to dynamically animate objects using CSS animations.
033 *
034 * @author Pavel Zlámal
035 */
036public class Animate {
037
038    // store used styles, so they are not injected to the DOM everytime.
039    private static final ArrayList<String> usedStyles = new ArrayList<String>();
040
041    /**
042     * Animate any element with specific animation. Animation is done by CSS and runs only once.
043     *
044     * Animation is started when element is appended to the DOM or new (not same) animation is added
045     * to already displayed element. Animation runs on hidden elements too and is not paused/stopped
046     * when element is set as hidden.
047     *
048     * @param widget Widget to apply animation to.
049     * @param animation Type of animation to apply.
050     * @param <T> Any object extending UIObject class (typically Widget).
051     * @return Animation's CSS class name, which can be removed to stop animation.
052     */
053    public static <T extends UIObject> String animate(final T widget, final Animation animation) {
054        return animate(widget, animation, 1, -1, -1);
055    }
056
057    /**
058     * Animate any element with specific animation. Animation is done by CSS and runs multiple times.
059     *
060     * Animation is started when element is appended to the DOM or new (not same) animation is added
061     * to already displayed element. Animation runs on hidden elements too and is not paused/stopped
062     * when element is set as hidden.
063     *
064     * @param widget Widget to apply animation to.
065     * @param animation Type of animation to apply.
066     * @param count Number of animation repeats. 0 disables animation, any negative value set repeats to infinite.
067     * @param <T> Any object extending UIObject class (typically Widget).
068     * @return Animation's CSS class name, which can be removed to stop animation.
069     */
070    public static <T extends UIObject> String animate(final T widget, final Animation animation, final int count) {
071        return animate(widget, animation, count, -1, -1);
072    }
073
074    /**
075     * Animate any element with specific animation. Animation is done by CSS and runs multiple times.
076     *
077     * Animation is started when element is appended to the DOM or new (not same) animation is added
078     * to already displayed element. Animation runs on hidden elements too and is not paused/stopped
079     * when element is set as hidden.
080     *
081     * @param widget Widget to apply animation to.
082     * @param animation Type of animation to apply.
083     * @param count Number of animation repeats. 0 disables animation, any negative value set repeats to infinite.
084     * @param duration Animation duration in ms. 0 disables animation, any negative value keeps default of original animation.
085     * @param <T> Any object extending UIObject class (typically Widget).
086     * @return Animation's CSS class name, which can be removed to stop animation.
087     */
088    public static <T extends UIObject> String animate(final T widget, final Animation animation, final int count, final int duration) {
089        return animate(widget, animation, count, duration, -1);
090    }
091
092    /**
093     * Animate any element with specific animation. Animation is done by CSS and runs multiple times.
094     *
095     * Animation is started when element is appended to the DOM or new (not same) animation is added
096     * to already displayed element. Animation runs on hidden elements too and is not paused/stopped
097     * when element is set as hidden.
098     *
099     * @param widget Widget to apply animation to.
100     * @param animation Type of animation to apply.
101     * @param count Number of animation repeats. 0 disables animation, any negative value set repeats to infinite.
102     * @param duration Animation duration in ms. 0 disables animation, any negative value keeps default of original animation.
103     * @param delay Delay before starting the animation loop in ms. Value <= 0 means no delay.
104     * @param <T> Any object extending UIObject class (typically Widget).
105     * @return Animation's CSS class name, which can be removed to stop animation.
106     */
107    public static <T extends UIObject> String animate(final T widget, final Animation animation, final int count, final int duration, final int delay) {
108
109        if (widget != null && animation != null) {
110            // on valid input
111            if (widget.getStyleName().contains(animation.getCssName())) {
112                // animation is present, remove it and run again.
113                stopAnimation(widget, animation.getCssName() + " " + getStyleNameFromAnimation(animation.getCssName(),count,duration,delay));
114                Scheduler.get().scheduleFixedDelay(new Scheduler.RepeatingCommand() {
115                    @Override
116                    public boolean execute() {
117                        styleElement(widget.getElement(), animation.getCssName(), count, duration, delay);
118                        return false;
119                    }
120                }, 200);
121                return animation.getCssName() + " " + getStyleNameFromAnimation(animation.getCssName(),count,duration,delay);
122            } else {
123                // animation was not present, run immediately
124                return styleElement(widget.getElement(), animation.getCssName(), count, duration, delay);
125            }
126        } else {
127            return null;
128        }
129
130    }
131
132    /**
133     * Animate any element with specific animation. Animation is done by CSS and runs only once.
134     *
135     * Animation is started when element is appended to the DOM or new (not same) animation is added
136     * to already displayed element. Animation runs on hidden elements too and is not paused/stopped
137     * when element is set as hidden.
138     *
139     * @param widget Widget to apply animation to.
140     * @param animation Custom CSS class name used as animation.
141     * @param <T> Any object extending UIObject class (typically Widget).
142     * @return Animation's CSS class name, which can be removed to stop animation.
143     */
144    public static <T extends UIObject> String animate(final T widget, final String animation) {
145        return animate(widget, animation, 1, -1, -1);
146    }
147
148    /**
149     * Animate any element with specific animation. Animation is done by CSS and runs multiple times.
150     *
151     * Animation is started when element is appended to the DOM or new (not same) animation is added
152     * to already displayed element. Animation runs on hidden elements too and is not paused/stopped
153     * when element is set as hidden.
154     *
155     * @param widget Widget to apply animation to.
156     * @param animation Custom CSS class name used as animation.
157     * @param count Number of animation repeats. 0 disables animation, any negative value set repeats to infinite.
158     * @param <T> Any object extending UIObject class (typically Widget).
159     * @return Animation's CSS class name, which can be removed to stop animation.
160     */
161    public static <T extends UIObject> String animate(final T widget, final String animation, final int count) {
162        return animate(widget, animation, count, -1, -1);
163    }
164
165    /**
166     * Animate any element with specific animation. Animation is done by CSS and runs multiple times.
167     *
168     * Animation is started when element is appended to the DOM or new (not same) animation is added
169     * to already displayed element. Animation runs on hidden elements too and is not paused/stopped
170     * when element is set as hidden.
171     *
172     * @param widget Widget to apply animation to.
173     * @param animation Custom CSS class name used as animation.
174     * @param count Number of animation repeats. 0 disables animation, any negative value set repeats to infinite.
175     * @param duration Animation duration in ms. 0 disables animation, any negative value keeps default of original animation.
176     * @param <T> Any object extending UIObject class (typically Widget).
177     * @return Animation's CSS class name, which can be removed to stop animation.
178     */
179    public static <T extends UIObject> String animate(final T widget, final String animation, final int count, final int duration) {
180        return animate(widget, animation, count, duration, -1);
181    }
182
183    /**
184     * Animate any element with specific animation. Animation is done by CSS and runs multiple times.
185     *
186     * Animation is started when element is appended to the DOM or new (not same) animation is added
187     * to already displayed element. Animation runs on hidden elements too and is not paused/stopped
188     * when element is set as hidden.
189     *
190     * @param widget Widget to apply animation to.
191     * @param animation Custom CSS class name used as animation.
192     * @param count Number of animation repeats. 0 disables animation, any negative value set repeats to infinite.
193     * @param duration Animation duration in ms. 0 disables animation, any negative value keeps default of original animation.
194     * @param delay Delay before starting the animation loop in ms. Value <= 0 means no delay.
195     * @param <T> Any object extending UIObject class (typically Widget).
196     * @return Animation's CSS class name, which can be removed to stop animation.
197     */
198    public static <T extends UIObject> String animate(final T widget, final String animation, final int count, final int duration, final int delay) {
199
200        if (widget != null && animation != null) {
201            // on valid input
202            if (widget.getStyleName().contains(animation)) {
203                // animation is present, remove it and run again.
204                stopAnimation(widget, animation);
205                Scheduler.get().scheduleFixedDelay(new Scheduler.RepeatingCommand() {
206                    @Override
207                    public boolean execute() {
208                        styleElement(widget.getElement(), animation, count, duration, delay);
209                        return false;
210                    }
211                }, 200);
212                return animation + " " + getStyleNameFromAnimation(animation,count,duration,delay);
213            } else {
214                // animation was not present, run immediately
215                return styleElement(widget.getElement(), animation, count, duration, delay);
216            }
217        } else {
218            return null;
219        }
220
221    }
222
223    /**
224     * Styles element with animation class. New class name is generated to customize count, duration and delay.
225     * Style is removed on animation end (if not set to infinite).
226     *
227     * @param element Element to apply animation to.
228     * @param animation Type of animation to apply.
229     * @param count Number of animation repeats. 0 disables animation, any negative value set repeats to infinite.
230     * @param duration Animation duration in ms. 0 disables animation, any negative value keeps default of original animation.
231     * @param delay Delay before starting the animation loop in ms. Value <= 0 means no delay.
232     * @param <T> Any object extending UIObject class (typically Widget).
233     * @return Animation's CSS class name, which can be removed to stop animation.
234     */
235    private static <T extends UIObject> String styleElement(Element element, String animation, int count, int duration, int delay) {
236
237        if (!usedStyles.contains(animation + " " + getStyleNameFromAnimation(animation,count,duration,delay))) {
238
239            String styleSheet = "." + getStyleNameFromAnimation(animation, count, duration, delay) + " {";
240
241            // 1 is default, 0 disable animation, any negative -> infinite loop
242            if (count >= 0) {
243
244                styleSheet += "-webkit-animation-iteration-count: " + count + ";" +
245                        "-moz-animation-iteration-count:" + count + ";" +
246                        "-ms-animation-iteration-count:" + count + ";" +
247                        "-o-animation-iteration-count:" + count + ";" +
248                        "animation-iteration-count:" + count + ";";
249
250            } else {
251
252                styleSheet += "-webkit-animation-iteration-count: infinite;" +
253                        "-moz-animation-iteration-count: infinite;" +
254                        "-ms-animation-iteration-count: infinite;" +
255                        "-o-animation-iteration-count: infinite;" +
256                        "animation-iteration-count: infinite;";
257
258            }
259
260            // if not default (any negative -> use default)
261            if (duration >= 0) {
262
263                styleSheet += "-webkit-animation-duration: " + duration + "ms;" +
264                        "-moz-animation-duration:" + duration + "ms;" +
265                        "-ms-animation-duration:" + duration + "ms;" +
266                        "-o-animation-duration:" + duration + "ms;" +
267                        "animation-duration:" + duration + "ms;";
268
269            }
270
271            // if not default (any negative -> use default)
272            if (delay >= 0) {
273
274                styleSheet += "-webkit-animation-delay: " + delay + "ms;" +
275                        "-moz-animation-delay:" + delay + "ms;" +
276                        "-ms-animation-delay:" + delay + "ms;" +
277                        "-o-animation-delay:" + delay + "ms;" +
278                        "animation-delay:" + delay + "ms;";
279
280            }
281
282            styleSheet += "}";
283
284            // inject new style
285            StyleInjector.injectAtEnd(styleSheet, true);
286
287            usedStyles.add(animation + " " + getStyleNameFromAnimation(animation, count, duration, delay));
288
289        }
290
291        // start animation
292        element.addClassName(animation + " " + getStyleNameFromAnimation(animation,count,duration,delay));
293
294        // remove animation on end so we could start it again
295        // removeAnimationOnEnd(element, animation + " anim-"+count+"-"+duration+"-"+delay);
296
297        return animation + " " + getStyleNameFromAnimation(animation,count,duration,delay);
298
299    }
300
301    /**
302     * Removes custom animation class on animation end.
303     *
304     * @param widget Element to remove style from.
305     * @param animation Animation CSS class to remove.
306     */
307    public static final <T extends UIObject> void removeAnimationOnEnd(final T widget, final String animation) {
308        if (widget != null && animation != null) {
309            removeAnimationOnEnd(widget.getElement(), animation);
310        }
311    }
312
313    /**
314     * Removes custom animation class on animation end.
315     *
316     * @param element Element to remove style from.
317     * @param animation Animation CSS class to remove.
318     */
319    private static final native void removeAnimationOnEnd(Element element, String animation) /*-{
320
321        var elem = $wnd.jQuery(element);
322        elem.one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', { elem: elem }, function(event) {
323            event.data.elem.removeClass(animation);
324        });
325
326    }-*/;
327
328    /**
329     * Removes custom animation class and stops animation.
330     *
331     * @param widget Element to remove style from.
332     * @param animation Animation CSS class to remove.
333     */
334    public static final <T extends UIObject> void stopAnimation(final T widget, final String animation){
335        if (widget != null && animation != null) {
336            stopAnimation(widget.getElement(), animation);
337        }
338    }
339
340    /**
341     * Removes custom animation class and stops animation.
342     *
343     * @param element Element to remove style from.
344     * @param animation Animation CSS class to remove.
345     */
346    private static final native void stopAnimation(Element element, String animation) /*-{
347        $wnd.jQuery(element).removeClass(animation);
348    }-*/;
349
350    /**
351     * Helper method, which returns unique class name for combination of animation and it's settings.
352     *
353     * @param animation Animation CSS class name.
354     * @param count Number of animation repeats.
355     * @param duration Animation duration in ms.
356     * @param delay Delay before starting the animation loop in ms.
357     * @return String representation of class name like "animation-count-duration-delay".
358     */
359    private static String getStyleNameFromAnimation(final String animation, int count, int duration, int delay) {
360
361        // fix input
362        if (count < 0) count = -1;
363        if (duration < 0) duration = -1;
364        if (delay < 0) delay = -1;
365
366        String styleName = "";
367
368        // for all valid animations
369        if (animation != null && !animation.isEmpty() && animation.split(" ").length > 1) {
370
371            styleName += animation.split(" ")[1]+"-"+count+"-"+duration+"-"+delay;
372
373        // for all custom animations
374        } else if (animation != null && !animation.isEmpty() && animation.split(" ").length == 1) {
375
376            styleName += animation+"-"+count+"-"+duration+"-"+delay;
377
378        }
379
380        return styleName;
381
382    }
383
384}