workspace: Use the new WorkspaceLayout for allocating window clones

Switch to the new WorkspaceLayout layout manager to allocate the window
clones of the overview properly using Clutters layouting mechanisms.

Since we now no longer make use of the fullGeometry, we can remove the
setFullGeometry() function from the Workspace class. Also we can stop
setting the actualGeometry on the Workspaces and WorkspaceViews and
instead just set the fixed position and size of the views to their
full or actual geometry. This also has the benefit that we no longer
have to set a custom clip, but can simply enable clip_to_allocation.

The geometry needs to be set inside a BEFORE_REDRAW later because
_updateWorkspacesActualGeometry() is called from a notify::allocation
handler.

This isn't doing any animations when showing/hiding the overview yet,
we'll add that in the next commit.

https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1305
This commit is contained in:
Jonas Dreßler 2020-06-03 18:17:54 +02:00 committed by Florian Müllner
parent 21187a4cec
commit 751189253a
4 changed files with 112 additions and 514 deletions

View File

@ -1,7 +1,7 @@
/* Window Picker */ /* Window Picker */
$window_picker_spacing: $base_spacing * 2; // 16px $window_picker_spacing: $base_spacing; // 6px
$window_picker_padding: $base_padding * 2; // 16px $window_picker_padding: $base_padding * 2; // 12px
$window_thumbnail_border_color:transparentize($selected_fg_color, 0.65); $window_thumbnail_border_color:transparentize($selected_fg_color, 0.65);
@ -13,8 +13,8 @@ $window_clone_border_size: 6px;
// Window picker // Window picker
.window-picker { .window-picker {
// Space between window thumbnails // Space between window thumbnails
-horizontal-spacing: $window_picker_spacing; spacing: $window_picker_spacing;
-vertical-spacing: $window_picker_spacing;
// Padding for container around window thumbnails // Padding for container around window thumbnails
padding: $window_picker_padding; padding: $window_picker_padding;

View File

@ -237,8 +237,6 @@ var WindowPreview = GObject.registerClass({
this._windowActor.connect('destroy', () => this.destroy()); this._windowActor.connect('destroy', () => this.destroy());
this._updateAttachedDialogs(); this._updateAttachedDialogs();
this.x = this.boundingBox.x;
this.y = this.boundingBox.y;
let clickAction = new Clutter.ClickAction(); let clickAction = new Clutter.ClickAction();
clickAction.connect('clicked', () => this._activate()); clickAction.connect('clicked', () => this._activate());

View File

@ -1,7 +1,7 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Workspace */ /* exported Workspace */
const { Clutter, GLib, GObject, Meta, St } = imports.gi; const { Clutter, GLib, GObject, St } = imports.gi;
const DND = imports.ui.dnd; const DND = imports.ui.dnd;
const Main = imports.ui.main; const Main = imports.ui.main;
@ -26,12 +26,6 @@ function _interpolate(start, end, step) {
return start + (end - start) * step; return start + (end - start) * step;
} }
var WindowPositionFlags = {
NONE: 0,
INITIAL: 1 << 0,
ANIMATE: 1 << 1,
};
// Window Thumbnail Layout Algorithm // Window Thumbnail Layout Algorithm
// ================================= // =================================
// //
@ -367,28 +361,6 @@ var UnalignedLayoutStrategy = class extends LayoutStrategy {
} }
}; };
function padArea(area, padding) {
return {
x: area.x + padding.left,
y: area.y + padding.top,
width: area.width - padding.left - padding.right,
height: area.height - padding.top - padding.bottom,
};
}
function rectEqual(one, two) {
if (one == two)
return true;
if (!one || !two)
return false;
return one.x == two.x &&
one.y == two.y &&
one.width == two.width &&
one.height == two.height;
}
function animateAllocation(actor, box) { function animateAllocation(actor, box) {
if (actor.allocation.equal(box) || if (actor.allocation.equal(box) ||
actor.allocation.get_width() === 0 || actor.allocation.get_width() === 0 ||
@ -815,39 +787,23 @@ var WorkspaceLayout = GObject.registerClass({
var Workspace = GObject.registerClass( var Workspace = GObject.registerClass(
class Workspace extends St.Widget { class Workspace extends St.Widget {
_init(metaWorkspace, monitorIndex) { _init(metaWorkspace, monitorIndex) {
super._init({ style_class: 'window-picker' }); super._init({
style_class: 'window-picker',
layout_manager: new WorkspaceLayout(metaWorkspace, monitorIndex),
});
// When dragging a window, we use this slot for reserve space. // When dragging a window, we use this slot for reserve space.
this._reservedSlot = null; this._reservedSlot = null;
this._reservedSlotWindow = null; this._reservedSlotWindow = null;
this.metaWorkspace = metaWorkspace; this.metaWorkspace = metaWorkspace;
// The full geometry is the geometry we should try and position
// windows for. The actual geometry we allocate may be less than
// this, like if the workspace switcher is slid out.
this._fullGeometry = null;
// The actual geometry is the geometry we need to arrange windows
// in. If this is a smaller area than the full geometry, we'll
// do some simple aspect ratio like math to fit the layout calculated
// for the full geometry into this area.
this._actualGeometry = null;
this._actualGeometryLater = 0;
this._currentLayout = null;
this.monitorIndex = monitorIndex; this.monitorIndex = monitorIndex;
this._monitor = Main.layoutManager.monitors[this.monitorIndex]; this._monitor = Main.layoutManager.monitors[this.monitorIndex];
if (monitorIndex != Main.layoutManager.primaryIndex) if (monitorIndex != Main.layoutManager.primaryIndex)
this.add_style_class_name('external-monitor'); this.add_style_class_name('external-monitor');
this.set_size(0, 0);
this._dropRect = new Clutter.Actor({ opacity: 0 });
this._dropRect._delegate = this;
this.add_actor(this._dropRect);
this.connect('style-changed', this._onStyleChanged.bind(this));
this.connect('destroy', this._onDestroy.bind(this)); this.connect('destroy', this._onDestroy.bind(this));
const windows = global.get_window_actors().map(a => a.meta_window) const windows = global.get_window_actors().map(a => a.meta_window)
@ -858,7 +814,7 @@ class Workspace extends St.Widget {
this._windows = []; this._windows = [];
for (let i = 0; i < windows.length; i++) { for (let i = 0; i < windows.length; i++) {
if (this._isOverviewWindow(windows[i])) if (this._isOverviewWindow(windows[i]))
this._addWindowClone(windows[i], true); this._addWindowClone(windows[i]);
} }
// Track window changes // Track window changes
@ -872,68 +828,14 @@ class Workspace extends St.Widget {
this._windowEnteredMonitor.bind(this)); this._windowEnteredMonitor.bind(this));
this._windowLeftMonitorId = global.display.connect('window-left-monitor', this._windowLeftMonitorId = global.display.connect('window-left-monitor',
this._windowLeftMonitor.bind(this)); this._windowLeftMonitor.bind(this));
this._repositionWindowsId = 0; this._layoutFrozenId = 0;
this.leavingOverview = false; // DND requires this to be set
this._delegate = this;
this._positionWindowsFlags = 0;
this._positionWindowsId = 0;
}
vfunc_map() {
super.vfunc_map();
this._syncActualGeometry();
} }
vfunc_get_focus_chain() { vfunc_get_focus_chain() {
return this.get_children().filter(c => c.visible).sort((a, b) => { return this.layout_manager.getFocusChain();
if (a instanceof WindowPreview && b instanceof WindowPreview)
return a.slotId - b.slotId;
return 0;
});
}
setFullGeometry(geom) {
if (rectEqual(this._fullGeometry, geom))
return;
this._fullGeometry = geom;
if (this.mapped)
this._recalculateWindowPositions(WindowPositionFlags.NONE);
}
setActualGeometry(geom) {
if (rectEqual(this._actualGeometry, geom))
return;
this._actualGeometry = geom;
this._actualGeometryDirty = true;
if (this.mapped)
this._syncActualGeometry();
}
_syncActualGeometry() {
if (this._actualGeometryLater || !this._actualGeometryDirty)
return;
if (!this._actualGeometry)
return;
this._actualGeometryLater = Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
this._actualGeometryLater = 0;
if (!this.mapped)
return false;
let geom = this._actualGeometry;
this._dropRect.set_position(geom.x, geom.y);
this._dropRect.set_size(geom.width, geom.height);
this._updateWindowPositions(Main.overview.animationInProgress ? WindowPositionFlags.ANIMATE : WindowPositionFlags.NONE);
return false;
});
} }
_lookupIndex(metaWindow) { _lookupIndex(metaWindow) {
@ -959,232 +861,59 @@ class Workspace extends St.Widget {
this._reservedSlotWindow = metaWindow; this._reservedSlotWindow = metaWindow;
this._reservedSlot = this._windows[this._lookupIndex(metaWindow)]; this._reservedSlot = this._windows[this._lookupIndex(metaWindow)];
} }
this._recalculateWindowPositions(WindowPositionFlags.ANIMATE);
}
_recalculateWindowPositions(flags) {
this._positionWindowsFlags |= flags;
if (this._positionWindowsId > 0)
return;
this._positionWindowsId = Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
this._realRecalculateWindowPositions(this._positionWindowsFlags);
this._positionWindowsFlags = 0;
this._positionWindowsId = 0;
return false;
});
}
_realRecalculateWindowPositions(flags) {
if (this._repositionWindowsId > 0) {
GLib.source_remove(this._repositionWindowsId);
this._repositionWindowsId = 0;
}
let clones = this._windows.slice();
if (clones.length == 0)
return;
clones.sort((a, b) => {
return a.metaWindow.get_stable_sequence() - b.metaWindow.get_stable_sequence();
});
if (this._reservedSlot)
clones.push(this._reservedSlot);
this._currentLayout = this._computeLayout(clones);
this._updateWindowPositions(flags);
}
_updateWindowPositions(flags) {
if (this._currentLayout == null) {
this._recalculateWindowPositions(flags);
return;
}
// We will reposition windows anyway when enter again overview or when ending the windows
// animations with fade animation.
// In this way we avoid unwanted animations of windows repositioning while
// animating overview.
if (this.leavingOverview || this._animatingWindowsFade)
return;
let initialPositioning = flags & WindowPositionFlags.INITIAL;
let animate = flags & WindowPositionFlags.ANIMATE;
let layout = this._currentLayout;
let strategy = layout.strategy;
let [, , padding] = this._getSpacingAndPadding();
let area = padArea(this._actualGeometry, padding);
let slots = strategy.computeWindowSlots(layout, area);
const isOnCurrentWorkspace =
this.metaWorkspace === null || this.metaWorkspace.active;
for (let i = 0; i < slots.length; i++) {
let slot = slots[i];
const [x, y, cellWidth, cellHeight, clone] = slot;
clone.slotId = i;
// Positioning a window currently being dragged must be avoided;
// we'll just leave a blank spot in the layout for it.
if (clone.inDrag)
continue;
const cloneWidth = cellWidth;
const cloneHeight = cellHeight;
if (!clone.positioned) {
// This window appeared after the overview was already up
// Grow the clone from the center of the slot
clone.x = x + cloneWidth / 2;
clone.y = y + cloneHeight / 2;
clone.width = 0;
clone.height = 0;
clone.positioned = true;
}
if (animate && isOnCurrentWorkspace) {
if (!clone.metaWindow.showing_on_its_workspace()) {
/* Hidden windows should fade in and grow
* therefore we need to resize them now so they
* can be scaled up later */
if (initialPositioning) {
clone.opacity = 0;
clone.x = x;
clone.y = y;
clone.width = cloneWidth;
clone.height = cloneHeight;
}
clone.ease({
opacity: 255,
mode: Clutter.AnimationMode.EASE_IN_QUAD,
duration: Overview.ANIMATION_TIME,
});
}
this._animateClone(clone, x, y, cloneWidth, cloneHeight);
} else {
// cancel any active tweens (otherwise they might override our changes)
clone.remove_all_transitions();
clone.set_position(x, y);
clone.set_size(cloneWidth, cloneHeight);
clone.set_opacity(255);
}
}
} }
syncStacking(stackIndices) { syncStacking(stackIndices) {
let clones = this._windows.slice(); this.layout_manager.syncStacking(stackIndices);
clones.sort((a, b) => {
let indexA = stackIndices[a.metaWindow.get_stable_sequence()];
let indexB = stackIndices[b.metaWindow.get_stable_sequence()];
return indexA - indexB;
});
for (let i = 0; i < clones.length; i++) {
let clone = clones[i];
if (i == 0) {
clone.setStackAbove(this._dropRect);
} else {
let previousClone = clones[i - 1];
clone.setStackAbove(previousClone);
}
}
}
_animateClone(clone, x, y, width, height) {
clone.ease({
x, y,
width, height,
duration: Overview.ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
});
}
_delayedWindowRepositioning() {
let [x, y] = global.get_pointer();
let pointerHasMoved = this._cursorX != x && this._cursorY != y;
let inWorkspace = this._fullGeometry.x < x && x < this._fullGeometry.x + this._fullGeometry.width &&
this._fullGeometry.y < y && y < this._fullGeometry.y + this._fullGeometry.height;
if (pointerHasMoved && inWorkspace) {
// store current cursor position
this._cursorX = x;
this._cursorY = y;
return GLib.SOURCE_CONTINUE;
}
let actorUnderPointer = global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, x, y);
for (let i = 0; i < this._windows.length; i++) {
if (this._windows[i] == actorUnderPointer)
return GLib.SOURCE_CONTINUE;
}
this._recalculateWindowPositions(WindowPositionFlags.ANIMATE);
this._repositionWindowsId = 0;
return GLib.SOURCE_REMOVE;
} }
_doRemoveWindow(metaWin) { _doRemoveWindow(metaWin) {
let win = metaWin.get_compositor_private();
let clone = this._removeWindowClone(metaWin); let clone = this._removeWindowClone(metaWin);
if (clone) { if (!clone)
// If metaWin.get_compositor_private() returned non-NULL, that return;
// means the window still exists (and is just being moved to
// another workspace or something), so set its overviewHint
// accordingly. (If it returned NULL, then the window is being
// destroyed; we'd like to animate this, but it's too late at
// this point.)
if (win) {
let [stageX, stageY] = clone.get_transformed_position();
const [transformedWidth, transformedHeight] =
clone.get_transformed_size();
metaWin._overviewHint = { clone.destroy();
x: stageX,
y: stageY,
width: transformedWidth,
height: transformedHeight,
};
}
clone.destroy();
}
// We need to reposition the windows; to avoid shuffling windows // We need to reposition the windows; to avoid shuffling windows
// around while the user is interacting with the workspace, we delay // around while the user is interacting with the workspace, we delay
// the positioning until the pointer remains still for at least 750 ms // the positioning until the pointer remains still for at least 750 ms
// or is moved outside the workspace // or is moved outside the workspace
this.layout_manager.layout_frozen = true;
// remove old handler if (this._layoutFrozenId > 0) {
if (this._repositionWindowsId > 0) { GLib.source_remove(this._layoutFrozenId);
GLib.source_remove(this._repositionWindowsId); this._layoutFrozenId = 0;
this._repositionWindowsId = 0;
} }
// setup new handler let [oldX, oldY] = global.get_pointer();
let [x, y] = global.get_pointer();
this._cursorX = x;
this._cursorY = y;
this._currentLayout = null; this._layoutFrozenId = GLib.timeout_add(
this._repositionWindowsId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, WINDOW_REPOSITIONING_DELAY, GLib.PRIORITY_DEFAULT,
this._delayedWindowRepositioning.bind(this)); WINDOW_REPOSITIONING_DELAY,
GLib.Source.set_name_by_id(this._repositionWindowsId, '[gnome-shell] this._delayedWindowRepositioning'); () => {
const [newX, newY] = global.get_pointer();
const pointerHasMoved = oldX !== newX || oldY !== newY;
const actorUnderPointer = global.stage.get_actor_at_pos(
Clutter.PickMode.REACTIVE, newX, newY);
if ((pointerHasMoved && this.contains(actorUnderPointer)) ||
this._windows.some(w => w.contains(actorUnderPointer))) {
oldX = newX;
oldY = newY;
return GLib.SOURCE_CONTINUE;
}
this.layout_manager.layout_frozen = false;
this._layoutFrozenId = 0;
return GLib.SOURCE_REMOVE;
});
GLib.Source.set_name_by_id(this._layoutFrozenId,
'[gnome-shell] this._layoutFrozenId');
} }
_doAddWindow(metaWin) { _doAddWindow(metaWin) {
if (this.leavingOverview)
return;
let win = metaWin.get_compositor_private(); let win = metaWin.get_compositor_private();
if (!win) { if (!win) {
@ -1224,23 +953,16 @@ class Workspace extends St.Widget {
return; return;
} }
let clone = this._addWindowClone(metaWin, false); this._addWindowClone(metaWin);
if (metaWin._overviewHint) { if (this._layoutFrozenId > 0) {
let x = metaWin._overviewHint.x - this.x; // If a window was closed before, unfreeze the layout to ensure
let y = metaWin._overviewHint.y - this.y; // the new window is immediately shown
const width = metaWin._overviewHint.width; this.layout_manager.layout_frozen = false;
const height = metaWin._overviewHint.height;
delete metaWin._overviewHint;
clone.positioned = true; GLib.source_remove(this._layoutFrozenId);
this._layoutFrozenId = 0;
clone.set_position(x, y);
clone.set_size(width, height);
} }
this._currentLayout = null;
this._recalculateWindowPositions(WindowPositionFlags.ANIMATE);
} }
_windowAdded(metaWorkspace, metaWin) { _windowAdded(metaWorkspace, metaWin) {
@ -1275,7 +997,7 @@ class Workspace extends St.Widget {
fadeToOverview() { fadeToOverview() {
// We don't want to reposition windows while animating in this way. // We don't want to reposition windows while animating in this way.
this._animatingWindowsFade = true; this.layout_manager.layout_frozen = true;
this._overviewShownId = Main.overview.connect('shown', this._doneShowingOverview.bind(this)); this._overviewShownId = Main.overview.connect('shown', this._doneShowingOverview.bind(this));
if (this._windows.length == 0) if (this._windows.length == 0)
return; return;
@ -1319,7 +1041,7 @@ class Workspace extends St.Widget {
} }
fadeFromOverview() { fadeFromOverview() {
this.leavingOverview = true; this.layout_manager.layout_frozen = true;
this._overviewHiddenId = Main.overview.connect('hidden', this._doneLeavingOverview.bind(this)); this._overviewHiddenId = Main.overview.connect('hidden', this._doneLeavingOverview.bind(this));
if (this._windows.length == 0) if (this._windows.length == 0)
return; return;
@ -1327,9 +1049,9 @@ class Workspace extends St.Widget {
for (let i = 0; i < this._windows.length; i++) for (let i = 0; i < this._windows.length; i++)
this._windows[i].remove_all_transitions(); this._windows[i].remove_all_transitions();
if (this._repositionWindowsId > 0) { if (this._layoutFrozenId > 0) {
GLib.source_remove(this._repositionWindowsId); GLib.source_remove(this._layoutFrozenId);
this._repositionWindowsId = 0; this._layoutFrozenId = 0;
} }
if (this.metaWorkspace !== null && !this.metaWorkspace.active) if (this.metaWorkspace !== null && !this.metaWorkspace.active)
@ -1375,10 +1097,6 @@ class Workspace extends St.Widget {
clone.hideOverlay(false); clone.hideOverlay(false);
if (clone.metaWindow.showing_on_its_workspace()) { if (clone.metaWindow.showing_on_its_workspace()) {
clone.x = clone.boundingBox.x;
clone.y = clone.boundingBox.y;
clone.width = clone.boundingBox.width;
clone.height = clone.boundingBox.height;
clone.ease({ clone.ease({
opacity, opacity,
duration, duration,
@ -1391,20 +1109,18 @@ class Workspace extends St.Widget {
} }
zoomToOverview() { zoomToOverview() {
// Position and scale the windows.
this._recalculateWindowPositions(WindowPositionFlags.ANIMATE | WindowPositionFlags.INITIAL);
} }
zoomFromOverview() { zoomFromOverview() {
this.leavingOverview = true;
for (let i = 0; i < this._windows.length; i++) for (let i = 0; i < this._windows.length; i++)
this._windows[i].remove_all_transitions(); this._windows[i].remove_all_transitions();
if (this._repositionWindowsId > 0) { if (this._layoutFrozenId > 0) {
GLib.source_remove(this._repositionWindowsId); GLib.source_remove(this._layoutFrozenId);
this._repositionWindowsId = 0; this._layoutFrozenId = 0;
} }
this.layout_manager.layout_frozen = true;
this._overviewHiddenId = Main.overview.connect('hidden', this._doneLeavingOverview.bind(this)); this._overviewHiddenId = Main.overview.connect('hidden', this._doneLeavingOverview.bind(this));
if (this.metaWorkspace !== null && !this.metaWorkspace.active) if (this.metaWorkspace !== null && !this.metaWorkspace.active)
@ -1421,10 +1137,6 @@ class Workspace extends St.Widget {
if (clone.metaWindow.showing_on_its_workspace()) { if (clone.metaWindow.showing_on_its_workspace()) {
clone.ease({ clone.ease({
x: clone.boundingBox.x,
y: clone.boundingBox.y,
width: clone.boundingBox.width,
height: clone.boundingBox.height,
opacity: 255, opacity: 255,
duration: Overview.ANIMATION_TIME, duration: Overview.ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD, mode: Clutter.AnimationMode.EASE_OUT_QUAD,
@ -1432,8 +1144,6 @@ class Workspace extends St.Widget {
} else { } else {
// The window is hidden, make it shrink and fade it out // The window is hidden, make it shrink and fade it out
clone.ease({ clone.ease({
width: 0,
height: 0,
opacity: 0, opacity: 0,
duration: Overview.ANIMATION_TIME, duration: Overview.ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD, mode: Clutter.AnimationMode.EASE_OUT_QUAD,
@ -1447,6 +1157,11 @@ class Workspace extends St.Widget {
this._overviewHiddenId = 0; this._overviewHiddenId = 0;
} }
if (this._overviewShownId) {
Main.overview.disconnect(this._overviewShownId);
this._overviewShownId = 0;
}
if (this.metaWorkspace) { if (this.metaWorkspace) {
this.metaWorkspace.disconnect(this._windowAddedId); this.metaWorkspace.disconnect(this._windowAddedId);
this.metaWorkspace.disconnect(this._windowRemovedId); this.metaWorkspace.disconnect(this._windowRemovedId);
@ -1454,32 +1169,20 @@ class Workspace extends St.Widget {
global.display.disconnect(this._windowEnteredMonitorId); global.display.disconnect(this._windowEnteredMonitorId);
global.display.disconnect(this._windowLeftMonitorId); global.display.disconnect(this._windowLeftMonitorId);
if (this._repositionWindowsId > 0) { if (this._layoutFrozenId > 0) {
GLib.source_remove(this._repositionWindowsId); GLib.source_remove(this._layoutFrozenId);
this._repositionWindowsId = 0; this._layoutFrozenId = 0;
}
if (this._positionWindowsId > 0) {
Meta.later_remove(this._positionWindowsId);
this._positionWindowsId = 0;
}
if (this._actualGeometryLater > 0) {
Meta.later_remove(this._actualGeometryLater);
this._actualGeometryLater = 0;
} }
this._windows = []; this._windows = [];
} }
// Sets this.leavingOverview flag to false.
_doneLeavingOverview() { _doneLeavingOverview() {
this.leavingOverview = false; this.layout_manager.layout_frozen = false;
} }
_doneShowingOverview() { _doneShowingOverview() {
this._animatingWindowsFade = false; this.layout_manager.layout_frozen = false;
this._recalculateWindowPositions(WindowPositionFlags.INITIAL);
} }
_isMyWindow(window) { _isMyWindow(window) {
@ -1495,9 +1198,8 @@ class Workspace extends St.Widget {
} }
// Create a clone of a (non-desktop) window and add it to the window list // Create a clone of a (non-desktop) window and add it to the window list
_addWindowClone(metaWindow, positioned) { _addWindowClone(metaWindow) {
let clone = new WindowPreview(metaWindow, this); let clone = new WindowPreview(metaWindow, this);
clone.positioned = positioned;
clone.connect('selected', clone.connect('selected',
this._onCloneSelected.bind(this)); this._onCloneSelected.bind(this));
@ -1510,9 +1212,6 @@ class Workspace extends St.Widget {
clone.connect('drag-end', () => { clone.connect('drag-end', () => {
Main.overview.endWindowDrag(metaWindow); Main.overview.endWindowDrag(metaWindow);
}); });
clone.connect('size-changed', () => {
this._recalculateWindowPositions(WindowPositionFlags.NONE);
});
clone.connect('show-chrome', () => { clone.connect('show-chrome', () => {
let focus = global.stage.key_focus; let focus = global.stage.key_focus;
if (focus == null || this.contains(focus)) if (focus == null || this.contains(focus))
@ -1524,10 +1223,10 @@ class Workspace extends St.Widget {
}); });
}); });
clone.connect('destroy', () => { clone.connect('destroy', () => {
this._removeWindowClone(metaWindow); this._doRemoveWindow(metaWindow);
}); });
this.add_child(clone); this.layout_manager.addWindow(clone, metaWindow);
if (this._windows.length == 0) if (this._windows.length == 0)
clone.setStackAbove(null); clone.setStackAbove(null);
@ -1546,94 +1245,14 @@ class Workspace extends St.Widget {
if (index == -1) if (index == -1)
return null; return null;
this.layout_manager.removeWindow(this._windows[index]);
return this._windows.splice(index, 1).pop(); return this._windows.splice(index, 1).pop();
} }
_isBetterLayout(oldLayout, newLayout) { _onStyleChanged() {
if (oldLayout.scale === undefined) const themeNode = this.get_theme_node();
return true; this.layout_manager.spacing = themeNode.get_length('spacing');
let spacePower = (newLayout.space - oldLayout.space) * LAYOUT_SPACE_WEIGHT;
let scalePower = (newLayout.scale - oldLayout.scale) * LAYOUT_SCALE_WEIGHT;
if (newLayout.scale > oldLayout.scale && newLayout.space > oldLayout.space) {
// Win win -- better scale and better space
return true;
} else if (newLayout.scale > oldLayout.scale && newLayout.space <= oldLayout.space) {
// Keep new layout only if scale gain outweighs aspect space loss
return scalePower > spacePower;
} else if (newLayout.scale <= oldLayout.scale && newLayout.space > oldLayout.space) {
// Keep new layout only if aspect space gain outweighs scale loss
return spacePower > scalePower;
} else {
// Lose -- worse scale and space
return false;
}
}
_getBestLayout(windows, area, rowSpacing, columnSpacing) {
// We look for the largest scale that allows us to fit the
// largest row/tallest column on the workspace.
let lastLayout = {};
let strategy = new UnalignedLayoutStrategy(this._monitor, rowSpacing, columnSpacing);
for (let numRows = 1; ; numRows++) {
let numColumns = Math.ceil(windows.length / numRows);
// If adding a new row does not change column count just stop
// (for instance: 9 windows, with 3 rows -> 3 columns, 4 rows ->
// 3 columns as well => just use 3 rows then)
if (numColumns == lastLayout.numColumns)
break;
let layout = { area, strategy, numRows, numColumns };
strategy.computeLayout(windows, layout);
strategy.computeScaleAndSpace(layout);
if (!this._isBetterLayout(lastLayout, layout))
break;
lastLayout = layout;
}
return lastLayout;
}
_getSpacingAndPadding() {
let node = this.get_theme_node();
// Window grid spacing
let columnSpacing = node.get_length('-horizontal-spacing');
let rowSpacing = node.get_length('-vertical-spacing');
let padding = {
left: node.get_padding(St.Side.LEFT),
top: node.get_padding(St.Side.TOP),
bottom: node.get_padding(St.Side.BOTTOM),
right: node.get_padding(St.Side.RIGHT),
};
// All of the overlays have the same chrome sizes,
// so just pick the first one.
let clone = this._windows[0];
let [topBorder, bottomBorder] = clone.chromeHeights();
let [leftBorder, rightBorder] = clone.chromeWidths();
rowSpacing += (topBorder + bottomBorder) / 2;
columnSpacing += (rightBorder + leftBorder) / 2;
padding.top += topBorder;
padding.bottom += bottomBorder;
padding.left += leftBorder;
padding.right += rightBorder;
return [rowSpacing, columnSpacing, padding];
}
_computeLayout(windows) {
let [rowSpacing, columnSpacing, padding] = this._getSpacingAndPadding();
let area = padArea(this._fullGeometry, padding);
return this._getBestLayout(windows, area, rowSpacing, columnSpacing);
} }
_onCloneSelected(clone, time) { _onCloneSelected(clone, time) {
@ -1666,15 +1285,6 @@ class Workspace extends St.Widget {
if (this._isMyWindow(window)) if (this._isMyWindow(window))
return false; return false;
// Set a hint on the Mutter.Window so its initial position
// in the new workspace will be correct
window._overviewHint = {
x: actor.x,
y: actor.y,
width: actor.width,
heigth: actor.height,
};
// We need to move the window before changing the workspace, because // We need to move the window before changing the workspace, because
// the move itself could cause a workspace change if the window enters // the move itself could cause a workspace change if the window enters
// the primary monitor // the primary monitor

View File

@ -25,13 +25,9 @@ var WorkspacesViewBase = GObject.registerClass({
this.connect('destroy', this._onDestroy.bind(this)); this.connect('destroy', this._onDestroy.bind(this));
global.focus_manager.add_group(this); global.focus_manager.add_group(this);
// The actor itself isn't a drop target, so we don't want to pick on its area
this.set_size(0, 0);
this._monitorIndex = monitorIndex; this._monitorIndex = monitorIndex;
this._fullGeometry = null; this._fullGeometry = null;
this._actualGeometry = null;
this._inDrag = false; this._inDrag = false;
this._windowDragBeginId = Main.overview.connect('window-drag-begin', this._dragBegin.bind(this)); this._windowDragBeginId = Main.overview.connect('window-drag-begin', this._dragBegin.bind(this));
@ -63,12 +59,13 @@ var WorkspacesViewBase = GObject.registerClass({
setFullGeometry(geom) { setFullGeometry(geom) {
this._fullGeometry = geom; this._fullGeometry = geom;
this._syncFullGeometry();
} }
setActualGeometry(geom) { vfunc_allocate(box) {
this._actualGeometry = geom; this.set_allocation(box);
this._syncActualGeometry();
for (const child of this)
child.allocate_available_size(0, 0, box.get_width(), box.get_height());
} }
}); });
@ -100,12 +97,9 @@ class WorkspacesView extends WorkspacesViewBase {
this._updateWorkspaceActors(false); this._updateWorkspaceActors(false);
}); });
this._overviewShownId = Main.overview.connect('shown', () => {
this._overviewShownId = this.clip_to_allocation = true;
Main.overview.connect('shown', () => { });
this.set_clip(this._fullGeometry.x, this._fullGeometry.y,
this._fullGeometry.width, this._fullGeometry.height);
});
this._switchWorkspaceNotifyId = this._switchWorkspaceNotifyId =
global.window_manager.connect('switch-workspace', global.window_manager.connect('switch-workspace',
@ -117,16 +111,6 @@ class WorkspacesView extends WorkspacesViewBase {
this._workspaces[i].setReservedSlot(window); this._workspaces[i].setReservedSlot(window);
} }
_syncFullGeometry() {
for (let i = 0; i < this._workspaces.length; i++)
this._workspaces[i].setFullGeometry(this._fullGeometry);
}
_syncActualGeometry() {
for (let i = 0; i < this._workspaces.length; i++)
this._workspaces[i].setActualGeometry(this._actualGeometry);
}
getActiveWorkspace() { getActiveWorkspace() {
let workspaceManager = global.workspace_manager; let workspaceManager = global.workspace_manager;
let active = workspaceManager.get_active_workspace_index(); let active = workspaceManager.get_active_workspace_index();
@ -144,7 +128,7 @@ class WorkspacesView extends WorkspacesViewBase {
} }
animateFromOverview(animationType) { animateFromOverview(animationType) {
this.remove_clip(); this.clip_to_allocation = false;
for (let w = 0; w < this._workspaces.length; w++) { for (let w = 0; w < this._workspaces.length; w++) {
if (animationType == AnimationType.ZOOM) if (animationType == AnimationType.ZOOM)
@ -242,12 +226,8 @@ class WorkspacesView extends WorkspacesViewBase {
} }
} }
if (this._fullGeometry) { if (this._fullGeometry)
this._updateWorkspaceActors(false); this._updateWorkspaceActors(false);
this._syncFullGeometry();
}
if (this._actualGeometry)
this._syncActualGeometry();
} }
_activeWorkspaceChanged(_wm, _from, _to, _direction) { _activeWorkspaceChanged(_wm, _from, _to, _direction) {
@ -353,14 +333,6 @@ class ExtraWorkspaceView extends WorkspacesViewBase {
this._workspace.setReservedSlot(window); this._workspace.setReservedSlot(window);
} }
_syncFullGeometry() {
this._workspace.setFullGeometry(this._fullGeometry);
}
_syncActualGeometry() {
this._workspace.setActualGeometry(this._actualGeometry);
}
getActiveWorkspace() { getActiveWorkspace() {
return this._workspace; return this._workspace;
} }
@ -452,6 +424,7 @@ class WorkspacesDisplay extends St.Widget {
this._scrollEventId = 0; this._scrollEventId = 0;
this._keyPressEventId = 0; this._keyPressEventId = 0;
this._scrollTimeoutId = 0; this._scrollTimeoutId = 0;
this._syncActualGeometryLater = 0;
this._actualGeometry = null; this._actualGeometry = null;
this._fullGeometry = null; this._fullGeometry = null;
@ -476,6 +449,11 @@ class WorkspacesDisplay extends St.Widget {
this._parentSetLater = 0; this._parentSetLater = 0;
} }
if (this._syncActualGeometryLater) {
Meta.later_remove(this._syncActualGeometryLater);
this._syncActualGeometryLater = 0;
}
if (this._scrollTimeoutId !== 0) { if (this._scrollTimeoutId !== 0) {
GLib.source_remove(this._scrollTimeoutId); GLib.source_remove(this._scrollTimeoutId);
this._scrollTimeoutId = 0; this._scrollTimeoutId = 0;
@ -755,7 +733,17 @@ class WorkspacesDisplay extends St.Widget {
const height = this.allocation.get_height(); const height = this.allocation.get_height();
this._actualGeometry = { x, y, width, height }; this._actualGeometry = { x, y, width, height };
this._syncWorkspacesActualGeometry();
if (this._syncActualGeometryLater > 0)
return;
this._syncActualGeometryLater =
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
this._syncWorkspacesActualGeometry();
this._syncActualGeometryLater = 0;
return GLib.SOURCE_REMOVE;
});
} }
_syncWorkspacesActualGeometry() { _syncWorkspacesActualGeometry() {
@ -765,7 +753,9 @@ class WorkspacesDisplay extends St.Widget {
let monitors = Main.layoutManager.monitors; let monitors = Main.layoutManager.monitors;
for (let i = 0; i < monitors.length; i++) { for (let i = 0; i < monitors.length; i++) {
let geometry = i === this._primaryIndex ? this._actualGeometry : monitors[i]; let geometry = i === this._primaryIndex ? this._actualGeometry : monitors[i];
this._workspacesViews[i].setActualGeometry(geometry); const { x, y, width, height } = geometry;
this._workspacesViews[i].set({ x, y, width, height });
} }
} }