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



commit d8f643293048b5658563bfa1585f2165705266c8
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 | 301 ++++++++++++++++++++++++++++----------------
 1 file changed, 193 insertions(+), 108 deletions(-)
---
diff --git a/js/ui/workspaceAnimation.js b/js/ui/workspaceAnimation.js
index 93ff8edbe9..a4b98cd027 100644
--- a/js/ui/workspaceAnimation.js
+++ b/js/ui/workspaceAnimation.js
@@ -1,20 +1,22 @@
 // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
 /* exported WorkspaceAnimationController */
 
-const { Clutter, GObject, Meta, Shell } = imports.gi;
+const { Clutter, GObject, Meta, Shell, St } = imports.gi;
 
 const Main = imports.ui.main;
+const Layout = imports.ui.layout;
 const SwipeTracker = imports.ui.swipeTracker;
 
 const WINDOW_ANIMATION_TIME = 250;
 
 const WorkspaceGroup = GObject.registerClass(
 class WorkspaceGroup extends Clutter.Actor {
-    _init(controller, workspace) {
+    _init(controller, workspace, monitor) {
         super._init();
 
         this._controller = controller;
         this._workspace = workspace;
+        this._monitor = monitor;
         this._windows = [];
 
         this._refreshWindows();
@@ -31,6 +33,11 @@ class WorkspaceGroup extends Clutter.Actor {
         if (!window.showing_on_its_workspace())
             return false;
 
+        let geometry = global.display.get_monitor_geometry(this._monitor.index);
+        let [intersects, intersection_] = window.get_frame_rect().intersect(geometry);
+        if (!intersects)
+            return false;
+
         if (window.is_on_all_workspaces())
             return false;
 
@@ -51,8 +58,8 @@ class WorkspaceGroup extends Clutter.Actor {
         for (let window of windows) {
             let clone = new Clutter.Clone({
                 source: window,
-                x: window.x,
-                y: window.y,
+                x: window.x - this._monitor.x,
+                y: window.y - this._monitor.y,
             });
 
             this.add_actor(clone);
@@ -91,10 +98,11 @@ class WorkspaceGroup extends Clutter.Actor {
 
 const StickyGroup = GObject.registerClass(
 class StickyGroup extends Clutter.Actor {
-    _init(controller) {
+    _init(controller, monitor) {
         super._init();
 
         this._controller = controller;
+        this._monitor = monitor;
         this._windows = [];
 
         this._refreshWindows();
@@ -108,6 +116,11 @@ class StickyGroup extends Clutter.Actor {
         if (!window.showing_on_its_workspace())
             return false;
 
+        let geometry = global.display.get_monitor_geometry(this._monitor.index);
+        let [intersects, intersection_] = window.get_frame_rect().intersect(geometry);
+        if (!intersects)
+            return false;
+
         return window.is_on_all_workspaces() || window === this._controller.movingWindow;
     }
 
@@ -121,8 +134,8 @@ class StickyGroup extends Clutter.Actor {
         for (let window of windows) {
             let clone = new Clutter.Clone({
                 source: window,
-                x: window.x,
-                y: window.y,
+                x: window.x - this._monitor.x,
+                y: window.y - this._monitor.y,
             });
 
             this.add_actor(clone);
@@ -195,19 +208,11 @@ var WorkspaceAnimationController = class {
         let switchData = {};
 
         this._switchData = switchData;
-        switchData.stickyGroup = new StickyGroup(this);
-        switchData.workspaces = [];
+        switchData.monitors = [];
+
         switchData.gestureActivated = false;
         switchData.inProgress = false;
 
-        switchData.container = new Clutter.Actor();
-
-        wgroup.add_actor(switchData.stickyGroup);
-        wgroup.add_actor(switchData.container);
-
-        let x = 0;
-        let y = 0;
-
         if (!workspaces) {
             workspaces = [];
 
@@ -215,65 +220,96 @@ var WorkspaceAnimationController = class {
                 workspaces.push(i);
         }
 
-        for (let i of workspaces) {
-            let ws = workspaceManager.get_workspace_by_index(i);
-            let fullscreen = ws.list_windows().some(w => w.is_fullscreen());
+        for (let monitor of Main.layoutManager.monitors) {
+            let record = {
+                index: monitor.index,
+                clipBin: new St.Widget({
+                    x_expand: true,
+                    y_expand: true,
+                    clip_to_allocation: true,
+                }),
+                container: new Clutter.Actor(),
+                stickyGroup: new StickyGroup(this, monitor),
+                workspaces: [],
+            };
 
-            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;
+            let constraint = new Layout.MonitorConstraint({ index: monitor.index });
+            record.clipBin.add_constraint(constraint);
+
+            record.clipBin.add_actor(record.container);
+            record.clipBin.add_actor(record.stickyGroup);
+
+            wgroup.add_actor(record.clipBin);
+
+            let x = 0;
+            let y = 0;
+
+            let geometry = Main.layoutManager.monitors[record.index];
+
+            for (let i of workspaces) {
+                let ws = workspaceManager.get_workspace_by_index(i);
+                let fullscreen = ws.list_windows().some(w => w.get_monitor() === record.index && 
w.is_fullscreen());
+
+                if (i > 0 && vertical && !fullscreen && record.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;
+                }
+
+                let info = {
+                    ws,
+                    actor: new WorkspaceGroup(this, ws, monitor),
+                    fullscreen,
+                    x,
+                    y,
+                };
+
+                if (vertical)
+                    info.position = info.y / geometry.height;
+                else if (Clutter.get_default_text_direction() === Clutter.TextDirection.RTL)
+                    info.position = -info.x / geometry.width;
+                else
+                    info.position = info.x / geometry.width;
+
+                record.workspaces[i] = info;
+                record.container.add_actor(info.actor);
+                record.container.set_child_above_sibling(info.actor, null);
+                info.actor.set_position(x, y);
+
+                if (vertical)
+                    y += geometry.height;
+                else if (Clutter.get_default_text_direction() === Clutter.TextDirection.RTL)
+                    x -= geometry.width;
+                else
+                    x += geometry.width;
             }
 
-            let info = {
-                ws,
-                actor: new WorkspaceGroup(this, ws),
-                fullscreen,
-                x,
-                y,
-            };
-
-            if (vertical)
-                info.position = info.y / global.screen_height;
-            else if (Clutter.get_default_text_direction() === Clutter.TextDirection.RTL)
-                info.position = -info.x / global.screen_width;
-            else
-                info.position = info.x / global.screen_width;
+            record.clipBin.set_child_above_sibling(record.stickyGroup, null);
 
-            switchData.workspaces[i] = info;
-            switchData.container.add_actor(info.actor);
-            switchData.container.set_child_above_sibling(info.actor, null);
-            info.actor.set_position(x, y);
+            switchData.monitors.push(record);
 
-            if (vertical)
-                y += global.screen_height;
-            else if (Clutter.get_default_text_direction() === Clutter.TextDirection.RTL)
-                x -= global.screen_width;
+            if (global.workspace_manager.layout_rows === -1)
+                record.container.y = -record.workspaces[activeWorkspaceIndex].y;
             else
-                x += global.screen_width;
+                record.container.x = -record.workspaces[activeWorkspaceIndex].x;
         }
-
-        if (global.workspace_manager.layout_rows === -1)
-            switchData.container.y = -switchData.workspaces[activeWorkspaceIndex].y;
-        else
-            switchData.container.x = -switchData.workspaces[activeWorkspaceIndex].x;
-
-        wgroup.set_child_above_sibling(switchData.stickyGroup, null);
     }
 
     _finishWorkspaceSwitch(switchData) {
         this._switchData = null;
 
-        switchData.container.destroy();
-        switchData.stickyGroup.destroy();
+        for (let monitor of switchData.monitors)
+            monitor.clipBin.destroy();
 
         this.movingWindow = null;
     }
 
     animateSwitchWorkspace(from, to, direction, onComplete) {
-        if (this._switchData)
-            this._switchData.container.remove_all_transitions();
+        if (this._switchData) {
+            for (let monitor of this._switchData.monitors)
+                monitor.container.remove_all_transitions();
+        }
 
         let workspaces = [];
 
@@ -301,28 +337,35 @@ var WorkspaceAnimationController = class {
         this._prepareWorkspaceSwitch(workspaces);
         this._switchData.inProgress = true;
 
-        let fromWs = this._switchData.workspaces[from];
-        let toWs = this._switchData.workspaces[to];
-
-        this._switchData.container.x = -fromWs.x;
-        this._switchData.container.y = -fromWs.y;
-
-        this._switchData.container.ease({
-            x: -toWs.x,
-            y: -toWs.y,
-            duration: WINDOW_ANIMATION_TIME,
-            mode: Clutter.AnimationMode.EASE_OUT_CUBIC,
-            onComplete: () => {
-                this._finishWorkspaceSwitch(this._switchData);
-                onComplete();
-            },
-        });
+        for (let monitor of this._switchData.monitors) {
+            let fromWs = monitor.workspaces[from];
+            let toWs = monitor.workspaces[to];
+
+            monitor.container.x = -fromWs.x;
+            monitor.container.y = -fromWs.y;
+
+            let params = {
+                x: -toWs.x,
+                y: -toWs.y,
+                duration: WINDOW_ANIMATION_TIME,
+                mode: Clutter.AnimationMode.EASE_OUT_CUBIC,
+            };
+
+            if (monitor.index === Main.layoutManager.primaryIndex) {
+                params.onComplete = () => {
+                    this._finishWorkspaceSwitch(this._switchData);
+                    onComplete();
+                };
+            }
+
+            monitor.container.ease(params);
+        }
     }
 
-    _findClosestWorkspace(position) {
-        let distances = this._switchData.workspaces.map(ws => Math.abs(ws.position - position));
-        let index = distances.indexOf(Math.min(...distances));
-        return this._switchData.workspaces[index];
+    _findClosestWorkspaceIndex(position) {
+        let record = this._switchData.monitors[Main.layoutManager.primaryIndex];
+        let distances = record.workspaces.map(ws => Math.abs(ws.position - position));
+        return distances.indexOf(Math.min(...distances));
     }
 
     _switchWorkspaceBegin(tracker, monitor) {
@@ -336,71 +379,113 @@ var WorkspaceAnimationController = class {
             ? Clutter.Orientation.HORIZONTAL
             : Clutter.Orientation.VERTICAL;
 
-        let baseDistance = horiz ? global.screen_width : global.screen_height;
+        let geometry = Main.layoutManager.monitors[monitor];
+        let baseDistance = horiz ? geometry.width : geometry.height;
 
         let progress;
         let cancelProgress;
         if (this._switchData && this._switchData.gestureActivated) {
-            this._switchData.container.remove_all_transitions();
+            for (let record of this._switchData.monitors)
+                record.container.remove_all_transitions();
+
+            let record = this._switchData.monitors[monitor];
+
             if (!horiz)
-                progress = -this._switchData.container.y / baseDistance;
+                progress = -record.container.y / baseDistance;
             else if (Clutter.get_default_text_direction() === Clutter.TextDirection.RTL)
-                progress = this._switchData.container.x / baseDistance;
+                progress = record.container.x / baseDistance;
             else
-                progress = -this._switchData.container.x / baseDistance;
+                progress = -record.container.x / baseDistance;
 
-            cancelProgress = this._findClosestWorkspace(progress).position;
+            let ws = record.workspaces[this._findClosestWorkspaceIndex(progress)];
+            cancelProgress = ws.position;
         } else {
             this._prepareWorkspaceSwitch();
 
             let activeIndex = workspaceManager.get_active_workspace_index();
-            let ws = this._switchData.workspaces[activeIndex];
+            let ws = this._switchData.monitors[monitor].workspaces[activeIndex];
 
             progress = cancelProgress = ws.position;
         }
 
-        let points = this._switchData.workspaces.map(ws => ws.position);
+        let points = this._switchData.monitors[monitor].workspaces.map(ws => ws.position);
+
+        this._switchData.baseMonitor = monitor;
 
         tracker.confirmSwipe(baseDistance, points, progress, cancelProgress);
     }
 
+    _interpolateProgress(progress, monitor) {
+        let monitor1 = this._switchData.monitors[this._switchData.baseMonitor];
+        let monitor2 = monitor;
+
+        let points1 = monitor1.workspaces.map(ws => ws.position);
+        let points2 = monitor2.workspaces.map(ws => ws.position);
+
+        let upper = points1.indexOf(points1.find(p => p >= progress));
+        let lower = points1.indexOf(points1.slice().reverse().find(p => p <= progress));
+
+        if (points1[upper] === points1[lower])
+            return points2[upper];
+
+        let 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;
+        for (let monitor of this._switchData.monitors) {
+            let xPos = 0;
+            let yPos = 0;
+
+            let geometry = Main.layoutManager.monitors[monitor.index];
 
-        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);
+            let p = this._interpolateProgress(progress, monitor);
 
-        this._switchData.container.set_position(xPos, yPos);
+            if (global.workspace_manager.layout_rows === -1)
+                yPos = -Math.round(p * geometry.height);
+            else if (Clutter.get_default_text_direction() === Clutter.TextDirection.RTL)
+                xPos = Math.round(p * geometry.width);
+            else
+                xPos = -Math.round(p * geometry.width);
+
+            monitor.container.set_position(xPos, yPos);
+        }
     }
 
     _switchWorkspaceEnd(tracker, duration, endProgress) {
         if (!this._switchData)
             return;
 
-        let newWs = this._findClosestWorkspace(endProgress);
 
         let 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);
-            },
-        });
+        let index = this._findClosestWorkspaceIndex(endProgress);
+
+        for (let monitor of this._switchData.monitors) {
+            let newWs = monitor.workspaces[index];
+
+            let params = {
+                x: -newWs.x,
+                y: -newWs.y,
+                duration,
+                mode: Clutter.AnimationMode.EASE_OUT_CUBIC,
+            };
+
+            if (monitor.index === Main.layoutManager.primaryIndex) {
+                params.onComplete = () => {
+                    if (!newWs.ws.active)
+                        newWs.ws.activate(global.get_current_time());
+                    this._finishWorkspaceSwitch(switchData);
+                };
+            }
+
+            monitor.container.ease(params);
+        }
     }
 
     isAnimating() {


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