[gnome-shell/wip/swarm: 7/9] Search: Animate new window of apps in AllView and FrequentView



commit 2b9f90256bb357d9378b5a6abd4b92f2be5c1229
Author: Carlos Soriano <carlos soriano89 gmail com>
Date:   Thu Jun 26 21:36:15 2014 +0200

    Search: Animate new window of apps in AllView and FrequentView
    
    Following design mockups, animate the icons on AllView and FrequentView
    to zoom out when opening a new window of the app or when the app is not
    running and the user execute it.

 js/ui/appDisplay.js |   53 +++++++++++++++++++-------------
 js/ui/iconGrid.js   |   83 ++++++++++++++++++++++++++++++++++++++++++++++-----
 2 files changed, 107 insertions(+), 29 deletions(-)
---
diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
index f6fdfb4..22693b2 100644
--- a/js/ui/appDisplay.js
+++ b/js/ui/appDisplay.js
@@ -483,7 +483,7 @@ const AllView = new Lang.Class({
 
         apps.forEach(Lang.bind(this, function(appId) {
             let app = appSys.lookup_app(appId);
-            let icon = new AppIcon(app);
+            let icon = new AppIcon(app, { animateOnNewWindow: true });
             this.addItem(icon);
         }));
 
@@ -783,7 +783,7 @@ const FrequentView = new Lang.Class({
         for (let i = 0; i < mostUsed.length; i++) {
             if (!mostUsed[i].get_app_info().should_show())
                 continue;
-            let appIcon = new AppIcon(mostUsed[i]);
+            let appIcon = new AppIcon(mostUsed[i], { animateOnNewWindow: true });
             this._grid.addItem(appIcon, -1);
         }
     },
@@ -1530,7 +1530,7 @@ Signals.addSignalMethods(AppFolderPopup.prototype);
 const AppIcon = new Lang.Class({
     Name: 'AppIcon',
 
-    _init : function(app, iconParams) {
+    _init : function(app, params) {
         this.app = app;
         this.id = app.get_id();
         this.name = app.get_name();
@@ -1543,12 +1543,15 @@ const AppIcon = new Lang.Class({
                                      y_fill: true });
         this.actor._delegate = this;
 
-        if (!iconParams)
-            iconParams = {};
+        if (!params)
+            params = {};
+
+        this._params = Params.parse(params, { animateOnNewWindow: false,
+                                              createIcon: Lang.bind(this, this._createIcon),
+                                              setSizeManually: true },
+                                              true);
 
-        iconParams['createIcon'] = Lang.bind(this, this._createIcon);
-        iconParams['setSizeManually'] = true;
-        this.icon = new IconGrid.BaseIcon(app.get_name(), iconParams);
+        this.icon = new IconGrid.BaseIcon(app.get_name(), this._params);
         this.actor.set_child(this.icon.actor);
 
         this.actor.label_actor = this.icon.label;
@@ -1647,13 +1650,7 @@ const AppIcon = new Lang.Class({
 
     _onClicked: function(actor, button) {
         this._removeMenuTimeout();
-
-        if (button == 0 || button == 1) {
-            this._onActivate(Clutter.get_current_event());
-        } else if (button == 2) {
-            this.app.open_new_window(-1);
-            Main.overview.hide();
-        }
+        this.activate(button);
     },
 
     _onKeyboardPopupMenu: function() {
@@ -1707,19 +1704,29 @@ const AppIcon = new Lang.Class({
         this.emit('menu-state-changed', false);
     },
 
-    _onActivate: function (event) {
-        let modifiers = event.get_state();
+    activate: function (button) {
+        let event = Clutter.get_current_event();
+        let modifiers = event ? event.get_state() : 0;
+        let openNewWindow = modifiers & Clutter.ModifierType.CONTROL_MASK &&
+                            this.app.state == Shell.AppState.RUNNING ||
+                            button && button == 2;
+
+        if (this._params.animateOnNewWindow &&
+            (this.app.state == Shell.AppState.STOPPED || openNewWindow))
+            this.animateOut();
 
-        if (modifiers & Clutter.ModifierType.CONTROL_MASK
-            && this.app.state == Shell.AppState.RUNNING) {
+        if (openNewWindow)
             this.app.open_new_window(-1);
-        } else {
+        else
             this.app.activate();
-        }
 
         Main.overview.hide();
     },
 
+    animateOut: function() {
+        this.icon.animateOut();
+    },
+
     shellWorkspaceLaunch : function(params) {
         params = Params.parse(params, { workspace: -1,
                                         timestamp: 0 });
@@ -1808,6 +1815,10 @@ const AppIconMenu = new Lang.Class({
             if (this._source.app.can_open_new_window()) {
                 this._newWindowMenuItem = this._appendMenuItem(_("New Window"));
                 this._newWindowMenuItem.connect('activate', Lang.bind(this, function() {
+                    if (this._source.params.animateOnNewWindow &&
+                        this._source.app.state == Shell.AppState.STOPPED)
+                        this._source.animateOut();
+
                     this._source.app.open_new_window(-1);
                     this.emit('activate-window', null);
                 }));
diff --git a/js/ui/iconGrid.js b/js/ui/iconGrid.js
index 841415b..2ad5a7e 100644
--- a/js/ui/iconGrid.js
+++ b/js/ui/iconGrid.js
@@ -35,13 +35,17 @@ const AnimationDirection = {
     OUT: 1
 };
 
+const APPICON_ANIMATION_OUT_SCALE = 3;
+const APPICON_ANIMATION_OUT_TIME = 0.25;
+
 const BaseIcon = new Lang.Class({
     Name: 'BaseIcon',
 
     _init : function(label, params) {
         params = Params.parse(params, { createIcon: null,
                                         setSizeManually: false,
-                                        showLabel: true });
+                                        showLabel: true },
+                                        true);
 
         let styleClass = 'overview-icon';
         if (params.showLabel)
@@ -58,23 +62,23 @@ const BaseIcon = new Lang.Class({
 
         this._spacing = 0;
 
-        let box = new Shell.GenericContainer();
-        box.connect('allocate', Lang.bind(this, this._allocate));
-        box.connect('get-preferred-width',
+        this._box = new Shell.GenericContainer();
+        this._box.connect('allocate', Lang.bind(this, this._allocate));
+        this._box.connect('get-preferred-width',
                     Lang.bind(this, this._getPreferredWidth));
-        box.connect('get-preferred-height',
+        this._box.connect('get-preferred-height',
                     Lang.bind(this, this._getPreferredHeight));
-        this.actor.set_child(box);
+        this.actor.set_child(this._box);
 
         this.iconSize = ICON_SIZE;
         this._iconBin = new St.Bin({ x_align: St.Align.MIDDLE,
                                      y_align: St.Align.MIDDLE });
 
-        box.add_actor(this._iconBin);
+        this._box.add_actor(this._iconBin);
 
         if (params.showLabel) {
             this.label = new St.Label({ text: label });
-            box.add_actor(this.label);
+            this._box.add_actor(this.label);
         } else {
             this.label = null;
         }
@@ -192,9 +196,72 @@ const BaseIcon = new Lang.Class({
 
     _onIconThemeChanged: function() {
         this._createIconTexture(this.iconSize);
+    },
+
+    animateOut: function() {
+        // Animate only the container box instead of the entire actor,
+        // so the styles like hover and running are not applied while
+        // animating.
+        actorZoomOut(this._box);
     }
 });
 
+function actorZoomOut(actor) {
+    let actorClone = new Clutter.Clone({ source: actor,
+                                         reactive: false });
+    let [width, height] = actor.get_transformed_size();
+    let [x, y] = actor.get_transformed_position();
+    actorClone.set_size(width, height);
+    actorClone.set_position(x, y);
+    actorClone.opacity = 255;
+    actorClone.set_pivot_point(0.5, 0.5);
+
+    Main.uiGroup.add_actor(actorClone);
+
+    // Avoid monitor edges to not zoom outside the current monitor
+    let monitor = Main.layoutManager.findMonitorForActor(actor);
+    let finalWidth = width * APPICON_ANIMATION_OUT_SCALE;
+    let finalHeight = height * APPICON_ANIMATION_OUT_SCALE;
+    // Assume pivot point at 0.5, 0.5
+    let finalX = x - (finalWidth - width) / 2;
+    let finalY = y - (finalHeight - height) / 2;
+    let [vecX, vecY] = vectorToBeContainedInActor(finalX, finalY, finalWidth, finalHeight, monitor);
+
+    Tweener.addTween(actorClone,
+                     { time: APPICON_ANIMATION_OUT_TIME,
+                       scale_x: APPICON_ANIMATION_OUT_SCALE,
+                       scale_y: APPICON_ANIMATION_OUT_SCALE,
+                       translation_x: vecX,
+                       translation_y: vecY,
+                       opacity: 0,
+                       transition: 'easeOutQuad',
+                       onComplete: function() {
+                           actorClone.destroy();
+                       }
+                    });
+}
+
+function vectorToBeContainedInActor(x, y, width, height, actor) {
+        // Avoid actors bigger than the screen, since they cannot be contained
+        if (width >  actor.width || height > actor.height)
+            return undefined;
+
+        let vecX = 0;
+        let vecY = 0;
+
+        if (x < actor.x)
+            vecX = actor.x - x;
+        else if (x + width > actor.x + actor.width)
+            vecX = actor.x + actor.width - (x + width);
+
+        if (y < actor.y)
+            vecY = actor.y - y;
+        else if (y + height > actor.y + actor.height)
+            vecY = actor.y + actor.height - (y + height);
+
+        return [vecX, vecY];
+}
+
 const IconGrid = new Lang.Class({
     Name: 'IconGrid',
 


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