[gnome-shell/wip/swarm: 8/10] Prototype IconGrid animations



commit 010f85bed53c05d64811ffb8f05b56c7d1e9d7ef
Author: Carlos Soriano <carlos soriano89 gmail com>
Date:   Sun Mar 16 23:21:28 2014 +0100

    Prototype IconGrid animations

 js/ui/appDisplay.js   |  119 +++++++++++++++-
 js/ui/iconGrid.js     |  354 +++++++++++++++++++++++++++++++++++++++++++++++--
 js/ui/viewSelector.js |   64 ++++++---
 3 files changed, 498 insertions(+), 39 deletions(-)
---
diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
index e43a679..eee88bf 100644
--- a/js/ui/appDisplay.js
+++ b/js/ui/appDisplay.js
@@ -93,7 +93,8 @@ const BaseAppView = new Lang.Class({
                                                 minRows: MIN_ROWS,
                                                 minColumns: MIN_COLUMNS,
                                                 fillParent: false,
-                                                padWithSpacing: true });
+                                                padWithSpacing: true,
+                                                animation: IconGrid.ANIMATION_NONE });
         params = Params.parse(params, { usePagination: false });
 
         if(params.usePagination)
@@ -142,6 +143,10 @@ const BaseAppView = new Lang.Class({
     _compareItems: function(a, b) {
         return a.name.localeCompare(b.name);
     },
+    
+    animate: function(animationType, animationDirection, params) {
+        throw new Error('Not implemented');
+    },
 
     loadGrid: function() {
         this._allItems.sort(this._compareItems);
@@ -273,7 +278,7 @@ const AllView = new Lang.Class({
     Extends: BaseAppView,
 
     _init: function() {
-        this.parent({ usePagination: true }, null);
+        this.parent({ usePagination: true }, { animation: IconGrid.ANIMATION_SWARM_FAR_FIRST });
         this._scrollView = new St.ScrollView({ style_class: 'all-apps',
                                                x_expand: true,
                                                y_expand: true,
@@ -435,12 +440,50 @@ const AllView = new Lang.Class({
         this.loadGrid();
         this._refilterApps();
     },
+    
+    animate: function(animationDirection) {
+        let dashPosition = Main.overview._dash._showAppsIcon.get_transformed_position();
+        let dashSize = Main.overview._dash._showAppsIcon.get_transformed_size();
+        if (animationDirection == IconGrid.ANIMATION_DIRECTION_IN) {
+            let toAnimate = this._grid.actor.connect('notify::allocation', Lang.bind(this,
+                function() {
+                    if (this._grid.actor.mapped) {
+                        this._grid.actor.disconnect(toAnimate);
+                        this._grid.animate(IconGrid.ANIMATION_TYPE_SWARM_SPRING,
+                                           animationDirection,
+                                           { sourcePosition: dashPosition,
+                                             sourceSize: dashSize,
+                                             page: this._currentPage });
+                    }
+                }));
+        } else {
+            if (this._displayingPopup && this._currentPopup) {
+                this._currentPopup.popdown();
+                let spaceClosedId = this._grid.connect('space-closed', Lang.bind(this,
+                    function() {
+                        this._grid.disconnect(spaceClosedId);
+                        this._grid.animate(IconGrid.ANIMATION_TYPE_SWARM_SPRING,
+                                           animationDirection,
+                                           { sourcePosition: dashPosition,
+                                             sourceSize: dashSize,
+                                             page: this._currentPage });
+                    }));
+            } else {
+                this._grid.animate(IconGrid.ANIMATION_TYPE_SWARM_SPRING,
+                                   animationDirection,
+                                   { sourcePosition: dashPosition,
+                                     sourceSize: dashSize,
+                                     page: this._currentPage });
+            }
+        }
+    },
 
     getCurrentPageY: function() {
         return this._grid.getPageY(this._currentPage);
     },
 
     goToPage: function(pageNumber) {
+        log("got to page " + pageNumber);
         pageNumber = clamp(pageNumber, 0, this._grid.nPages() - 1);
 
         if (this._currentPage == pageNumber && this._displayingPopup && this._currentPopup)
@@ -500,9 +543,10 @@ const AllView = new Lang.Class({
     },
 
     _onScroll: function(actor, event) {
+        log("current page XXX " + this._currentPage);
         if (this._displayingPopup)
             return Clutter.EVENT_STOP;
-
+        log("current page " + this._currentPage);
         let direction = event.get_scroll_direction();
         if (direction == Clutter.ScrollDirection.UP)
             this.goToPage(this._currentPage - 1);
@@ -632,7 +676,7 @@ const FrequentView = new Lang.Class({
     Extends: BaseAppView,
 
     _init: function() {
-        this.parent(null, { fillParent: true });
+        this.parent(null, { fillParent: true, animation: IconGrid.ANIMATION_SWARM_FAR_FIRST });
         this.actor = new St.Widget({ style_class: 'frequent-apps',
                                      layout_manager: new Clutter.BinLayout(),
                                      x_expand: true, y_expand: true });
@@ -676,6 +720,28 @@ const FrequentView = new Lang.Class({
             this._grid.addItem(appIcon, -1);
         }
     },
+    
+    animate: function(animationDirection) {
+        let dashPosition = Main.overview._dash._showAppsIcon.get_transformed_position();
+        let dashSize = Main.overview._dash._showAppsIcon.get_transformed_size();
+        if (animationDirection == IconGrid.ANIMATION_DIRECTION_IN) {
+            let toAnimate = this._grid.actor.connect('notify::allocation', Lang.bind(this,
+                function() {
+                    if (this._grid.actor.mapped) {
+                        this._grid.actor.disconnect(toAnimate);
+                        this._grid.animate(IconGrid.ANIMATION_TYPE_SWARM_SPRING,
+                                           animationDirection,
+                                           { sourcePosition: dashPosition,
+                                             sourceSize: dashSize });
+                    }
+                }));
+        } else {
+            this._grid.animate(IconGrid.ANIMATION_TYPE_SWARM_SPRING,
+                                           animationDirection,
+                                           { sourcePosition: dashPosition,
+                                             sourceSize: dashSize });
+        }
+    },
 
     // Called before allocation to calculate dynamic spacing
     adaptToSize: function(width, height) {
@@ -688,6 +754,10 @@ const FrequentView = new Lang.Class({
         let availWidth = box.x2 - box.x1;
         let availHeight = box.y2 - box.y1;
         this._grid.adaptToSize(availWidth, availHeight);
+    },
+    
+    swarmAnimation: function() {
+        //this._grid.swarmAnimation();
     }
 });
 
@@ -794,6 +864,12 @@ const AppDisplay = new Lang.Class({
         this._showView(initialView);
         this._updateFrequentVisibility();
     },
+    
+    animate: function(animationDirection) {
+        let view = this._views[global.settings.get_uint('app-picker-view')].view;
+        log("animationDirection " + animationDirection);
+        view.animate(animationDirection);
+    },
 
     _showView: function(activeIndex) {
         for (let i = 0; i < this._views.length; i++) {
@@ -924,7 +1000,7 @@ const FolderView = new Lang.Class({
     Extends: BaseAppView,
 
     _init: function() {
-        this.parent(null, null);
+        this.parent(null, { animation: IconGrid.ANIMATION_TYPE_APPEAR });
         // If it not expand, the parent doesn't take into account its preferred_width when allocating
         // the second time it allocates, so we apply the "Standard hack for ClutterBinLayout"
         this._grid.actor.x_expand = true;
@@ -943,6 +1019,10 @@ const FolderView = new Lang.Class({
     _keyFocusIn: function(actor) {
         Util.ensureActorVisibleInScrollView(this.actor, actor);
     },
+    
+    animate: function(animationType, animationDirection, params) {
+        this._grid.animate(animationType, animationDirection, params);
+    },
 
     createFolderIcon: function(size) {
         let layout = new Clutter.TableLayout();
@@ -1034,6 +1114,7 @@ const FolderIcon = new Lang.Class({
 
     _init: function(id, path, parentView) {
         this.id = id;
+        this._path = path;
         this._parentView = parentView;
 
         this._folder = new Gio.Settings({ schema_id: 'org.gnome.desktop.app-folders.folder',
@@ -1199,6 +1280,10 @@ const FolderIcon = new Lang.Class({
             this.view.adaptToSize(width, height);
         this._popupInvalidated = true;
     },
+    
+    clone: function() {
+        return new FolderIcon(this.id, this._path, this._parentView);
+    }
 });
 Signals.addSignalMethods(FolderIcon.prototype);
 
@@ -1277,8 +1362,22 @@ 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.ANIMATION_TYPE_APPEAR,
+                                     IconGrid.ANIMATION_DIRECTION_IN,
+                                     { sourcePosition: this._source.actor.get_transformed_position(),
+                                       sourceSize: this._source.actor.get_transformed_size() });
+            }));
 
         this.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
 
@@ -1290,8 +1389,7 @@ const AppFolderPopup = new Lang.Class({
         if (!this._isOpen)
             return;
 
-        this._boxPointer.hide(BoxPointer.PopupAnimation.FADE |
-                              BoxPointer.PopupAnimation.SLIDE);
+        this._boxPointer.hide(BoxPointer.PopupAnimation.FADE, this._view.animateOut );
         this._isOpen = false;
         this.emit('open-state-changed', false);
     },
@@ -1335,6 +1433,7 @@ const AppIcon = new Lang.Class({
 
         iconParams['createIcon'] = Lang.bind(this, this._createIcon);
         iconParams['setSizeManually'] = true;
+        this._iconParams = iconParams;
         this.icon = new IconGrid.BaseIcon(app.get_name(), iconParams);
         this.actor.set_child(this.icon.actor);
 
@@ -1510,6 +1609,10 @@ const AppIcon = new Lang.Class({
     shouldShowTooltip: function() {
         return this.actor.hover && (!this._menu || !this._menu.isOpen);
     },
+    
+    clone: function() {
+        return new AppIcon(this.app, this._iconParams);
+    }
 });
 Signals.addSignalMethods(AppIcon.prototype);
 
diff --git a/js/ui/iconGrid.js b/js/ui/iconGrid.js
index 16c58e9..8422433 100644
--- a/js/ui/iconGrid.js
+++ b/js/ui/iconGrid.js
@@ -10,12 +10,30 @@ 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.400;
+const ANIMATION_TIME_OUT = 0.200;
+const ANIMATION_MAX_DELAY_FOR_ITEM = 0.250;
+const ANIMATION_MAX_DELAY_OUT_FOR_ITEM = 0.125;
+const ANIMATION_FADE_IN_TIME_FOR_ITEM = 0.100;
+
+const ANIMATION_TIME_TYPE_APPEAR = 0.400;
+const ANIMATION_APPEAR_ICON_SCALE = 1.1;
+
+const ANIMATION_TYPE_NONE = 0;
+const ANIMATION_TYPE_APPEAR = 1;
+const ANIMATION_TYPE_SWARM_RANDOM = 2;
+const ANIMATION_TYPE_SWARM_SPRING = 3;
+
+const ANIMATION_DIRECTION_OUT = 0;
+const ANIMATION_DIRECTION_IN = 1;
+
 const BaseIcon = new Lang.Class({
     Name: 'BaseIcon',
 
@@ -23,6 +41,8 @@ const BaseIcon = new Lang.Class({
         params = Params.parse(params, { createIcon: null,
                                         setSizeManually: false,
                                         showLabel: true });
+        this.labelForClone = label;
+        this.paramsForClone = params;
 
         let styleClass = 'overview-icon';
         if (params.showLabel)
@@ -186,7 +206,8 @@ const IconGrid = new Lang.Class({
                                         minColumns: 1,
                                         fillParent: false,
                                         xAlign: St.Align.MIDDLE,
-                                        padWithSpacing: false });
+                                        padWithSpacing: false,
+                                        animation: ANIMATION_TYPE_NONE });
         this._rowLimit = params.rowLimit;
         this._colLimit = params.columnLimit;
         this._minRows = params.minRows;
@@ -194,6 +215,7 @@ const IconGrid = new Lang.Class({
         this._xAlign = params.xAlign;
         this._fillParent = params.fillParent;
         this._padWithSpacing = params.padWithSpacing;
+        this._animationType = params.animation;
 
         this.topPadding = 0;
         this.bottomPadding = 0;
@@ -216,6 +238,9 @@ const IconGrid = new Lang.Class({
         this._grid.connect('allocate', Lang.bind(this, this._allocate));
         this._grid.connect('actor-added', Lang.bind(this, this._childAdded));
         this._grid.connect('actor-removed', Lang.bind(this, this._childRemoved));
+
+        this._paintedItems = [];
+        this._animating = false;
     },
 
     _keyFocusIn: function(actor) {
@@ -256,6 +281,17 @@ const IconGrid = new Lang.Class({
         return children;
     },
 
+    _getVisibleItems: function() {;
+        let items = this._items.filter(function(item) {
+            return item.actor.visible;
+        });
+        return items;
+    },
+
+    _getPaintedItems: function() {;
+        return this._paintedItems;
+    },
+
     _getPreferredHeight: function (grid, forWidth, alloc) {
         if (this._fillParent)
             // Ignore all size requests of children and request a size of 0;
@@ -290,7 +326,7 @@ const IconGrid = new Lang.Class({
             box = this._grid.get_theme_node().get_content_box(gridBox);
         }
 
-        let children = this._getVisibleChildren();
+        let items = this._getVisibleItems();
         let availWidth = box.x2 - box.x1;
         let availHeight = box.y2 - box.y1;
         let spacing = this._getSpacing();
@@ -312,15 +348,18 @@ const IconGrid = new Lang.Class({
         let y = box.y1 + this.topPadding;
         let columnIndex = 0;
         let rowIndex = 0;
-        for (let i = 0; i < children.length; i++) {
-            let childBox = this._calculateChildBox(children[i], x, y, box);
+        this._paintedItems = [];
+
+        for (let i = 0; i < items.length; i++) {
+            let childBox = this._calculateChildBox(items[i].actor, x, y, box);
 
             if (this._rowLimit && rowIndex >= this._rowLimit ||
                 this._fillParent && childBox.y2 > availHeight - this.bottomPadding) {
-                this._grid.set_skip_paint(children[i], true);
+                this._grid.set_skip_paint(items[i].actor, true);
             } else {
-                children[i].allocate(childBox, flags);
-                this._grid.set_skip_paint(children[i], false);
+                items[i].actor.allocate(childBox, flags);
+                this._grid.set_skip_paint(items[i].actor, false);
+                this._paintedItems.push(items[i]);
             }
 
             columnIndex++;
@@ -338,6 +377,269 @@ const IconGrid = new Lang.Class({
         }
     },
 
+    /**
+     * Intended to be override by subclasses if they need a diferent
+     * set of items to be animated.
+     */
+    animate: function(animationType, animationDirection, params) {
+        params = Params.parse(params, { sourcePosition: null,
+                                        sourceSize: null });
+
+        // Restore opacity from the out animation of all elements. We assume the
+        // items need full opacity now
+        for (let index = 0; index < this._items.length; index++)
+            this._items[index].actor.opacity = 255;
+
+        this._animateReal(this._paintedItems, animationType, animationDirection, params);
+    },
+
+    _animateReal: function(items, animationType, animationDirection, params) {
+        if (this._animating || items.length == 0)
+            return;
+        this._animating = true;
+
+        switch (animationType) {
+            case ANIMATION_TYPE_SWARM_SPRING:
+                this._animateSwarmSpring(items, animationDirection, params.sourcePosition, 
params.sourceSize);
+                break;
+            case ANIMATION_TYPE_SWARM_RANDOM:
+                this._animateSwarmRandom(items, animationDirection, params.sourcePosition, 
params.sourceSize);
+                break;
+            case ANIMATION_TYPE_APPEAR:
+                this._animateAppear(items, animationDirection);
+                break;
+            default:
+                break;
+        }
+    },
+
+    _animateAppear: function(items, animationDirection) {
+        for (let index = 0; index < items.length; index++) {
+            // FIXME? Seems that putting the items at opacity 0
+            // for animating seems like it doesn't belongs here.
+            // But works well.
+            items[index].actor.opacity = 0;
+            let delay = index / items.length * ANIMATION_MAX_DELAY_FOR_ITEM;
+
+            let [originalX, originalY] = items[index].actor.get_transformed_position();
+            let [originalWidth, originalHeight] = items[index].actor.get_transformed_size();
+
+            let itemClone = items[index].clone();
+            Main.uiGroup.add_actor(itemClone.actor);
+
+            itemClone.actor.reactive = false;
+            itemClone.actor.set_position(originalX, originalY);
+            itemClone.actor.set_scale(0, 0);
+            itemClone.actor.set_pivot_point(0.5, 0.5);
+            // Force to use the current size, so the label and the container
+            // of the actor doesnn't resize while the icon texture is 
+            // animating. That avoids to see the label wrapping the text and
+            // also avoids the container to be positioned top left while animating instead
+            // of center.
+            itemClone.actor.set_size(originalWidth, originalHeight);
+
+            let originalIconSize = itemClone.icon.iconSize;
+            // Defeat onComplete anonymous function closure
+            let item = items[index];
+            let isLastItem = index == items.length - 1;
+            Tweener.addTween(itemClone.actor,
+                            { time: ANIMATION_TIME_TYPE_APPEAR / 4,
+                              transition: 'easeInOutQuad',
+                              delay: delay,
+                              scale_x: ANIMATION_APPEAR_ICON_SCALE,
+                              scale_y: ANIMATION_APPEAR_ICON_SCALE,
+                              onComplete: Lang.bind(this, function() {
+                                  Tweener.addTween(itemClone.actor,
+                                                   { time: ANIMATION_TIME_TYPE_APPEAR - 
ANIMATION_TIME_TYPE_APPEAR / 4,
+                                                     transition: 'easeInOutQuad',
+                                                     scale_x: 1,
+                                                     scale_y: 1,
+                                                     onComplete: Lang.bind(this, function() {
+                                                        if (isLastItem)
+                                                            this._animating = false;
+                                                        item.actor.opacity = 255;
+                                                        itemClone.actor.destroy();
+                                                    })
+                                                   });
+                              })
+                            });
+        }
+    },
+
+            /*
+            _animateSwarmSpring
+            // Implicit animations implementation.
+            itemClone.actor.save_easing_state();
+            itemClone.icon._iconBin.save_easing_state();
+
+            itemClone.actor.set_easing_duration(ANIMATION_TIME_IN * 3);
+            itemClone.actor.set_easing_mode(Clutter.AnimationMode.EASE_OUT_QUAD);
+            itemClone.actor.set_easing_delay(delay);
+            itemClone.icon._iconBin.set_easing_delay(delay);
+            itemClone.actor.set_position(originalX, originalY);
+            itemClone.actor.set_opacity(255);
+
+            if (this._animationType == ANIMATION_TYPE_APPEAR) {
+                itemClone.icon._iconBin.set_easing_mode(Clutter.AnimationMode.EASE_OUT_BACK);
+                itemClone.actor.set_easing_mode(Clutter.AnimationMode.EASE_OUT_BACK);
+            } else {
+                itemClone.icon._iconBin.set_easing_mode(Clutter.AnimationMode.EASE_OUT_QUAD);
+            }
+            itemClone.icon._iconBin.set_size(300-20, 300-20);
+            itemClone.actor.set_size(300, 300);
+
+            itemClone.actor.restore_easing_state();
+            itemClone.icon._iconBin.restore_easing_state();
+
+            itemClone.actor.connect('transition-stopped::', Lang.bind(this,
+                function(a, n, completed) {
+                    if (isLastItem)
+                        this._animating = false;
+                    item.actor.opacity = 255;
+                    itemClone.actor.destroy();                
+               }));
+            */
+
+    _animateSwarmSpring: function(items, animationDirection, sourcePosition, sourceSize) {
+        log("swarmSpring");
+        let distances = items.map(Lang.bind(this, function(item) {
+            return this._distance(item.actor.get_transformed_position(), sourcePosition);
+        }));
+        let maxDist = Math.max.apply(Math, distances);
+        let minDist = Math.min.apply(Math, distances);
+        let normalization = maxDist - minDist;
+
+        for (let index = 0; index < items.length; index++) {
+            // FIXME? Seems that putting the items at opacity 0
+            // for animating seems like it doesn't belongs here.
+            // But works well.
+            items[index].actor.opacity = 0;
+
+            let itemClone = items[index].clone();
+            Main.uiGroup.add_actor(itemClone.actor);
+
+            itemClone.actor.reactive = false;
+            let scale = sourceSize[0] / items[index].actor.get_transformed_size()[0];
+            itemClone.actor.set_pivot_point(0.5, 0.5);
+
+            // Defeat onComplete anonymous function closure
+            let item = items[index];
+            let isLastItem = index == items.length - 1;
+            let itms = items;
+
+            let movementParams, fadeParams;
+            if (animationDirection == ANIMATION_DIRECTION_IN) {
+                itemClone.actor.set_position(sourcePosition[0], sourcePosition[1]);
+                itemClone.actor.opacity = 0;
+                itemClone.actor.set_scale(scale, scale);
+                let delay = (1 - (distances[index] - minDist) / normalization) * 
ANIMATION_MAX_DELAY_FOR_ITEM;
+                let [finalX, finalY]  = items[index].actor.get_transformed_position();
+                movementParams = { time: ANIMATION_TIME_IN,
+                                   transition: 'easeInOutQuad',
+                                   delay: delay,
+                                   x: finalX,
+                                   y: finalY,
+                                   scale_x: 1,
+                                   scale_y: 1,
+                                   onComplete: Lang.bind(this, function() {
+                                       if (isLastItem)
+                                           this._animating = false;
+                                       item.actor.opacity = 255;
+                                       itemClone.actor.destroy();
+                                   })};
+                fadeParams = { time: ANIMATION_FADE_IN_TIME_FOR_ITEM,
+                               transition: 'easeInOutQuad',
+                               delay: delay,
+                               opacity: 255 };
+            } else {
+                let [startX, startY]  = items[index].actor.get_transformed_position();
+                itemClone.actor.set_position(startX, startY);
+
+                let delay = (distances[index] - minDist) / normalization * ANIMATION_MAX_DELAY_OUT_FOR_ITEM;
+                let [finalX, finalY] = [sourcePosition[0], sourcePosition[1]];
+                movementParams = { time: ANIMATION_TIME_OUT,
+                                   transition: 'easeInOutQuad',
+                                   delay: delay,
+                                   x: finalX,
+                                   y: finalY,
+                                   scale_x: scale,
+                                   scale_y: scale,
+                                   onComplete: Lang.bind(this, function() {
+                                       if (isLastItem)
+                                           this._animating = false;
+                                       itemClone.actor.destroy();
+                                   })};
+                fadeParams = { time: ANIMATION_FADE_IN_TIME_FOR_ITEM,
+                               transition: 'easeInOutQuad',
+                               delay: ANIMATION_TIME_OUT + delay - ANIMATION_FADE_IN_TIME_FOR_ITEM,
+                               opacity: 0 };
+            }
+
+
+            Tweener.addTween(itemClone.actor, movementParams);
+            Tweener.addTween(itemClone.actor, fadeParams);
+        }
+    },
+
+    _animateSwarmRandom: function(item, animationDirection, startPosition, startSize) {
+        let distances = items.map(Lang.bind(this, function(item) {
+            return this._distance(item.actor.get_transformed_position(), startPosition);
+        }));
+        let maxDist = Math.max.apply(Math, distances);
+        let minDist = Math.min.apply(Math, distances);
+        let normalization = maxDist - minDist;
+
+        for (let index = 0; index < items.length; index++) {
+            // FIXME? Seems that putting the items at opacity 0
+            // for animating seems like it doesn't belongs here.
+            // But works well.
+            items[index].actor.opacity = 0;
+            delay = (1 - (distances[index] - minDist) / normalization) * ANIMATION_MAX_DELAY_FOR_ITEM;
+
+            let [finalX, finalY] = items[index].actor.get_transformed_position();
+
+            let itemClone = items[index].clone();
+            Main.uiGroup.add_actor(itemClone.actor);
+
+            itemClone.actor.reactive = false;
+            //itemClone.actor.set_size(startSize, startSize);
+            itemClone.actor.set_position(startPosition[0], startPosition[1]);
+            itemClone.actor.opacity = 0;
+
+            // Defeat onComplete anonymous function closure
+            let item = items[index];
+            let isLastItem = index == items.length - 1;
+            Tweener.addTween(itemClone.actor,
+                             { time: ANIMATION_TIME_IN,
+                               transition: 'easeInOutQuad',
+                               delay: delay,
+                               x: finalX,
+                               y: finalY,
+                               onComplete: Lang.bind(this, function() {
+                                    if (isLastItem)
+                                        this._animating = false;
+                                    item.actor.opacity = 255;
+                                    itemClone.actor.destroy();
+                                })});
+
+            Tweener.addTween(itemClone.actor,
+                             { time: ANIMATION_FADE_IN_TIME_FOR_ITEM,
+                               transition: 'easeInOutQuad',
+                               delay: delay,
+                               opacity: 255 });
+        }
+    },
+
+    _distance: function(point1, point2) {
+        let x = point1[0] - point2[0];
+        let y = point1[1] - point2[1];
+        return Math.sqrt(x * x + y * y);
+    },
+
+    _translation: function(item, target) {
+        return [target[0] - item[0], target[1] - item[1]];
+    },
+
     _calculateChildBox: function(child, x, y, box) {
         let [childMinWidth, childMinHeight, childNaturalWidth, childNaturalHeight] =
              child.get_preferred_size();
@@ -593,10 +895,11 @@ const PaginatedIconGrid = new Lang.Class({
         let columnIndex = 0;
         let rowIndex = 0;
 
-        for (let i = 0; i < children.length; i++) {
-            let childBox = this._calculateChildBox(children[i], x, y, box);
-            children[i].allocate(childBox, flags);
-            this._grid.set_skip_paint(children[i], false);
+        let visibleItems = this._getVisibleItems();
+        for (let i = 0; i < visibleItems.length; i++) {
+            let childBox = this._calculateChildBox(visibleItems[i].actor, x, y, box);
+            visibleItems[i].actor.allocate(childBox, flags);
+            this._grid.set_skip_paint(visibleItems[i].actor, false);
 
             columnIndex++;
             if (columnIndex == nColumns) {
@@ -613,6 +916,19 @@ const PaginatedIconGrid = new Lang.Class({
         }
     },
 
+    animate: function(animationType, animationDirection, params) {
+        params = Params.parse(params, { sourcePosition: null,
+                                        sourceSize: null,
+                                        page: 0 });
+
+        // Restore opacity from the out animation of all elements. We assume the
+        // items need full opacity now
+        for (let index = 0; index < this._items.length; index++)
+            this._items[index].actor.opacity = 255;
+
+        this._animateReal(this._getItemsInPage(params.page), animationType, animationDirection, params);
+    },
+
     _computePages: function (availWidthPerPage, availHeightPerPage) {
         let [nColumns, usedWidth] = this._computeLayout(availWidthPerPage);
         let nRows;
@@ -668,6 +984,22 @@ const PaginatedIconGrid = new Lang.Class({
         return Math.floor(index / this._childrenPerPage);
     },
 
+    _getItemsInPage: function(pageNumber) {
+        let items = this._getVisibleItems();
+        log("getItemsPAge " + items.length);
+
+        let firstIndex = this._childrenPerPage * pageNumber;
+        let indexOffset = 0;
+        let itemsInPage = []
+
+        while (indexOffset < this._childrenPerPage && firstIndex + indexOffset < items.length) {
+               itemsInPage.push(items[firstIndex + indexOffset]);
+               indexOffset++;
+        }
+        log("intems in page " + itemsInPage.length + " chidren per page " + this._childrenPerPage);
+        return itemsInPage;
+    },
+
     /**
     * openExtraSpace:
     * @sourceItem: the item for which to create extra space
diff --git a/js/ui/viewSelector.js b/js/ui/viewSelector.js
index bc157dc..5674072 100644
--- a/js/ui/viewSelector.js
+++ b/js/ui/viewSelector.js
@@ -19,6 +19,7 @@ const Search = imports.ui.search;
 const ShellEntry = imports.ui.shellEntry;
 const Tweener = imports.ui.tweener;
 const WorkspacesView = imports.ui.workspacesView;
+const IconGrid = imports.ui.iconGrid;
 
 const SHELL_KEYBINDINGS_SCHEMA = 'org.gnome.shell.keybindings';
 
@@ -211,13 +212,19 @@ const ViewSelector = new Lang.Class({
             oldPage.hide();
 
         this.emit('page-empty');
-
-        this._activePage.show();
-        Tweener.addTween(this._activePage,
-            { opacity: 255,
-              time: OverviewControls.SIDE_CONTROLS_ANIMATION_TIME,
-              transition: 'easeOutQuad'
-            });
+        log("animation in " + this._activePage);
+        if (this._activePage == this._appsPage) {
+            this._activePage.opacity = 255;
+            this._activePage.show();
+            this.appDisplay.animate(IconGrid.ANIMATION_DIRECTION_IN);
+        } else {
+            this._activePage.show();
+            Tweener.addTween(this._activePage,
+                { opacity: 255,
+                  time: OverviewControls.SIDE_CONTROLS_ANIMATION_TIME,
+                  transition: 'easeOutQuad'
+                });
+        }
     },
 
     _showPage: function(page, noFade) {
@@ -227,19 +234,36 @@ const ViewSelector = new Lang.Class({
         let oldPage = this._activePage;
         this._activePage = page;
         this.emit('page-changed');
+        log("animation out " + oldPage);
+        if (oldPage && !noFade) {
+            if (oldPage == this._appsPage) {
+                this.appDisplay.animate(IconGrid.ANIMATION_DIRECTION_OUT);
+                let time = IconGrid.ANIMATION_TIME_OUT + IconGrid.ANIMATION_MAX_DELAY_OUT_FOR_ITEM * 
St.get_slow_down_factor();
+                Mainloop.timeout_add(time * 1000, Lang.bind(this, function() { this._fadePageIn(oldPage); 
return GLib.SOURCE_REMOVE; }));
+            } else {
+                Tweener.addTween(oldPage,
+                                 { opacity: 0,
+                                   time: OverviewControls.SIDE_CONTROLS_ANIMATION_TIME,
+                                   transition: 'easeOutQuad',
+                                   onComplete: Lang.bind(this,
+                                       function() {
+                                           this._fadePageIn(oldPage);
+                                       })
+                                 });
+            }
+        }
+        else {
+            if (oldPage == this._appsPage) {
+                this.appDisplay.animate(IconGrid.ANIMATION_DIRECTION_OUT);
+                let time = IconGrid.ANIMATION_TIME_OUT + IconGrid.ANIMATION_MAX_DELAY_OUT_FOR_ITEM * 
St.get_slow_down_factor();
+                Mainloop.timeout_add(time * 1000, Lang.bind(this, function() { this._fadePageIn(oldPage); 
return GLib.SOURCE_REMOVE; }));
+            } else {
+                this._fadePageIn(oldPage);
+            }
+        }
 
-        if (oldPage && !noFade)
-            Tweener.addTween(oldPage,
-                             { opacity: 0,
-                               time: OverviewControls.SIDE_CONTROLS_ANIMATION_TIME,
-                               transition: 'easeOutQuad',
-                               onComplete: Lang.bind(this,
-                                   function() {
-                                       this._fadePageIn(oldPage);
-                                   })
-                             });
-        else
-            this._fadePageIn(oldPage);
+        /*if (this._activePage == this._appsPage)
+            this.appDisplay.animateIn();*/
     },
 
     _a11yFocusPage: function(page) {


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