Compare commits

...

5 Commits

Author SHA1 Message Date
Florian Müllner
253d234b50 windowManager: Use easing for window animations 2019-05-15 22:08:07 +00:00
Florian Müllner
c168bd4e01 windowManager: Use ClutterTransition to animate window dimming 2019-05-15 22:08:07 +00:00
Florian Müllner
339797dbb4 environment: Patch in some implicit animation convenience
Setting up implicit animations is more verbose than using tweener, in
particular when setting up callbacks to run on update or completion.
In order to make its use more convenient, monkey-patch ClutterActor
with an ease() method that works similarly to Tweener.addTween().

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/22
2019-05-15 22:08:07 +00:00
Florian Müllner
6c7dcd5ffd environment: Respect enable-animations setting when easing
In addition to the slow down factor, we want implicit animations to
follow GTK+'s enable-animations setting as well.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/22
2019-05-15 22:08:07 +00:00
Florian Müllner
94f12fa764 environment: Support slow down factor when easing
Being able to slow down animations is a helpful debugging tool; to not
lose it when starting to use Clutter's implicit animations, monkey-patch
the appropriate methods to support our global slow down factor.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/22
2019-05-15 21:36:57 +00:00
2 changed files with 286 additions and 196 deletions

View File

@ -57,6 +57,60 @@ function _patchLayoutClass(layoutClass, styleProps) {
};
}
function _adjustEasingTime(msecs) {
if (!St.Settings.get().enable_animations)
return 1;
return St.get_slow_down_factor() * msecs;
}
function _easeActor(actor, props, easingParams) {
let { duration, delay, mode,
onStopped, onUpdate, onComplete } = easingParams;
let animatedProps = Object.keys(props).map(p => p.replace('_', '-', 'g'));
actor.save_easing_state();
if (duration)
actor.set_easing_duration(duration);
if (delay)
actor.set_easing_delay(delay);
if (mode)
actor.set_easing_mode(mode);
actor.set(props);
if (onUpdate || onComplete || onStopped) {
let transition = actor.get_transition(animatedProps[0]);
if (transition) {
let updateId = 0;
if (onUpdate)
updateId = transition.connect('new-frame', onUpdate);
let id = transition.connect('stopped', isFinished => {
if (updateId != 0)
transition.disconnect(updateId);
transition.disconnect(id);
if (onComplete)
onComplete();
if (onStopped)
onStopped(isFinished);
});
} else {
if (onComplete)
onComplete();
if (onStopped)
onStopped(true);
}
}
actor.restore_easing_state();
}
function _loggingFunc() {
let fields = {'MESSAGE': [].join.call(arguments, ', ')};
let domain = "GNOME Shell";
@ -93,6 +147,19 @@ function init() {
column_spacing: 'spacing-columns' });
_patchLayoutClass(Clutter.BoxLayout, { spacing: 'spacing' });
let origSetEasingDuration = Clutter.Actor.prototype.set_easing_duration;
Clutter.Actor.prototype.set_easing_duration = function(msecs) {
origSetEasingDuration.call(this, _adjustEasingTime(msecs));
};
let origSetEasingDelay = Clutter.Actor.prototype.set_easing_delay;
Clutter.Actor.prototype.set_easing_delay = function(msecs) {
origSetEasingDelay.call(this, _adjustEasingTime(msecs));
};
Clutter.Actor.prototype.ease = function(props, easingParams) {
_easeActor(this, props, easingParams);
};
Clutter.Actor.prototype.toString = function() {
return St.describe_actor(this);
};

View File

