[gnome-shell/wip/swarm: 8/12] appDisplay: Animate AllView and FrequentView



commit 4abb981dc547453698dab2005336b5c2f042eafe
Author: Carlos Soriano <carlos soriano89 gmail com>
Date:   Tue Jun 17 17:46:45 2014 +0200

    appDisplay: Animate AllView and FrequentView

 js/ui/appDisplay.js   |   77 +++++++++++++++++++++++++++
 js/ui/iconGrid.js     |  138 ++++++++++++++++++++++++++++++++++++++++++++++++-
 js/ui/viewSelector.js |   22 ++++++--
 3 files changed, 229 insertions(+), 8 deletions(-)
---
diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
index 3c18f6d..894c2aa 100644
--- a/js/ui/appDisplay.js
+++ b/js/ui/appDisplay.js
@@ -439,6 +439,47 @@ const AllView = new Lang.Class({
         this.loadGrid();
         this._refilterApps();
     },
+    
+    animate: function(animationDirection, onCompleteOut) {
+        let dashPosition = Main.overview._dash._showAppsIcon.get_transformed_position();
+        let dashSize = Main.overview._dash._showAppsIcon.get_transformed_size();
+        // Design decision, 1/2 of the dash icon size.
+        let dashScaledSize = [dashSize[0] / 2, dashSize[1] / 2];
+        let gridAnimationFunction = Lang.bind(this, function() {
+            this._grid.animate(IconGrid.ANIMATION_TYPE_SWARM_SPRING,
+                               animationDirection,
+                               { sourcePosition: dashPosition,
+                                 sourceSize: dashScaledSize,
+                                 page: this._currentPage })
+        });
+        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);
+                        gridAnimationFunction();
+                    }
+                }));
+        } else {
+            let animationDoneId = this._grid.connect('animation-done', Lang.bind(this,
+                function () {
+                    this._grid.disconnect(animationDoneId);
+                    if (onCompleteOut)
+                        onCompleteOut();
+                }));
+
+            if (this._displayingPopup && this._currentPopup) {
+                this._currentPopup.popdown();
+                let spaceClosedId = this._grid.connect('space-closed', Lang.bind(this,
+                    function() {
+                        this._grid.disconnect(spaceClosedId);
+                        gridAnimationFunction();
+                    }));
+            } else {
+                gridAnimationFunction();
+            }
+        }
+    },
 
     getCurrentPageY: function() {
         return this._grid.getPageY(this._currentPage);
@@ -680,6 +721,37 @@ const FrequentView = new Lang.Class({
             this._grid.addItem(appIcon, -1);
         }
     },
+    
+    animate: function(animationDirection, onCompleteOut) {
+        let dashPosition = Main.overview._dash._showAppsIcon.get_transformed_position();
+        let dashSize = Main.overview._dash._showAppsIcon.get_transformed_size();
+        // Design decision, 1/2 of the dash icon size.
+        let dashScaledSize = [dashSize[0] / 2, dashSize[1] / 2];
+        let gridAnimationFunction = Lang.bind(this, function() {
+            this._grid.animate(IconGrid.ANIMATION_TYPE_SWARM_SPRING,
+                               animationDirection,
+                               { sourcePosition: dashPosition,
+                                 sourceSize: dashScaledSize });
+        });
+
+        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);
+                        gridAnimationFunction();
+                    }
+                }));
+        } else {
+            let animationDoneId = this._grid.connect('animation-done', Lang.bind(this,
+                function () {
+                    this._grid.disconnect(animationDoneId);
+                    if (onCompleteOut)
+                        onCompleteOut();
+                }));
+            gridAnimationFunction();
+        }
+    },
 
     // Called before allocation to calculate dynamic spacing
     adaptToSize: function(width, height) {
@@ -798,6 +870,11 @@ const AppDisplay = new Lang.Class({
         this._showView(initialView);
         this._updateFrequentVisibility();
     },
+    
+    animate: function(animationDirection, onCompleteOut) {
+        let view = this._views[global.settings.get_uint('app-picker-view')].view;
+        view.animate(animationDirection, onCompleteOut);
+    },
 
     _showView: function(activeIndex) {
         for (let i = 0; i < this._views.length; i++) {
diff --git a/js/ui/iconGrid.js b/js/ui/iconGrid.js
index 90301ce..98958ec 100644
--- a/js/ui/iconGrid.js
+++ b/js/ui/iconGrid.js
@@ -10,6 +10,7 @@ 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;
@@ -17,12 +18,17 @@ 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_APPEAR_ICON_SCALE = 1.1;
 
 const ANIMATION_TYPE_APPEAR = 1;
+const ANIMATION_TYPE_SWARM_SPRING = 2;
 
+const ANIMATION_DIRECTION_OUT = 0;
 const ANIMATION_DIRECTION_IN = 1;
 
 const BaseIcon = new Lang.Class({
@@ -364,6 +370,9 @@ const IconGrid = new Lang.Class({
         this._animating = true;
 
         switch (animationType) {
+            case ANIMATION_TYPE_SWARM_SPRING:
+                this._animateSwarmSpring(actors, animationDirection, params.sourcePosition, 
params.sourceSize);
+                break;
             case ANIMATION_TYPE_APPEAR:
                 this._animateAppear(actors, animationDirection);
                 break;
@@ -408,11 +417,12 @@ const IconGrid = new Lang.Class({
                                                      scale_x: 1,
                                                      scale_y: 1,
                                                      onComplete: Lang.bind(this, function() {
-                                                        if (isLastActor)
+                                                        if (isLastActor) {
                                                             this._animating = false;
+                                                            this.emit('animation-done');
+                                                        }
                                                         actor.opacity = 255;
                                                         actorClone.destroy();
-                                                        this.emit('animation-done');
                                                     })
                                                    });
                               })
@@ -420,6 +430,108 @@ const IconGrid = new Lang.Class({
         }
     },
 
+    _animateSwarmSpring: function(actors, animationDirection, sourcePosition, sourceSize) {
+        let distances = actors.map(Lang.bind(this, function(actor) {
+            return this._distance(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 < actors.length; index++) {
+            // FIXME? Seems that putting the actors at opacity 0
+            // for animating seems like it doesn't belongs here.
+            // But works well.
+            actors[index].opacity = 0;
+
+            let actorClone = new Clutter.Clone({ source: actors[index],
+                                                reactive: false });
+            Main.uiGroup.add_actor(actorClone);
+
+            actorClone.reactive = false;
+            actorClone.set_pivot_point(0.5, 0.5);
+            let scaleX = sourceSize[0] / actors[index].get_transformed_size()[0];
+            let scaleY = sourceSize[1] / actors[index].get_transformed_size()[1];
+
+            // Defeat onComplete anonymous function closure
+            let actor = actors[index];
+            let isLastItem = index == actors.length - 1;
+
+            let movementParams, fadeParams;
+            if (animationDirection == ANIMATION_DIRECTION_IN) {
+                actorClone.set_position(sourcePosition[0], sourcePosition[1]);
+                actorClone.opacity = 0;
+                actorClone.set_scale(scaleX, scaleY);
+                let delay = (1 - (distances[index] - minDist) / normalization) * 
ANIMATION_MAX_DELAY_FOR_ITEM;
+                let [finalX, finalY]  = actors[index].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;
+                                           this.emit('animation-done');
+                                       }
+                                       actor.opacity = 255;
+                                       actorClone.destroy();
+                                   })};
+                fadeParams = { time: ANIMATION_FADE_IN_TIME_FOR_ITEM,
+                               transition: 'easeInOutQuad',
+                               delay: delay,
+                               opacity: 255 };
+            } else {
+                let [startX, startY]  = actors[index].get_transformed_position();
+                actorClone.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: scaleX,
+                                   scale_y: scaleY,
+                                   onComplete: Lang.bind(this, function() {
+                                       if (isLastItem) {
+                                           this._animating = false;
+                                           this.emit('animation-done');
+                                           this._restoreItemsOpacity();
+                                       }
+                                       actorClone.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(actorClone, movementParams);
+            Tweener.addTween(actorClone, fadeParams);
+        }
+    },
+
+
+    /**
+     * FIXME?: We assume the icons use full opacity.
+     **/
+    _restoreItemsOpacity: function() {
+        for (let index = 0; index < this._items.length; index++) {
+            this._items[index].actor.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);
+    },
+
     _calculateChildBox: function(child, x, y, box) {
         let [childMinWidth, childMinHeight, childNaturalWidth, childNaturalHeight] =
              child.get_preferred_size();
@@ -690,6 +802,14 @@ const PaginatedIconGrid = new Lang.Class({
         }
     },
 
+    animate: function(animationType, animationDirection, params) {
+        params = Params.parse(params, { page: 0,
+                                        sourcePosition: null,
+                                        sourceSize: null });
+
+        this._animateReal(this._getChildrenInPage(params.page), animationType, animationDirection, params);
+    },
+
     _computePages: function (availWidthPerPage, availHeightPerPage) {
         let [nColumns, usedWidth] = this._computeLayout(availWidthPerPage);
         let nRows;
@@ -745,6 +865,20 @@ const PaginatedIconGrid = new Lang.Class({
         return Math.floor(index / this._childrenPerPage);
     },
 
+    _getChildrenInPage: function(pageNumber) {
+        let children = this._getVisibleChildren();
+
+        let firstIndex = this._childrenPerPage * pageNumber;
+        let indexOffset = 0;
+        let childrenInPage = []
+
+        while (indexOffset < this._childrenPerPage && firstIndex + indexOffset < children.length) {
+               childrenInPage.push(children[firstIndex + indexOffset]);
+               indexOffset++;
+        }
+        return childrenInPage;
+    },
+
     /**
     * openExtraSpace:
     * @sourceItem: the item for which to create extra space
diff --git a/js/ui/viewSelector.js b/js/ui/viewSelector.js
index 81780f5..4d8684c 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';
 
@@ -227,14 +228,23 @@ const ViewSelector = new Lang.Class({
                          });
     },
 
-    _animateIn: function(page) {
+    _animateIn: function(page, oldPage) {
         this._activePage.show();
 
-        this._fadePageIn(this._activePage);
+        if (page == this._appsPage) {
+            this.appDisplay.animate(IconGrid.ANIMATION_DIRECTION_IN);
+        } else {
+            this._fadePageIn(this._activePage);
+        }
     },
 
-    _animateOut: function(page, onComplete) {
-        this._fadePageOut(page, onComplete);
+    _animateOut: function(page, newPage, onComplete) {
+        if (page == this._appsPage) {
+            this.appDisplay.animate(IconGrid.ANIMATION_DIRECTION_OUT,
+                                    onComplete);
+        } else {
+            this._fadePageOut(page, onComplete);
+        }
     },
 
     _hidePageAndSyncEmpty: function(page) {
@@ -254,11 +264,11 @@ const ViewSelector = new Lang.Class({
         let animateActivePage = Lang.bind(this,
             function() {
                 this._hidePageAndSyncEmpty(oldPage);
-                this._animateIn(this._activePage);
+                this._animateIn(this._activePage, oldPage);
             });
 
         if (oldPage && animateOut)
-            this._animateOut(oldPage, animateActivePage)
+            this._animateOut(oldPage, newPage, animateActivePage)
         else
             animateActivePage();
     },


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