Commit ebe6f59d authored by Carlos Garnacho's avatar Carlos Garnacho Committed by Florian Müllner

windowManager: Make workspace switching gestures follow motion

When 4fg swipe motion happens, set up early the workspace switching
animation with all surrounding workspaces. This allows us to move
all content back and forth in any direction. This works on both
touchcreens and touchpads.

When the gesture is activated, the same data is reused to follow
up with the tween animation.

The threshold has been also doubled, it was fairly small to start
with, and feels better now that workspaces stick to fingers.

https://bugzilla.gnome.org/show_bug.cgi?id=788994
parent e5c95b91
......@@ -3,6 +3,7 @@
const Clutter = imports.gi.Clutter;
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const GObject = imports.gi.GObject;
const Lang = imports.lang;
const Mainloop = imports.mainloop;
const Meta = imports.gi.Meta;
......@@ -34,6 +35,7 @@ var WINDOW_ANIMATION_TIME = 0.25;
var DIM_BRIGHTNESS = -0.3;
var DIM_TIME = 0.500;
var UNDIM_TIME = 0.250;
var MOTION_THRESHOLD = 100;
var ONE_SECOND = 1000; // in ms
......@@ -495,13 +497,8 @@ var TouchpadWorkspaceSwitchAction = new Lang.Class({
},
_checkActivated() {
const MOTION_THRESHOLD = 50;
let allowedModes = Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW;
let dir;
if ((allowedModes & Main.actionMode) == 0)
return;
if (this._dy < -MOTION_THRESHOLD)
dir = Meta.MotionDirection.DOWN;
else if (this._dy > MOTION_THRESHOLD)
......@@ -511,26 +508,35 @@ var TouchpadWorkspaceSwitchAction = new Lang.Class({
else if (this._dx > MOTION_THRESHOLD)
dir = Meta.MotionDirection.LEFT;
else
return;
return false;
this.emit('activated', dir);
return true;
},
_handleEvent(actor, event) {
let allowedModes = Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW;
if (event.type() != Clutter.EventType.TOUCHPAD_SWIPE)
return Clutter.EVENT_PROPAGATE;
if (event.get_touchpad_gesture_finger_count() != 4)
return Clutter.EVENT_PROPAGATE;
if ((allowedModes & Main.actionMode) == 0)
return Clutter.EVENT_PROPAGATE;
if (event.get_gesture_phase() == Clutter.TouchpadGesturePhase.UPDATE) {
let [dx, dy] = event.get_gesture_motion_delta();
this._dx += dx;
this._dy += dy;
// Scale deltas up a bit to make it feel snappier
this._dx += dx * 2;
this._dy += dy * 2;
this.emit('motion', this._dx, this._dy);
} else {
if (event.get_gesture_phase() == Clutter.TouchpadGesturePhase.END)
this._checkActivated();
if ((event.get_gesture_phase() == Clutter.TouchpadGesturePhase.END && ! this._checkActivated()) ||
event.get_gesture_phase() == Clutter.TouchpadGesturePhase.CANCEL)
this.emit('cancel');
this._dx = 0;
this._dy = 0;
......@@ -544,14 +550,14 @@ Signals.addSignalMethods(TouchpadWorkspaceSwitchAction.prototype);
var WorkspaceSwitchAction = new Lang.Class({
Name: 'WorkspaceSwitchAction',
Extends: Clutter.SwipeAction,
Signals: { 'activated': { param_types: [Meta.MotionDirection.$gtype] } },
Signals: { 'activated': { param_types: [Meta.MotionDirection.$gtype] },
'motion': { param_types: [GObject.TYPE_DOUBLE, GObject.TYPE_DOUBLE] },
'cancel': { param_types: [] }},
_init() {
const MOTION_THRESHOLD = 50;
this.parent();
this.set_n_touch_points(4);
this.set_threshold_trigger_distance(MOTION_THRESHOLD, MOTION_THRESHOLD);
this._swept = false;
global.display.connect('grab-op-begin', () => {
this.cancel();
......@@ -561,13 +567,35 @@ var WorkspaceSwitchAction = new Lang.Class({
vfunc_gesture_prepare(actor) {
let allowedModes = Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW;
this._swept = false;
if (!this.parent(actor))
return false;
return (allowedModes & Main.actionMode);
},
vfunc_swept(actor, direction) {
vfunc_gesture_progress(actor) {
let [x, y] = this.get_motion_coords(0);
let [xPress, yPress] = this.get_press_coords(0);
this.emit('motion', x - xPress, y - yPress);
return true;
},
vfunc_gesture_cancel(actor) {
if (!this._swept)
this.emit('cancel');
},
vfunc_swipe(actor, direction) {
let [x, y] = this.get_motion_coords(0);
let [xPress, yPress] = this.get_press_coords(0);
if (Math.abs(x - xPress) < MOTION_THRESHOLD &&
Math.abs(y - yPress) < MOTION_THRESHOLD) {
this.emit('cancel');
return;
}
let dir;
if (direction & Clutter.SwipeDirection.UP)
......@@ -579,6 +607,7 @@ var WorkspaceSwitchAction = new Lang.Class({
else if (direction & Clutter.SwipeDirection.RIGHT)
dir = Meta.MotionDirection.LEFT;
this._swept = true;
this.emit('activated', dir);
}
});
......@@ -700,7 +729,14 @@ var WindowManager = new Lang.Class({
this._isWorkspacePrepended = false;
this._switchData = null;
this._shellwm.connect('kill-switch-workspace', this._switchWorkspaceDone.bind(this));
this._shellwm.connect('kill-switch-workspace', (shellwm) => {
if (this._switchData) {
if (this._switchData.inProgress)
this._switchWorkspaceDone(shellwm);
else if (!this._switchData.gestureActivated)
this._finishWorkspaceSwitch(this._switchData);
}
});
this._shellwm.connect('kill-window-effects', (shellwm, actor) => {
this._minimizeWindowDone(shellwm, actor);
this._mapWindowDone(shellwm, actor);
......@@ -982,12 +1018,16 @@ var WindowManager = new Lang.Class({
false, -1, 1);
let gesture = new WorkspaceSwitchAction();
gesture.connect('motion', this._switchWorkspaceMotion.bind(this));
gesture.connect('activated', this._actionSwitchWorkspace.bind(this));
gesture.connect('cancel', this._switchWorkspaceCancel.bind(this));
global.stage.add_action(gesture);
// This is not a normal Clutter.GestureAction, doesn't need add_action()
gesture = new TouchpadWorkspaceSwitchAction(global.stage);
gesture.connect('motion', this._switchWorkspaceMotion.bind(this));
gesture.connect('activated', this._actionSwitchWorkspace.bind(this));
gesture.connect('cancel', this._switchWorkspaceCancel.bind(this));
gesture = new AppSwitchAction();
gesture.connect('activated', this._switchApp.bind(this));
......@@ -1016,11 +1056,52 @@ var WindowManager = new Lang.Class({
return this._currentPadOsd.actor;
},
_switchWorkspaceMotion(action, xRel, yRel) {
let workspaceManager = global.workspace_manager;
let activeWorkspace = workspaceManager.get_active_workspace();
if (!this._switchData)
this._prepareWorkspaceSwitch(activeWorkspace.index(), -1);
if (yRel < 0 && !this._switchData.surroundings[Meta.MotionDirection.DOWN])
yRel = 0;
if (yRel > 0 && !this._switchData.surroundings[Meta.MotionDirection.UP])
yRel = 0;
if (xRel < 0 && !this._switchData.surroundings[Meta.MotionDirection.RIGHT])
xRel = 0;
if (xRel > 0 && !this._switchData.surroundings[Meta.MotionDirection.LEFT])
xRel = 0;
this._switchData.container.set_position(xRel, yRel);
},
_switchWorkspaceCancel() {
if (!this._switchData || this._switchData.inProgress)
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],
});
},
_actionSwitchWorkspace(action, direction) {
let workspaceManager = global.workspace_manager;
let activeWorkspace = workspaceManager.get_active_workspace();
let newWs = activeWorkspace.get_neighbor(direction);
this.actionMoveWorkspace(newWs);
if (newWs == activeWorkspace) {
this._switchWorkspaceCancel();
} else {
this._switchData.gestureActivated = true;
this.actionMoveWorkspace(newWs);
}
},
_lookupIndex(windows, metaWindow) {
......@@ -1762,6 +1843,8 @@ var WindowManager = new Lang.Class({
switchData.movingWindowBin = new Clutter.Actor();
switchData.windows = [];
switchData.surroundings = {};
switchData.gestureActivated = false;
switchData.inProgress = false;
switchData.container = new Clutter.Actor();
switchData.container.add_actor(switchData.curGroup);
......@@ -1857,6 +1940,13 @@ var WindowManager = new Lang.Class({
return;
}
// If we come from a gesture, switchData will already be set,
// and we don't want to overwrite it.
if (!this._switchData)
this._prepareWorkspaceSwitch(from, to, direction);
this._switchData.inProgress = true;
let [xDest, yDest] = this._getPositionForDirection(direction);
/* @direction is the direction that the "camera" moves, so the
......@@ -1866,8 +1956,6 @@ var WindowManager = new Lang.Class({
xDest = -xDest;
yDest = -yDest;
this._prepareWorkspaceSwitch(from, to, direction);
Tweener.addTween(this._switchData.container,
{ x: xDest,
y: yDest,
......
  • Raffaele
    @r started a discussion
    Last updated by Mikhail Podshivalin
    • Something like this for the builtin 3-finger "overview" gesture would also be great (though I see it might be more difficult to implement)

    • Meanwhile you can use this extension for 3-finger "overview" https://extensions.gnome.org/extension/1253/extended-gestures/

    • No, that extension doesn't animate the overview gesture (or any gesture), it allows to customize which action is triggered by which gesture.

    • Yes, you're right - it doesn't animate any gestures, but it's better than having no overview gesture :) New animated 4-finger workspace switch is amazing, I've waited really long for that and I'd certainly like to have the same effect for overview.

      There is however one bug with this new gesture and overview - #644 (closed)

    • it's better than having no overview gesture :)

      But there is a built-in overview gesture :)

    • Wow, big thanks for the info! Somehow I didn't know about this gesture.

    Please register or sign in to reply
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment