[gnome-shell/wip/swarm: 1/9] appDisplay: Animate AllView and FrequentView
- From: Carlos Soriano <csoriano src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-shell/wip/swarm: 1/9] appDisplay: Animate AllView and FrequentView
- Date: Wed, 25 Jun 2014 20:57:25 +0000 (UTC)
commit bf2c80c4e48ec570a10e17eb63a6c05e6f5b8afb
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 | 86 +++++++++++++++++++++++++++-
js/ui/iconGrid.js | 148 ++++++++++++++++++++++++++++++++++++++++++++++++-
js/ui/viewSelector.js | 14 ++++-
3 files changed, 241 insertions(+), 7 deletions(-)
---
diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
index 3944e27..8274aba 100644
--- a/js/ui/appDisplay.js
+++ b/js/ui/appDisplay.js
@@ -440,6 +440,49 @@ 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.ANIMATION_TYPE_SWARM_SPRING,
+ animationDirection,
+ { sourcePosition: centerDashPosition,
+ 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);
},
@@ -681,6 +724,38 @@ 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.ANIMATION_TYPE_SWARM_SPRING,
+ animationDirection,
+ { sourcePosition: centerDashPosition,
+ 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) {
let box = new Clutter.ActorBox();
@@ -799,6 +874,11 @@ const AppDisplay = new Lang.Class({
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++) {
let actor = this._views[i].view.actor;
@@ -947,7 +1027,7 @@ const FolderView = new Lang.Class({
_keyFocusIn: function(actor) {
Util.ensureActorVisibleInScrollView(this.actor, actor);
},
-
+
animate: function(animationType, animationDirection, params) {
this._grid.animate(animationType, animationDirection, params);
},
@@ -1282,13 +1362,13 @@ 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
+ // 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,
- Lang.bind(this,
+ Lang.bind(this,
function() {
// Restore the view opacity, so now we show the icons and animate
// them
diff --git a/js/ui/iconGrid.js b/js/ui/iconGrid.js
index e88c173..158b90f 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,118 @@ 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.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 == ANIMATION_DIRECTION_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();
@@ -692,6 +814,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 +877,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 13407a5..472cef3 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';
@@ -230,11 +231,20 @@ const ViewSelector = new Lang.Class({
_animateIn: function(page) {
page.show();
- this._fadePageIn(page);
+ if (page == this._appsPage) {
+ this.appDisplay.animate(IconGrid.ANIMATION_DIRECTION_IN);
+ } else {
+ this._fadePageIn(page);
+ }
},
_animateOut: function(page, onComplete) {
- this._fadePageOut(page, onComplete);
+ if (page == this._appsPage) {
+ this.appDisplay.animate(IconGrid.ANIMATION_DIRECTION_OUT,
+ onComplete);
+ } else {
+ this._fadePageOut(page, onComplete);
+ }
},
_hidePageAndSyncEmpty: function(page) {
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]