[gnome-shell/wip/swarm: 116/126] appDisplay: Animate AllView and FrequentView
- From: Carlos Soriano <csoriano src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-shell/wip/swarm: 116/126] appDisplay: Animate AllView and FrequentView
- Date: Mon, 11 Aug 2014 20:30:26 +0000 (UTC)
commit 1ec79c0bed6a31330829931275a47d83740f0c58
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 | 112 ++++++++++++++++++++++++++++++++++++
js/ui/iconGrid.js | 150 +++++++++++++++++++++++++++++++++++++++++++++++--
js/ui/viewSelector.js | 17 +++++-
3 files changed, 271 insertions(+), 8 deletions(-)
---
diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
index ca56d6c..9bef143 100644
--- a/js/ui/appDisplay.js
+++ b/js/ui/appDisplay.js
@@ -458,6 +458,58 @@ const AllView = new Lang.Class({
this._refilterApps();
},
+ animate: function(animationDirection, onCompleteOut) {
+ let dashPosition = Main.overview._dash._showAppsIcon.get_transformed_position();
+ let dashSize = Main.overview._dash._showAppsIcon.get_transformed_size();
+ let centerDashPosition = [dashPosition[0] + dashSize[0] / 2, dashPosition[1] + dashSize[1] / 2];
+ // 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.AnimationType.SPRING,
+ animationDirection,
+ { sourcePosition: centerDashPosition,
+ sourceSize: dashScaledSize,
+ page: this._currentPage })
+ });
+ if (animationDirection == IconGrid.AnimationDirection.IN) {
+ let toAnimate = this._grid.actor.connect('notify::allocation', Lang.bind(this,
+ function() {
+ if (this._grid.actor.mapped) {
+ this._grid.actor.disconnect(toAnimate);
+ // We need to avoid the clutter allocation loop, so we use
+ // a call before redraw, but also we need to hide items
+ // to not show them for a moment before the animation is
+ // started
+ this._grid.actor.opacity = 0;
+ Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this,
+ function() {
+ this._grid.actor.opacity = 255;
+ 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);
},
@@ -699,6 +751,47 @@ const FrequentView = new Lang.Class({
}
},
+ animate: function(animationDirection, onCompleteOut) {
+ let dashPosition = Main.overview._dash._showAppsIcon.get_transformed_position();
+ let dashSize = Main.overview._dash._showAppsIcon.get_transformed_size();
+ let centerDashPosition = [dashPosition[0] + dashSize[0] / 2, dashPosition[1] + dashSize[1] / 2];
+ // 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.AnimationType.SPRING,
+ animationDirection,
+ { sourcePosition: centerDashPosition,
+ sourceSize: dashScaledSize });
+ });
+
+ if (animationDirection == IconGrid.AnimationDirection.IN) {
+ let toAnimate = this._grid.actor.connect('notify::allocation', Lang.bind(this,
+ function() {
+ if (this._grid.actor.mapped) {
+ this._grid.actor.disconnect(toAnimate);
+ // We need to avoid the clutter allocation loop, so we use
+ // a call before redraw, but also we need to hide items
+ // to not show them for a moment before the animation is
+ // started
+ this._grid.actor.opacity = 0;
+ Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this,
+ function() {
+ this._grid.actor.opacity = 255;
+ 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) {
let box = new Clutter.ActorBox();
@@ -817,6 +910,25 @@ const AppDisplay = new Lang.Class({
this._updateFrequentVisibility();
},
+ animate: function(animationDirection, onCompleteOut) {
+ let view = this._views[global.settings.get_uint('app-picker-view')].view;
+
+ // Animate controls opacity using swarm time, since it will be
+ // the time the AllView or FrequentView takes to show it entirely.
+ let finalOpacity;
+ if (animationDirection == IconGrid.AnimationDirection.IN) {
+ this._controls.opacity = 0;
+ finalOpacity = 255;
+ } else {
+ finalOpacity = 0
+ }
+ Tweener.addTween(this._controls,
+ { time: IconGrid.ANIMATION_TIME_IN,
+ transition: 'easeInOutQuad',
+ opacity: finalOpacity });
+ view.animate(animationDirection, onCompleteOut);
+ },
+
_showView: function(activeIndex) {
for (let i = 0; i < this._views.length; i++) {
let actor = this._views[i].view.actor;
diff --git a/js/ui/iconGrid.js b/js/ui/iconGrid.js
index e29f3ba..5a2d1f4 100644
--- a/js/ui/iconGrid.js
+++ b/js/ui/iconGrid.js
@@ -18,16 +18,21 @@ const MIN_ICON_SIZE = 16;
const EXTRA_SPACE_ANIMATION_TIME = 0.25;
const ANIMATION_TIME_IN = 0.350;
+const ANIMATION_TIME_OUT = 1/2 * ANIMATION_TIME_IN;
const ANIMATION_MAX_DELAY_FOR_ITEM = 2/3 * ANIMATION_TIME_IN;
+const ANIMATION_MAX_DELAY_OUT_FOR_ITEM = 2/3 * ANIMATION_TIME_OUT;
+const ANIMATION_FADE_IN_TIME_FOR_ITEM = 1/4 * ANIMATION_TIME_IN;
const ANIMATION_BOUNCE_ICON_SCALE = 1.1;
const AnimationType = {
- BOUNCE: 0
+ BOUNCE: 0,
+ SPRING: 1
};
const AnimationDirection = {
- IN: 0
+ IN: 0,
+ OUT: 1
};
const BaseIcon = new Lang.Class({
@@ -356,12 +361,16 @@ const IconGrid = new Lang.Class({
* Intended to be override by subclasses if they need a diferent
* set of items to be animated.
*/
- _getChildrenToAnimate: function() {
+ _getChildrenToAnimate: function(params) {
return this._getVisibleChildren();
},
animate: function(animationType, animationDirection, params) {
- let actors = this._getChildrenToAnimate();
+ params = Params.parse(params, { page: 0,
+ sourcePosition: null,
+ sourceSize: null });
+
+ let actors = this._getChildrenToAnimate(params);
if (this._animating || actors.length == 0)
return;
this._animating = true;
@@ -371,6 +380,9 @@ const IconGrid = new Lang.Class({
let delayedAnimation = Lang.bind(this, function() {
switch (animationType) {
+ case AnimationType.SPRING:
+ this._animateSpring(actors, animationDirection, params.sourcePosition,
params.sourceSize);
+ break;
case AnimationType.BOUNCE:
this._animateBounce(actors, animationDirection);
break;
@@ -415,11 +427,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');
})
});
})
@@ -427,6 +440,113 @@ const IconGrid = new Lang.Class({
}
},
+ _animateSpring: 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++) {
+ let actorClone = new Clutter.Clone({ source: actors[index],
+ reactive: false });
+ Main.uiGroup.add_actor(actorClone);
+
+ actorClone.set_pivot_point(0.0, 0.0);
+ let [width, height] = actors[index].get_transformed_size();
+ actorClone.set_size(width, height);
+ let scaleX = sourceSize[0] / width;
+ let scaleY = sourceSize[1] / height;
+
+ // Defeat onComplete anonymous function closure
+ let actor = actors[index];
+ let isLastItem = index == actors.length - 1;
+
+ let movementParams, fadeParams;
+
+ // Center the actor on the source position, expecting sourcePosition to be the center
+ // where the actor should be. In this way we avoid misaligments if the source actor size changes
+ // or is diferent that it should be. (hint: for the AllView and FrequentView animations the
+ // sourceSize is not exactly the actor size, since we want smaller actors to be animated due to
+ // design decision)
+ let adjustedSourcePosition = [sourcePosition[0] - sourceSize[0] / 2, sourcePosition[1] -
sourceSize[1] / 2];
+
+ if (animationDirection == AnimationDirection.IN) {
+ actorClone.opacity = 0;
+ actorClone.set_scale(scaleX, scaleY);
+
+ actorClone.set_position(adjustedSourcePosition[0], adjustedSourcePosition[1]);
+
+ 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;
+ movementParams = { time: ANIMATION_TIME_OUT,
+ transition: 'easeInOutQuad',
+ delay: delay,
+ x: adjustedSourcePosition[0],
+ y: adjustedSourcePosition[1],
+ 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();
@@ -697,6 +817,10 @@ const PaginatedIconGrid = new Lang.Class({
}
},
+ _getChildrenToAnimate: function(params) {
+ return this._getChildrenInPage(params.page);
+ },
+
_computePages: function (availWidthPerPage, availHeightPerPage) {
let [nColumns, usedWidth] = this._computeLayout(availWidthPerPage);
let nRows;
@@ -752,6 +876,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 a3d1af5..9764848 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';
@@ -395,11 +396,23 @@ const ViewSelector = new Lang.Class({
_animateIn: function(page) {
page.show();
- this._fadePageIn(page);
+ if (page == this._appsPage) {
+ // Restore opacity of the page, since we could took the opacity
+ // on _fadePageIn if we didn't use swarm animation to animate out.
+ page.opacity = 255;
+ this.appDisplay.animate(IconGrid.AnimationDirection.IN);
+ } else {
+ this._fadePageIn(page);
+ }
},
_animateOut: function(page, onComplete) {
- this._fadePageOut(page, onComplete);
+ if (page == this._appsPage) {
+ this.appDisplay.animate(IconGrid.AnimationDirection.OUT,
+ onComplete);
+ } else {
+ this._fadePageOut(page, onComplete);
+ }
},
_hidePageAndSyncEmpty: function(page) {
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]