001    package 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    
023    import com.google.gwt.core.client.Scheduler;
024    import com.google.gwt.dom.client.Element;
025    import com.google.gwt.dom.client.StyleInjector;
026    import com.google.gwt.user.client.ui.UIObject;
027    import org.gwtbootstrap3.extras.animate.client.ui.constants.Animation;
028    
029    import java.util.ArrayList;
030    
031    /**
032     * Utility class to dynamically animate objects using CSS animations.
033     *
034     * @author Pavel Zlámal
035     */
036    public 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    }