[gnome-shell/wip/exalm/gestures2: 10/10] workspaceAnimation: Support multiple screens



commit d986bfeba7b86f81bd48ae31c0eaf0c63ee20a7e
Author: Alexander Mikhaylenko <exalm7659 gmail com>
Date:   Sun Jun 21 00:43:06 2020 +0500

    workspaceAnimation: Support multiple screens
    
    Currently, there's one animation for the whole canvas. While it looks fine
    with just one screen, it causes windows to move between screens when
    switching workspaces. Instead, have a separate animation on each screen,
    and sync their progress so that at any given time the progress "fraction"
    is the same between all screens. Clip all animations to their screens so
    that the windows don't leak to other screens.
    
    If a window is placed between every screen, can end up in multiple
    animations, in that case each part is still animated separately.
    
    Fixes https://gitlab.gnome.org/GNOME/gnome-shell/issues/1213
    
    https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1326

 js/ui/workspaceAnimation.js | 358 +++++++++++++++++++++++++++-----------------
 1 file changed, 219 insertions(+), 139 deletions(-)
---
diff --git a/js/ui/workspaceAnimation.js b/js/ui/workspaceAnimation.js
index c875b2242d..280840e54e 100644
--- a/js/ui/workspaceAnimation.js
+++ b/js/ui/workspaceAnimation.js
@@ -12,10 +12,11 @@ const WINDOW_ANIMATION_TIME = 250;
 
 const WorkspaceGroup = GObject.registerClass(
 class WorkspaceGroup extends Clutter.Actor {
-    _init(workspace, movingWindow) {
+    _init(workspace, monitor, movingWindow) {
         super._init();
 
         this._workspace = workspace;
+        this._monitor = monitor;
         this._movingWindow = movingWindow;
         this._windowRecords = [];
 
@@ -30,6 +31,11 @@ class WorkspaceGroup extends Clutter.Actor {
         if (!window.showing_on_its_workspace())
             return false;
 
+        const geometry = global.display.get_monitor_geometry(this._monitor.index);
+        const [intersects, intersection_] = window.get_frame_rect().intersect(geometry);
+        if (!intersects)
+            return false;
+
         const isSticky =
             window.is_on_all_workspaces() || window === this._movingWindow;
 
@@ -62,8 +68,8 @@ class WorkspaceGroup extends Clutter.Actor {
         for (const windowActor of windowActors) {
             const clone = new Clutter.Clone({
                 source: windowActor,
-                x: windowActor.x,
-                y: windowActor.y,
+                x: windowActor.x - this._monitor.x,
+                y: windowActor.y - this._monitor.y,
             });
 
             this.add_child(clone);
@@ -94,10 +100,20 @@ class WorkspaceGroup extends Clutter.Actor {
     }
 });
 
-const MonitorGroup = GObject.registerClass(
-class MonitorGroup extends Clutter.Actor {
-    _init(monitor) {
-        super._init();
+const MonitorGroup = GObject.registerClass({
+    Properties: {
+        'position': GObject.ParamSpec.double(
+            'position', 'position', 'position',
+            GObject.ParamFlags.READWRITE,
+            0, Infinity, 0),
+    },
+}, class MonitorGroup extends Clutter.Actor {
+    _init(monitor, workspaceIndices, movingWindow) {
+        super._init({
+            clip_to_allocation: true,
+        });
+
+        this._monitor = monitor;
 
         const constraint = new Layout.MonitorConstraint({ index: monitor.index });
         this.add_constraint(constraint);
@@ -106,6 +122,56 @@ class MonitorGroup extends Clutter.Actor {
 
         this.add_child(background);
 
+        this._container = new Clutter.Actor();
+        this.add_child(this._container);
+
+        const stickyGroup = new WorkspaceGroup(null, monitor, movingWindow);
+        this.add_child(stickyGroup);
+
+        this._workspaces = [];
+
+        const workspaceManager = global.workspace_manager;
+        const vertical = workspaceManager.layout_rows === -1;
+        const activeWorkspaceIndex = workspaceManager.get_active_workspace_index();
+
+        let x = 0;
+        let y = 0;
+
+        for (const i of workspaceIndices) {
+            const ws = workspaceManager.get_workspace_by_index(i);
+            const fullscreen = ws.list_windows().some(w => w.get_monitor() === monitor.index && 
w.is_fullscreen());
+
+            if (i > 0 && vertical && !fullscreen && monitor.index === Main.layoutManager.primaryIndex) {
+                // We have to shift windows up or down by the height of the panel to prevent having a
+                // visible gap between the windows while switching workspaces. Since fullscreen windows
+                // hide the panel, they don't need to be shifted up or down.
+                y -= Main.panel.height;
+            }
+
+            const info = {
+                ws,
+                actor: new WorkspaceGroup(ws, monitor, movingWindow),
+                x,
+                y,
+            };
+
+            this._workspaces.push(info);
+            this._container.add_child(info.actor);
+            info.actor.set_position(x, y);
+
+            if (vertical)
+                y += this.baseDistance;
+            else if (Clutter.get_default_text_direction() === Clutter.TextDirection.RTL)
+                x -= this.baseDistance;
+            else
+                x += this.baseDistance;
+        }
+
+        this.set_child_above_sibling(stickyGroup, null);
+
+        const activeInfo = this.findWorkspaceInfoByIndex(activeWorkspaceIndex);
+        this.position = this.getWorkspacePosition(activeInfo);
+
         this._bgManager = new Background.BackgroundManager({
             container: background,
             monitorIndex: monitor.index,
@@ -118,6 +184,54 @@ class MonitorGroup extends Clutter.Actor {
     _onDestroy() {
         this._bgManager.destroy();
     }
+
+    get baseDistance() {
+        if (global.workspace_manager.layout_rows === -1)
+            return this._monitor.height;
+        else
+            return this._monitor.width;
+    }
+
+    get position() {
+        if (global.workspace_manager.layout_rows === -1)
+            return -this._container.y;
+        else if (this.get_text_direction() === Clutter.TextDirection.RTL)
+            return this._container.x;
+        else
+            return -this._container.x;
+    }
+
+    set position(p) {
+        if (global.workspace_manager.layout_rows === -1)
+            this._container.y = -p;
+        else if (this.get_text_direction() === Clutter.TextDirection.RTL)
+            this._container.x = p;
+        else
+            this._container.x = -p;
+    }
+
+    get index() {
+        return this._monitor.index;
+    }
+
+    findWorkspaceInfoByIndex(index) {
+        const workspace = global.workspace_manager.get_workspace_by_index(index);
+        return this._workspaces.find(ws => ws.ws === workspace);
+    }
+
+    getWorkspacePosition(workspaceInfo) {
+        if (global.workspace_manager.layout_rows === -1)
+            return workspaceInfo.y;
+        else if (this.get_text_direction() === Clutter.TextDirection.RTL)
+            return -workspaceInfo.x;
+        else
+            return workspaceInfo.x;
+    }
+
+    getSnapPoints() {
+        return this._workspaces.map(ws =>
+            this.getWorkspacePosition(ws) / this.baseDistance);
+    }
 });
 
 var WorkspaceAnimationController = class {
@@ -149,88 +263,48 @@ var WorkspaceAnimationController = class {
             return;
 
         const workspaceManager = global.workspace_manager;
-        const vertical = workspaceManager.layout_rows === -1;
         const nWorkspaces = workspaceManager.get_n_workspaces();
-        const activeWorkspaceIndex = workspaceManager.get_active_workspace_index();
 
         const switchData = {};
 
         this._switchData = switchData;
-        switchData.stickyGroup = new WorkspaceGroup(null, this.movingWindow);
-        switchData.workspaces = [];
+        switchData.monitors = {};
+
         switchData.gestureActivated = false;
         switchData.inProgress = false;
 
-        switchData.container = new Clutter.Actor();
-        switchData.backgrounds = [];
-
-        for (const monitor of Main.layoutManager.monitors) {
-            const background = new MonitorGroup(monitor);
-            switchData.backgrounds.push(background);
-
-            Main.uiGroup.insert_child_above(background, global.window_group);
-        }
-
-        Main.uiGroup.insert_child_above(switchData.container, switchData.backgrounds[0]);
-        Main.uiGroup.insert_child_above(switchData.stickyGroup, switchData.container);
-
-        let x = 0;
-        let y = 0;
-
         if (!workspaceIndices)
             workspaceIndices = [...Array(nWorkspaces).keys()];
 
-        for (const i of workspaceIndices) {
-            const ws = workspaceManager.get_workspace_by_index(i);
-            const fullscreen = ws.list_windows().some(w => w.is_fullscreen());
+        const monitors = Meta.prefs_get_workspaces_only_on_primary()
+            ? [Main.layoutManager.primaryMonitor] : Main.layoutManager.monitors;
 
-            if (y > 0 && vertical && !fullscreen) {
-                // We have to shift windows up or down by the height of the panel to prevent having a
-                // visible gap between the windows while switching workspaces. Since fullscreen windows
-                // hide the panel, they don't need to be shifted up or down.
-                y -= Main.panel.height;
-            }
+        for (const monitor of monitors) {
+            if (Meta.prefs_get_workspaces_only_on_primary() &&
+                monitor.index !== Main.layoutManager.primaryIndex)
+                continue;
 
-            const info = {
-                ws,
-                actor: new WorkspaceGroup(ws, this.movingWindow),
-                x,
-                y,
-            };
+            const group = new MonitorGroup(monitor, workspaceIndices, this.movingWindow);
 
-            switchData.workspaces.push(info);
-            switchData.container.add_child(info.actor);
-            switchData.container.set_child_above_sibling(info.actor, null);
-            info.actor.set_position(x, y);
+            Main.uiGroup.insert_child_above(group, global.window_group);
 
-            if (vertical)
-                y += global.screen_height;
-            else if (Clutter.get_default_text_direction() === Clutter.TextDirection.RTL)
-                x -= global.screen_width;
-            else
-                x += global.screen_width;
+            switchData.monitors[monitor.index] = group;
         }
-
-        const activeInfo = this._findWorkspaceInfoByIndex(activeWorkspaceIndex);
-        if (vertical)
-            switchData.container.y = -activeInfo.y;
-        else
-            switchData.container.x = -activeInfo.x;
     }
 
     _finishWorkspaceSwitch(switchData) {
         this._switchData = null;
 
-        switchData.backgrounds.forEach(bg => bg.destroy());
-        switchData.container.destroy();
-        switchData.stickyGroup.destroy();
+        Object.values(switchData.monitors).forEach(m => m.destroy());
 
         this.movingWindow = null;
     }
 
     animateSwitch(from, to, direction, onComplete) {
-        if (this._switchData)
-            this._switchData.container.remove_all_transitions();
+        if (this._switchData) {
+            for (const monitorGroup of Object.values(this._switchData.monitors))
+                monitorGroup.remove_all_transitions();
+        }
 
         let workspaceIndices = [];
 
@@ -258,44 +332,34 @@ var WorkspaceAnimationController = class {
         this._prepareWorkspaceSwitch(workspaceIndices);
         this._switchData.inProgress = true;
 
-        const fromInfo = this._findWorkspaceInfoByIndex(from);
-        const toInfo = this._findWorkspaceInfoByIndex(to);
-
-        this._switchData.container.x = -fromInfo.x;
-        this._switchData.container.y = -fromInfo.y;
-
-        this._switchData.container.ease({
-            x: -toInfo.x,
-            y: -toInfo.y,
-            duration: WINDOW_ANIMATION_TIME,
-            mode: Clutter.AnimationMode.EASE_OUT_CUBIC,
-            onComplete: () => {
-                this._finishWorkspaceSwitch(this._switchData);
-                onComplete();
-            },
-        });
-    }
+        for (const monitorGroup of Object.values(this._switchData.monitors)) {
+            const fromInfo = monitorGroup.findWorkspaceInfoByIndex(from);
+            const toInfo = monitorGroup.findWorkspaceInfoByIndex(to);
 
-    _getProgressForWorkspace(workspaceInfo) {
-        if (global.workspace_manager.layout_rows === -1)
-            return workspaceInfo.y / global.screen_height;
-        else if (Clutter.get_default_text_direction() === Clutter.TextDirection.RTL)
-            return -workspaceInfo.x / global.screen_width;
-        else
-            return workspaceInfo.x / global.screen_width;
-    }
+            monitorGroup.position = monitorGroup.getWorkspacePosition(fromInfo);
+            const position = monitorGroup.getWorkspacePosition(toInfo);
+
+            const params = {
+                duration: WINDOW_ANIMATION_TIME,
+                mode: Clutter.AnimationMode.EASE_OUT_CUBIC,
+            };
 
-    _findWorkspaceInfoByIndex(index) {
-        return this._switchData.workspaces.find(ws => ws.ws.index() === index);
+            if (monitorGroup.index === Main.layoutManager.primaryIndex) {
+                params.onComplete = () => {
+                    this._finishWorkspaceSwitch(this._switchData);
+                    onComplete();
+                };
+            }
+
+            monitorGroup.ease_property('position', position, params);
+        }
     }
 
-    _findClosestWorkspace(progress) {
-        const distances = this._switchData.workspaces.map(ws => {
-            const workspaceProgress = this._getProgressForWorkspace(ws);
-            return Math.abs(workspaceProgress - progress);
-        });
-        const index = distances.indexOf(Math.min(...distances));
-        return this._switchData.workspaces[index];
+    _findClosestWorkspaceIndex(progress, monitorIndex) {
+        const monitorGroup = this._switchData.monitors[monitorIndex];
+        const distances = monitorGroup.getSnapPoints().map(p =>
+            Math.abs(p - progress));
+        return distances.indexOf(Math.min(...distances));
     }
 
     _switchWorkspaceBegin(tracker, monitor) {
@@ -309,72 +373,88 @@ var WorkspaceAnimationController = class {
             ? Clutter.Orientation.HORIZONTAL
             : Clutter.Orientation.VERTICAL;
 
-        const baseDistance = horiz ? global.screen_width : global.screen_height;
-
-        let progress;
-        let cancelProgress;
         if (this._switchData && this._switchData.gestureActivated) {
-            this._switchData.container.remove_all_transitions();
-            if (!horiz)
-                progress = -this._switchData.container.y / baseDistance;
-            else if (Clutter.get_default_text_direction() === Clutter.TextDirection.RTL)
-                progress = this._switchData.container.x / baseDistance;
-            else
-                progress = -this._switchData.container.x / baseDistance;
-
-            const ws = this._findClosestWorkspace(progress);
-            cancelProgress = this._getProgressForWorkspace(ws);
+            for (const group of Object.values(this._switchData.monitors))
+                group.remove_all_transitions();
         } else {
             this._prepareWorkspaceSwitch();
+        }
 
-            const activeIndex = workspaceManager.get_active_workspace_index();
-            const ws = this._findWorkspaceInfoByIndex(activeIndex);
+        this._switchData.baseMonitor = monitor;
 
-            progress = cancelProgress = this._getProgressForWorkspace(ws);
-        }
+        const monitorGroup = this._switchData.monitors[monitor];
+        const baseDistance = monitorGroup.baseDistance;
+        const progress = monitorGroup.position / baseDistance;
 
-        const points = this._switchData.workspaces.map(this._getProgressForWorkspace);
+        const closestIndex = this._findClosestWorkspaceIndex(progress, monitor);
+        const workspaceInfo = monitorGroup.findWorkspaceInfoByIndex(closestIndex);
+
+        const cancelProgress = monitorGroup.getWorkspacePosition(workspaceInfo) / baseDistance;
+        const points = monitorGroup.getSnapPoints();
 
         tracker.confirmSwipe(baseDistance, points, progress, cancelProgress);
     }
 
+    _interpolateProgress(progress, monitorGroup) {
+        const monitor1 = this._switchData.monitors[this._switchData.baseMonitor];
+        const monitor2 = monitorGroup;
+
+        if (monitor1.index === monitor2.index)
+            return progress;
+
+        const points1 = monitor1.getSnapPoints();
+        const points2 = monitor2.getSnapPoints();
+
+        const upper = points1.indexOf(points1.find(p => p >= progress));
+        const lower = points1.indexOf(points1.slice().reverse().find(p => p <= progress));
+
+        if (points1[upper] === points1[lower])
+            return points2[upper];
+
+        const t = (progress - points1[lower]) / (points1[upper] - points1[lower]);
+
+        return points2[lower] + (points2[upper] - points2[lower]) * t;
+    }
+
     _switchWorkspaceUpdate(tracker, progress) {
         if (!this._switchData)
             return;
 
-        let xPos = 0;
-        let yPos = 0;
-
-        if (global.workspace_manager.layout_rows === -1)
-            yPos = -Math.round(progress * global.screen_height);
-        else if (Clutter.get_default_text_direction() === Clutter.TextDirection.RTL)
-            xPos = Math.round(progress * global.screen_width);
-        else
-            xPos = -Math.round(progress * global.screen_width);
+        for (const monitorGroup of Object.values(this._switchData.monitors)) {
+            const p = this._interpolateProgress(progress, monitorGroup);
 
-        this._switchData.container.set_position(xPos, yPos);
+            monitorGroup.position = Math.round(p * monitorGroup.baseDistance);
+        }
     }
 
     _switchWorkspaceEnd(tracker, duration, endProgress) {
         if (!this._switchData)
             return;
 
-        const newWs = this._findClosestWorkspace(endProgress);
-
         const switchData = this._switchData;
         switchData.gestureActivated = true;
 
-        this._switchData.container.ease({
-            x: -newWs.x,
-            y: -newWs.y,
-            duration,
-            mode: Clutter.AnimationMode.EASE_OUT_CUBIC,
-            onComplete: () => {
-                if (!newWs.ws.active)
-                    newWs.ws.activate(global.get_current_time());
-                this._finishWorkspaceSwitch(switchData);
-            },
-        });
+        const index = this._findClosestWorkspaceIndex(endProgress, switchData.baseMonitor);
+
+        for (const monitorGroup of Object.values(this._switchData.monitors)) {
+            const newWs = monitorGroup.findWorkspaceInfoByIndex(index);
+            const position = monitorGroup.getWorkspacePosition(newWs);
+
+            const params = {
+                duration,
+                mode: Clutter.AnimationMode.EASE_OUT_CUBIC,
+            };
+
+            if (monitorGroup.index === Main.layoutManager.primaryIndex) {
+                params.onComplete = () => {
+                    if (!newWs.ws.active)
+                        newWs.ws.activate(global.get_current_time());
+                    this._finishWorkspaceSwitch(switchData);
+                };
+            }
+
+            monitorGroup.ease_property('position', position, params);
+        }
     }
 
     get gestureActive() {


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]