[gnome-shell/wip/swarm: 8/15] appDisplay: Animate AllView and FrequentView
- From: Carlos Soriano <csoriano src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-shell/wip/swarm: 8/15] appDisplay: Animate AllView and FrequentView
- Date: Mon, 23 Jun 2014 10:38:19 +0000 (UTC)
commit 281f8db42ac02a7600ad67e3091dc4d7dde77d3a
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 | 139 ++++++++++++++++++++++++++++++++++++++++++++++++-
js/ui/viewSelector.js | 22 ++++++--
3 files changed, 230 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 e88c173..92bb403 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;
@@ -410,11 +419,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');
})
});
})
@@ -422,6 +432,109 @@ 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.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];
+ let [width, height] = actors[index].get_transformed_size();
+ actorClone.set_size(width, height);
+
+ // 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();
@@ -692,6 +805,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;
@@ -747,6 +868,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]