@ -11,7 +11,6 @@ const WorkspaceSwitcherPopup = imports.ui.workspaceSwitcherPopup;
const InhibitShortcutsDialog = imports.ui.inhibitShortcutsDialog;
const Main = imports.ui.main;
const ModalDialog = imports.ui.modalDialog;
const Tweener = imports.ui.tweener;
const WindowMenu = imports.ui.windowMenu;
const PadOsd = imports.ui.padOsd;
const EdgeDragAction = imports.ui.edgeDragAction;
@ -21,15 +20,15 @@ const SwitchMonitor = imports.ui.switchMonitor;
const { loadInterfaceXML } = imports.misc.fileUtils;
var SHELL_KEYBINDINGS_SCHEMA = 'org.gnome.shell.keybindings';
var MINIMIZE_WINDOW_ANIMATION_TIME = 0.2;
var SHOW_WINDOW_ANIMATION_TIME = 0.15;
var DIALOG_SHOW_WINDOW_ANIMATION_TIME = 0.1;
var DESTROY_WINDOW_ANIMATION_TIME = 0.15;
var DIALOG_DESTROY_WINDOW_ANIMATION_TIME = 0.1;
var WINDOW_ANIMATION_TIME = 0.25;
var MINIMIZE_WINDOW_ANIMATION_TIME = 200; // ms
var SHOW_WINDOW_ANIMATION_TIME = 150; // ms
var DIALOG_SHOW_WINDOW_ANIMATION_TIME = 100; // ms
var DESTROY_WINDOW_ANIMATION_TIME = 150; // ms
var DIALOG_DESTROY_WINDOW_ANIMATION_TIME = 100; // ms
var WINDOW_ANIMATION_TIME = 250; // ms
var DIM_BRIGHTNESS = -0.3;
var DIM_TIME = 0.500;
var UNDIM_TIME = 0.250;
var DIM_TIME = 500; // ms
var UNDIM_TIME = 250; // ms
var MOTION_THRESHOLD = 100;
var ONE_SECOND = 1000; // in ms
@ -115,16 +114,39 @@ var DisplayChangeDialog = class extends ModalDialog.ModalDialog {
var WindowDimmer = class {
constructor(actor) {
this._brightnessEffect = new Clutter.BrightnessContrastEffect();
this._brightnessEffect = new Clutter.BrightnessContrastEffect({
name: 'dim',
enabled: false
});
actor.add_effect(this._brightnessEffect);
this.actor = actor;
this._enabled = true;
this._dimFactor = 0.0;
this._syncEnabled();
this._dimmed = false;
}
_syncEnabled() {
this._brightnessEffect.enabled = (this._enabled && this._dimFactor > 0);
this._brightnessEffect.enabled =
(this._enabled && (this._dimmed || this._transition != null));
}
get _transition() {
return this.actor.get_transition('dim');
}
_ensureTransition() {
if (this._transition)
return;
let params = {
propertyName: '@effects.dim.brightness',
progressMode: Clutter.AnimationMode.LINEAR,
duration: (this._dimmed ? DIM_TIME : UNDIM_TIME),
removeOnComplete: true
};
this.actor.add_transition('dim',
new Clutter.PropertyTransition(params));
this._transition.connect('completed', this._syncEnabled.bind(this));
}
setEnabled(enabled) {
@ -132,14 +154,20 @@ var WindowDimmer = class {
this._syncEnabled();
}
set dimFactor(factor) {
this._dimFactor = factor;
this._brightnessEffect.set_brightness(factor * DIM_BRIGHTNESS);
this._syncEnabled();
}
setDimmed(dimmed, animate) {
this._dimmed = dimmed;
get dimFactor() {
return this._dimFactor;
let val = 127 * (1 + dimmed * DIM_BRIGHTNESS);
let color = Clutter.Color.new(val, val, val, 255);
if (animate) {
this._ensureTransition();
this._transition.set_to(color);
} else {
this._effect.brightness = color;
}
this._syncEnabled();
}
};
@ -412,15 +440,16 @@ var TilePreview = class {
this._showing = true;
this.actor.show();
Tweener.addTween(this.actor,
{ x: tileRect.x,
y: tileRect.y,
width: tileRect.width,
height: tileRect.height,
opacity: 255,
time: WINDOW_ANIMATION_TIME,
transition: 'easeOutQuad'
});
this.actor.ease({
x: tileRect.x,
y: tileRect.y,
width: tileRect.width,
height: tileRect.height,
opacity: 255
}, {
duration: WINDOW_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD
});
}
hide() {
@ -428,12 +457,13 @@ var TilePreview = class {
return;
this._showing = false;
Tweener.addTween(this.actor,
{ opacity: 0,
time: WINDOW_ANIMATION_TIME,
transition: 'easeOutQuad',
onComplete: this._reset.bind(this)
});
this.actor.ease({
opacity: 0
}, {
duration: WINDOW_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => this._reset()
});
}
_reset() {
@ -1135,15 +1165,16 @@ var WindowManager = class {
return;
let switchData = this._switchData;
this._switchData = null;
Tweener.addTween(switchData.container,
{ x: 0,
y: 0,
time: WINDOW_ANIMATION_TIME,
transition: 'easeOutQuad',
onComplete: this._finishWorkspaceSwitch,
onCompleteScope: this,
onCompleteParams: [switchData],
});
switchData.container.ease({
x: 0,
y: 0
}, {
time: WINDOW_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => {
this._finishWorkspaceSwitch(switchData);
}
});
}
_actionSwitchWorkspace(action, direction) {
@ -1313,17 +1344,18 @@ var WindowManager = class {
this._minimizing.push(actor);
if (actor.meta_window.is_monitor_sized()) {
Tweener.addTween(actor,
{ opacity: 0,
time: MINIMIZE_WINDOW_ANIMATION_TIME,
transition: 'easeOutQuad',
onComplete: this._minimizeWindowDone,
onCompleteScope: this,
onCompleteParams: [shellwm, actor],
onOverwrite: this._minimizeWindowOverwritten,
onOverwriteScope: this,
onOverwriteParams: [shellwm, actor]
});
actor.ease({
opacity: 0
}, {
duration: MINIMIZE_WINDOW_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onStopped: isFinished => {
if (isFinished)
this._minimizeWindowDone(shellwm, actor);
else
this._minimizeWindowOverwritten(shellwm, actor);
},
});
} else {
let xDest, yDest, xScale, yScale;
let [success, geom] = actor.meta_window.get_icon_geometry();
@ -1346,26 +1378,27 @@ var WindowManager = class {
yScale = 0;
}
Tweener.addTween(actor,
{ scale_x: xScale,
scale_y: yScale,
x: xDest,
y: yDest,
time: MINIMIZE_WINDOW_ANIMATION_TIME,
transition: 'easeInExpo',
onComplete: this._minimizeWindowDone,
onCompleteScope: this,
onCompleteParams: [shellwm, actor],
onOverwrite: this._minimizeWindowOverwritten,
onOverwriteScope: this,
onOverwriteParams: [shellwm, actor]
});
actor.ease({
scale_x: xScale,
scale_y: yScale,
x: xDest,
y: yDest
}, {
duration: MINIMIZE_WINDOW_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_IN_EXPO,
onStopped: isFinished => {
if (isFinished)
this._minimizeWindowDone(shellwm, actor);
else
this._minimizeWindowOverwritten(shellwm, actor);
}
});
}
}
_minimizeWindowDone(shellwm, actor) {
if (this._removeEffect(this._minimizing, actor)) {
Tweener.removeTweens(actor);
actor.remove_all_transitions();
actor.set_scale(1.0, 1.0);
actor.set_opacity(255);
actor.set_pivot_point(0, 0);
@ -1394,17 +1427,18 @@ var WindowManager = class {
if (actor.meta_window.is_monitor_sized()) {
actor.opacity = 0;
actor.set_scale(1.0, 1.0);
Tweener.addTween(actor,
{ opacity: 255,
time: MINIMIZE_WINDOW_ANIMATION_TIME,
transition: 'easeOutQuad',
onComplete: this._unminimizeWindowDone,
onCompleteScope: this,
onCompleteParams: [shellwm, actor],
onOverwrite: this._unminimizeWindowOverwritten,
onOverwriteScope: this,
onOverwriteParams: [shellwm, actor]
});
actor.ease({
opacity: 255
}, {
duration: MINIMIZE_WINDOW_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onStopped: isFinished => {
if (isFinished)
this._unminimizeWindowDone(shellwm, actor);
else
this._unminimizeWindowOverwritten(shellwm, actor);
}
});
} else {
let [success, geom] = actor.meta_window.get_icon_geometry();
if (success) {
@ -1428,26 +1462,27 @@ var WindowManager = class {
let [xDest, yDest] = [rect.x, rect.y];
actor.show();
Tweener.addTween(actor,
{ scale_x: 1.0,
scale_y: 1.0,
x: xDest,
y: yDest,
time: MINIMIZE_WINDOW_ANIMATION_TIME,
transition: 'easeInExpo',
onComplete: this._unminimizeWindowDone,
onCompleteScope: this,
onCompleteParams: [shellwm, actor],
onOverwrite: this._unminimizeWindowOverwritten,
onOverwriteScope: this,
onOverwriteParams: [shellwm, actor]
});
actor.ease({
scale_x: 1,
scale_y: 1,
x: xDest,
y: yDest
}, {
duration: MINIMIZE_WINDOW_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_IN_EXPO,
onStopped: isFinished => {
if (isFinished)
this._unminimizeWindowDone(shellwm, actor);
else
this._unminimizeWindowOverwritten(shellwm, actor);
}
});
}
}
_unminimizeWindowDone(shellwm, actor) {
if (this._removeEffect(this._unminimizing, actor)) {
Tweener.removeTweens(actor);
actor.remove_all_transitions();
actor.set_scale(1.0, 1.0);
actor.set_opacity(255);
actor.set_pivot_point(0, 0);
@ -1513,15 +1548,16 @@ var WindowManager = class {
this._resizing.push(actor);
// Now scale and fade out the clone
Tweener.addTween(actorClone,
{ x: targetRect.x,
y: targetRect.y,
scale_x: scaleX,
scale_y: scaleY,
opacity: 0,
time: WINDOW_ANIMATION_TIME,
transition: 'easeOutQuad'
});
actorClone.ease({
x: targetRect.x,
y: targetRect.y,
scale_x: scaleX,
scale_y: scaleY,
opacity: 0
}, {
duration: WINDOW_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD
});
actor.translation_x = -targetRect.x + sourceRect.x;
actor.translation_y = -targetRect.y + sourceRect.y;
@ -1531,20 +1567,21 @@ var WindowManager = class {
actor.scale_y = 1 / scaleY;
// Scale it to its actual new size
Tweener.addTween(actor,
{ scale_x: 1.0,
scale_y: 1.0,
translation_x: 0,
translation_y: 0,
time: WINDOW_ANIMATION_TIME,
transition: 'easeOutQuad',
onComplete: this._sizeChangeWindowDone,
onCompleteScope: this,
onCompleteParams: [shellwm, actor],
onOverwrite: this._sizeChangeWindowOverwritten,
onOverwriteScope: this,
onOverwriteParams: [shellwm, actor]
});
actor.ease({
scale_x: 1,
scale_y: 1,
translation_x: 0,
translation_y: 0
}, {
duration: WINDOW_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onStopped: isFinished => {
if (isFinished)
this._sizeChangeWindowDone(shellwm, actor);
else
this._sizeChangeWindowOverwritten(shellwm, actor);
}
});
// Now unfreeze actor updates, to get it to the new size.
// It's important that we don't wait until the animation is completed to
@ -1564,7 +1601,7 @@ var WindowManager = class {
_sizeChangeWindowDone(shellwm, actor) {
if (this._removeEffect(this._resizing, actor)) {
Tweener.removeTweens(actor);
actor.remove_all_transitions();
actor.scale_x = 1.0;
actor.scale_y = 1.0;
actor.translation_x = 0;
@ -1614,14 +1651,7 @@ var WindowManager = class {
let dimmer = getWindowDimmer(actor);
if (!dimmer)
return;
if (this._shouldAnimate())
Tweener.addTween(dimmer,
{ dimFactor: 1.0,
time: DIM_TIME,
transition: 'linear'
});
else
dimmer.dimFactor = 1.0;
dimmer.setDimmed(true, this._shouldAnimate());
}
_undimWindow(window) {
@ -1631,13 +1661,7 @@ var WindowManager = class {
let dimmer = getWindowDimmer(actor);
if (!dimmer)
return;
if (this._shouldAnimate())
Tweener.addTween(dimmer,
{ dimFactor: 0.0,
time: UNDIM_TIME,
transition: 'linear' });
else
dimmer.dimFactor = 0.0;
dimmer.setDimmed(false, this._shouldAnimate());
}
_mapWindow(shellwm, actor) {
@ -1682,19 +1706,20 @@ var WindowManager = class {
actor.show();
this._mapping.push(actor);
Tweener.addTween(actor,
{ opacity: 255,
scale_x: 1,
scale_y: 1,
time: SHOW_WINDOW_ANIMATION_TIME,
transition: 'easeOutExpo',
onComplete: this._mapWindowDone,
onCompleteScope: this,
onCompleteParams: [shellwm, actor],
onOverwrite: this._mapWindowOverwrite,
onOverwriteScope: this,
onOverwriteParams: [shellwm, actor]
});
actor.ease({
opacity: 255,
scale_x: 1,
scale_y: 1
}, {
duration: SHOW_WINDOW_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_EXPO,
onStopped: isFinished => {
if (isFinished)
this._mapWindowDone(shellwm, actor);
else
this._mapWindowOverwrite(shellwm, actor);
}
});
break;
case Meta.WindowType.MODAL_DIALOG:
case Meta.WindowType.DIALOG:
@ -1704,19 +1729,20 @@ var WindowManager = class {
actor.show();
this._mapping.push(actor);
Tweener.addTween(actor,
{ opacity: 255,
scale_x: 1,
scale_y: 1,
time: DIALOG_SHOW_WINDOW_ANIMATION_TIME,
transition: 'easeOutQuad',
onComplete: this._mapWindowDone,
onCompleteScope: this,
onCompleteParams: [shellwm, actor],
onOverwrite: this._mapWindowOverwrite,
onOverwriteScope: this,
onOverwriteParams: [shellwm, actor]
});
actor.ease({
opacity: 255,
scale_x: 1,
scale_y: 1
}, {
duration: DIALOG_SHOW_WINDOW_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onStopped: isFinished => {
if (isFinished)
this._mapWindowDone(shellwm, actor);
else
this._mapWindowOverwrite(shellwm, actor);
}
});
break;
default:
shellwm.completed_map(actor);
@ -1726,7 +1752,7 @@ var WindowManager = class {
_mapWindowDone(shellwm, actor) {
if (this._removeEffect(this._mapping, actor)) {
Tweener.removeTweens(actor);
actor.remove_all_transitions();
actor.opacity = 255;
actor.set_pivot_point(0, 0);
actor.scale_y = 1;
@ -1770,19 +1796,17 @@ var WindowManager = class {
actor.set_pivot_point(0.5, 0.5);
this._destroying.push(actor);
Tweener.addTween(actor,
{ opacity: 0,
scale_x: 0.8,
scale_y: 0.8,
time: DESTROY_WINDOW_ANIMATION_TIME,
transition: 'easeOutQuad',
onComplete: this._destroyWindowDone,
onCompleteScope: this,
onCompleteParams: [shellwm, actor],
onOverwrite: this._destroyWindowDone,
onOverwriteScope: this,
onOverwriteParams: [shellwm, actor]
});
actor.ease({
opacity: 0,
scale_x: 0.8,
scale_y: 0.8
}, {
duration: DESTROY_WINDOW_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onStopped: () => {
this._destroyWindowDone(shellwm, actor);
}
});
break;
case Meta.WindowType.MODAL_DIALOG:
case Meta.WindowType.DIALOG:
@ -1792,22 +1816,20 @@ var WindowManager = class {
if (window.is_attached_dialog()) {
let parent = window.get_transient_for();
actor._parentDestroyId = parent.connect('unmanaged', () => {
Tweener.removeTweens(actor);
actor.remove_all_transitions();
this._destroyWindowDone(shellwm, actor);
});
}
Tweener.addTween(actor,
{ scale_y: 0,
time: DIALOG_DESTROY_WINDOW_ANIMATION_TIME,
transition: 'easeOutQuad',
onComplete: this._destroyWindowDone,
onCompleteScope: this,
onCompleteParams: [shellwm, actor],
onOverwrite: this._destroyWindowDone,
onOverwriteScope: this,
onOverwriteParams: [shellwm, actor]
});
actor.ease({
scale_y: 0
}, {
duration: DIALOG_DESTROY_WINDOW_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onStopped: () => {
this._destroyWindowDone(shellwm, actor);
}
});
break;
default:
shellwm.completed_destroy(actor);
@ -2045,15 +2067,16 @@ var WindowManager = class {
xDest = -xDest;
yDest = -yDest;
Tweener.addTween(this._switchData.container,
{ x: xDest,
y: yDest,
time: WINDOW_ANIMATION_TIME,
transition: 'easeOutQuad',
onComplete: this._switchWorkspaceDone,
onCompleteScope: this,
onCompleteParams: [shellwm]
});
this._switchData.container.ease({
x: xDest,
y: yDest
}, {
time: WINDOW_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => {
this._switchWorkspaceDone(shellwm);
}
});
}
_switchWorkspaceDone(shellwm) {