[gnome-shell/wip/swarm: 2/10] appDisplay: Animate folder view items



commit 512fe0bb6e8c18c2146798a1fcc2b3fc67ad2c54
Author: Carlos Soriano <carlos soriano89 gmail com>
Date:   Tue Jun 17 12:47:00 2014 +0200

    appDisplay: Animate folder view items
    
    Add a new animation  to folder view based on designers mockups that
    emulates pulsating icons.
    The code on iconGrid is though to work well for the upcoming patches to
    animate AllView and FrequentView.

 js/ui/appDisplay.js |   21 +++++++++-
 js/ui/iconGrid.js   |  105 +++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 124 insertions(+), 2 deletions(-)
---
diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
index 1ba0937..415d248 100644
--- a/js/ui/appDisplay.js
+++ b/js/ui/appDisplay.js
@@ -967,6 +967,10 @@ const FolderView = new Lang.Class({
         Util.ensureActorVisibleInScrollView(this.actor, actor);
     },
 
+    animate: function(animationType, animationDirection) {
+        this._grid.animate(animationType, animationDirection);
+    },
+
     createFolderIcon: function(size) {
         let layout = new Clutter.GridLayout();
         let icon = new St.Widget({ layout_manager: layout,
@@ -1342,8 +1346,20 @@ const AppFolderPopup = new Lang.Class({
         this.actor.show();
 
         this._boxPointer.setArrowActor(this._source.actor);
+        // We need to hide the icons of the view until the boxpointer animation
+        // is completed so we can animate the icons after as we like withouth
+        // showing them while boxpointer is animating.
+        this._view.actor.opacity = 0;
         this._boxPointer.show(BoxPointer.PopupAnimation.FADE |
-                              BoxPointer.PopupAnimation.SLIDE);
+                              BoxPointer.PopupAnimation.SLIDE,
+                              Lang.bind(this,
+            function() {
+                // Restore the view opacity, so now we show the icons and animate
+                // them
+                this._view.actor.opacity = 255;
+                this._view.animate(IconGrid.AnimationType.PULSE,
+                                   IconGrid.AnimationDirection.IN);
+            }));
 
         this.emit('open-state-changed', true);
     },
@@ -1355,7 +1371,8 @@ const AppFolderPopup = new Lang.Class({
         this._grabHelper.ungrab({ actor: this.actor });
 
         this._boxPointer.hide(BoxPointer.PopupAnimation.FADE |
-                              BoxPointer.PopupAnimation.SLIDE);
+                              BoxPointer.PopupAnimation.SLIDE,
+                              this._view.animateOut);
         this._isOpen = false;
         this.emit('open-state-changed', false);
     },
diff --git a/js/ui/iconGrid.js b/js/ui/iconGrid.js
index 9cf9f2f..15f5e57 100644
--- a/js/ui/iconGrid.js
+++ b/js/ui/iconGrid.js
@@ -10,12 +10,26 @@ const St = imports.gi.St;
 const Lang = imports.lang;
 const Params = imports.misc.params;
 const Tweener = imports.ui.tweener;
+const Main = imports.ui.main;
 
 const ICON_SIZE = 96;
 const MIN_ICON_SIZE = 16;
 
 const EXTRA_SPACE_ANIMATION_TIME = 0.25;
 
+const ANIMATION_TIME_IN = 0.350;
+const ANIMATION_MAX_DELAY_FOR_ITEM = 2/3 * ANIMATION_TIME_IN;
+
+const ANIMATION_BOUNCE_ICON_SCALE = 1.1;
+
+const AnimationType = {
+    PULSE: 0
+};
+
+const AnimationDirection = {
+    IN: 0
+};
+
 const BaseIcon = new Lang.Class({
     Name: 'BaseIcon',
 
@@ -338,6 +352,97 @@ const IconGrid = new Lang.Class({
         }
     },
 
+    /**
+     * Intended to be override by subclasses if they need a diferent
+     * set of items to be animated.
+     */
+    _getChildrenToAnimate: function() {
+        return this._getVisibleChildren();
+    },
+
+    animate: function(animationType, animationDirection) {
+        let actors = this._getChildrenToAnimate();
+        if (this._animating || actors.length == 0)
+            return;
+        this._animating = true;
+
+        for (let index = 0; index < actors.length; index++)
+            actors[index].opacity = 0;
+
+        // Hack:
+        // We need to delay the animation until the icons have the right
+        // size acording to updateChildrenScale. updateChildrenScale is
+        // normally called from adaptToSize, which is called inside a
+        // allocation, so it needs to be called inside a Meta.later_add
+        // BEFORE_REDRAW. That causes that in animate() we won't be sure
+        // if the items have the right size or not.
+        //
+        // To avoid that we need two things. First, expect the caller to
+        // animate() to call it inside a BEFORE_REDRAW. But that's not
+        // enough, since both animate() and updateChildrenScale() are
+        // called inside a BEFORE_REDRAW and we don't know which one
+        // will be executed first. So we need to make sure
+        // updateChildrenScale() is called first. To achieve that, we
+        // need to wait another frame here, calling the animation inside
+        // another BEFORE_REDRAW.
+        let delayedAnimation = Lang.bind(this, function() {
+            switch (animationType) {
+                case AnimationType.PULSE:
+                    this._animatePulse(actors, animationDirection);
+                    break;
+                default:
+                    log("animation doesn't exist. Icongrid won\'t work");
+            }
+        });
+        Meta.later_add(Meta.LaterType.BEFORE_REDRAW, delayedAnimation);
+    },
+
+    _animatePulse: function(actors, animationDirection) {
+        for (let index = 0; index < actors.length; index++) {
+            let delay = index / actors.length * ANIMATION_MAX_DELAY_FOR_ITEM;
+            let [originalX, originalY] = actors[index].get_transformed_position();
+            let [originalWidth, originalHeight] = actors[index].get_transformed_size();
+
+            let actorClone = new Clutter.Clone({ source: actors[index],
+                                                reactive: false });
+            Main.uiGroup.add_actor(actorClone);
+
+            actorClone.reactive = false;
+            actorClone.set_position(originalX, originalY);
+            actorClone.set_scale(0, 0);
+            actorClone.set_pivot_point(0.5, 0.5);
+            let [width, height] = actors[index].get_transformed_size();
+            actorClone.set_size(width, height);
+
+            // Defeat onComplete anonymous function closure
+            let actor= actors[index];
+            let isLastActor= index == actors.length - 1;
+            Tweener.addTween(actorClone,
+                            { time: ANIMATION_TIME_IN / 4,
+                              transition: 'easeInOutQuad',
+                              delay: delay,
+                              scale_x: ANIMATION_BOUNCE_ICON_SCALE,
+                              scale_y: ANIMATION_BOUNCE_ICON_SCALE,
+                              onComplete: Lang.bind(this, function() {
+                                  Tweener.addTween(actorClone,
+                                                   { time: ANIMATION_TIME_IN - ANIMATION_TIME_IN / 4,
+                                                     transition: 'easeInOutQuad',
+                                                     scale_x: 1,
+                                                     scale_y: 1,
+                                                     onComplete: Lang.bind(this, function() {
+                                                        if (isLastActor) {
+                                                            this._animating = false;
+                                                            this.emit('animation-done');
+                                                        }
+                                                        actor.opacity = 255;
+                                                        actorClone.destroy();
+                                                    })
+                                                   });
+                              })
+                            });
+        }
+    },
+
     _calculateChildBox: function(child, x, y, box) {
         let [childMinWidth, childMinHeight, childNaturalWidth, childNaturalHeight] =
              child.get_preferred_size();


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]