From ebe6f59d7e3c3b520b5d52ddf39be6a64ec3f80b Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Fri, 13 Jul 2018 23:38:03 +0200 Subject: [PATCH] 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 --- js/ui/windowManager.js | 126 ++++++++++++++++++++++++++++++++++------- 1 file changed, 107 insertions(+), 19 deletions(-) diff --git a/js/ui/windowManager.js b/js/ui/windowManager.js index 18f393dda..a88d4f9a0 100644 --- a/js/ui/windowManager.js +++ b/js/ui/windowManager.js @@ -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,