diff --git a/js/ui/workspaceAnimation.js b/js/ui/workspaceAnimation.js index 5036ff743..e0abb5256 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 = []; @@ -34,6 +35,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; @@ -66,8 +72,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); @@ -98,10 +104,20 @@ class WorkspaceGroup extends Clutter.Actor { } }); -const MonitorGroup = GObject.registerClass( -class MonitorGroup extends Clutter.Actor { - _init(monitor) { - super._init(); +const MonitorGroup = GObject.registerClass({ + Properties: { + 'progress': GObject.ParamSpec.double( + 'progress', 'progress', 'progress', + GObject.ParamFlags.READWRITE, + -Infinity, 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); @@ -110,6 +126,48 @@ 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._workspaceGroups = []; + + const workspaceManager = global.workspace_manager; + const vertical = workspaceManager.layout_rows === -1; + const activeWorkspace = workspaceManager.get_active_workspace(); + + 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 group = new WorkspaceGroup(ws, monitor, movingWindow); + + this._workspaceGroups.push(group); + this._container.add_child(group); + group.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.progress = this.getWorkspaceProgress(activeWorkspace); + this._bgManager = new Background.BackgroundManager({ container: background, monitorIndex: monitor.index, @@ -122,6 +180,84 @@ 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 progress() { + if (global.workspace_manager.layout_rows === -1) + return -this._container.y / this.baseDistance; + else if (this.get_text_direction() === Clutter.TextDirection.RTL) + return this._container.x / this.baseDistance; + else + return -this._container.x / this.baseDistance; + } + + set progress(p) { + if (global.workspace_manager.layout_rows === -1) + this._container.y = -Math.round(p * this.baseDistance); + else if (this.get_text_direction() === Clutter.TextDirection.RTL) + this._container.x = Math.round(p * this.baseDistance); + else + this._container.x = -Math.round(p * this.baseDistance); + } + + get index() { + return this._monitor.index; + } + + getWorkspaceProgress(workspace) { + const group = this._workspaceGroups.find(g => + g.workspace.index() === workspace.index()); + return this._getWorkspaceGroupProgress(group); + } + + _getWorkspaceGroupProgress(group) { + if (global.workspace_manager.layout_rows === -1) + return group.y / this.baseDistance; + else if (this.get_text_direction() === Clutter.TextDirection.RTL) + return -group.x / this.baseDistance; + else + return group.x / this.baseDistance; + } + + getSnapPoints() { + return this._workspaceGroups.map(g => + this._getWorkspaceGroupProgress(g)); + } + + findClosestWorkspace(progress) { + const distances = this.getSnapPoints().map(p => + Math.abs(p - progress)); + const index = distances.indexOf(Math.min(...distances)); + return this._workspaceGroups[index].workspace; + } + + _interpolateProgress(progress, monitorGroup) { + if (this.index === monitorGroup.index) + return progress; + + const points1 = monitorGroup.getSnapPoints(); + const points2 = this.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; + } + + updateSwipeForMonitor(progress, monitorGroup) { + this.progress = this._interpolateProgress(progress, monitorGroup); + } }); var WorkspaceAnimationController = class { @@ -153,73 +289,39 @@ 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.workspaceGroups = []; + switchData.monitors = []; + switchData.gestureActivated = false; switchData.inProgress = false; - switchData.container = new Clutter.Actor(); - switchData.backgroundGroup = new Clutter.Actor(); - - for (const monitor of Main.layoutManager.monitors) - switchData.backgroundGroup.add_child(new MonitorGroup(monitor)); - - Main.uiGroup.insert_child_above(switchData.backgroundGroup, global.window_group); - Main.uiGroup.insert_child_above(switchData.container, switchData.backgroundGroup); - 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 group = new WorkspaceGroup(ws, this.movingWindow); + const group = new MonitorGroup(monitor, workspaceIndices, this.movingWindow); - switchData.workspaceGroups.push(group); - switchData.container.add_child(group); - switchData.container.set_child_above_sibling(group, null); - group.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.push(group); } - - const activeGroup = this._findWorkspaceGroupByIndex(activeWorkspaceIndex); - if (vertical) - switchData.container.y = -activeGroup.y; - else - switchData.container.x = -activeGroup.x; } _finishWorkspaceSwitch(switchData) { this._switchData = null; - switchData.backgroundGroup.destroy(); - switchData.container.destroy(); - switchData.stickyGroup.destroy(); + switchData.monitors.forEach(m => m.destroy()); this.movingWindow = null; } @@ -253,45 +355,32 @@ var WorkspaceAnimationController = class { this._prepareWorkspaceSwitch(workspaceIndices); this._switchData.inProgress = true; - const fromGroup = this._findWorkspaceGroupByIndex(from); - const toGroup = this._findWorkspaceGroupByIndex(to); + const fromWs = global.workspace_manager.get_workspace_by_index(from); + const toWs = global.workspace_manager.get_workspace_by_index(to); - this._switchData.container.x = -fromGroup.x; - this._switchData.container.y = -fromGroup.y; + for (const monitorGroup of this._switchData.monitors) { + monitorGroup.progress = monitorGroup.getWorkspaceProgress(fromWs); + const progress = monitorGroup.getWorkspaceProgress(toWs); - this._switchData.container.ease({ - x: -toGroup.x, - y: -toGroup.y, - duration: WINDOW_ANIMATION_TIME, - mode: Clutter.AnimationMode.EASE_OUT_CUBIC, - onComplete: () => { - this._finishWorkspaceSwitch(this._switchData); - onComplete(); - this._swipeTracker.enabled = true; - }, - }); + const params = { + duration: WINDOW_ANIMATION_TIME, + mode: Clutter.AnimationMode.EASE_OUT_CUBIC, + }; + + if (monitorGroup.index === Main.layoutManager.primaryIndex) { + params.onComplete = () => { + this._finishWorkspaceSwitch(this._switchData); + onComplete(); + this._swipeTracker.enabled = true; + }; + } + + monitorGroup.ease_property('progress', progress, params); + } } - _getProgressForWorkspace(workspaceGroup) { - if (global.workspace_manager.layout_rows === -1) - return workspaceGroup.y / global.screen_height; - else if (Clutter.get_default_text_direction() === Clutter.TextDirection.RTL) - return -workspaceGroup.x / global.screen_width; - else - return workspaceGroup.x / global.screen_width; - } - - _findWorkspaceGroupByIndex(index) { - return this._switchData.workspaceGroups.find(g => g.workspace.index() === index); - } - - _findClosestWorkspaceGroup(progress) { - const distances = this._switchData.workspaceGroups.map(g => { - const workspaceProgress = this._getProgressForWorkspace(g); - return Math.abs(workspaceProgress - progress); - }); - const index = distances.indexOf(Math.min(...distances)); - return this._switchData.workspaceGroups[index]; + _findMonitorGroup(monitorIndex) { + return this._switchData.monitors.find(m => m.index === monitorIndex); } _switchWorkspaceBegin(tracker, monitor) { @@ -305,31 +394,22 @@ 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 wsGroup = this._findClosestWorkspaceGroup(progress); - cancelProgress = this._getProgressForWorkspaceGroup(wsGroup); + for (const group of this._switchData.monitors) + group.remove_all_transitions(); } else { this._prepareWorkspaceSwitch(); - - const activeIndex = workspaceManager.get_active_workspace_index(); - const wsGroup = this._findWorkspaceGroupByIndex(activeIndex); - - progress = cancelProgress = this._getProgressForWorkspace(wsGroup); } - const points = this._switchData.workspaceGroups.map(this._getProgressForWorkspace); + const monitorGroup = this._findMonitorGroup(monitor); + const baseDistance = monitorGroup.baseDistance; + const progress = monitorGroup.progress; + + const closestWs = monitorGroup.findClosestWorkspace(progress); + const cancelProgress = monitorGroup.getWorkspaceProgress(closestWs); + const points = monitorGroup.getSnapPoints(); + + this._switchData.baseMonitorGroup = monitorGroup; tracker.confirmSwipe(baseDistance, points, progress, cancelProgress); } @@ -338,39 +418,37 @@ var WorkspaceAnimationController = class { 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); - - this._switchData.container.set_position(xPos, yPos); + for (const monitorGroup of this._switchData.monitors) + monitorGroup.updateSwipeForMonitor(progress, this._switchData.baseMonitorGroup); } _switchWorkspaceEnd(tracker, duration, endProgress) { if (!this._switchData) return; - const newGroup = this._findClosestWorkspaceGroup(endProgress); - const switchData = this._switchData; switchData.gestureActivated = true; - this._switchData.container.ease({ - x: -newGroup.x, - y: -newGroup.y, - duration, - mode: Clutter.AnimationMode.EASE_OUT_CUBIC, - onComplete: () => { - if (!newGroup.workspace.active) - newGroup.workspace.activate(global.get_current_time()); - this._finishWorkspaceSwitch(switchData); - }, - }); + const newWs = switchData.baseMonitorGroup.findClosestWorkspace(endProgress); + + for (const monitorGroup of this._switchData.monitors) { + const progress = monitorGroup.getWorkspaceProgress(newWs); + + const params = { + duration, + mode: Clutter.AnimationMode.EASE_OUT_CUBIC, + }; + + if (monitorGroup.index === Main.layoutManager.primaryIndex) { + params.onComplete = () => { + if (!newWs.active) + newWs.activate(global.get_current_time()); + this._finishWorkspaceSwitch(switchData); + }; + } + + monitorGroup.ease_property('progress', progress, params); + } } get gestureActive() {