gnome-shell-extensions/window-list-reordering.patch

2058 lines
66 KiB
Diff

From 60b46c8d82dfbdcfdf54d3f022318637870a3756 Mon Sep 17 00:00:00 2001
From: Jakub Steiner <jimmac@gmail.com>
Date: Tue, 16 Jul 2024 09:40:53 +0200
Subject: [PATCH 01/24] window-list: Update styling
- Contemporary look. Fewer borders, thinner outlines for workspace indicators
- Lacks the designed unfocused window separators.
- Relies on https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/328
Fixes https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/issues/421
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/330>
---
extensions/window-list/classic.css | 68 ++++++++-----
extensions/window-list/stylesheet.css | 96 +++++++------------
.../workspace-indicator/stylesheet-dark.css | 4 +-
3 files changed, 78 insertions(+), 90 deletions(-)
diff --git a/extensions/window-list/classic.css b/extensions/window-list/classic.css
index d7ceb062..088c9478 100644
--- a/extensions/window-list/classic.css
+++ b/extensions/window-list/classic.css
@@ -1,22 +1,24 @@
+/*
+ * SPDX-FileCopyrightText: 2013 Florian Müllner <fmuellner@gnome.org>
+ * SPDX-FileCopyrightText: 2015 Jakub Steiner <jimmac@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
@import url("stylesheet.css");
@import url("stylesheet-workspace-switcher-light.css");
#panel.bottom-panel {
border-top-width: 1px;
border-bottom-width: 0px;
- height: 2.25em ;
- padding: 2px;
+ height: 2.5em;
}
- .bottom-panel .window-button > StWidget,
- .bottom-panel .window-picker-toggle > StWidget {
- color: #2e3436;
- background-color: #eee;
+ .bottom-panel .window-button > StWidget {
border-radius: 3px;
padding: 3px 6px 1px;
box-shadow: none;
text-shadow: none;
- border: 1px solid rgba(0,0,0,0.2);
}
.bottom-panel .window-button > StWidget {
@@ -24,27 +26,43 @@
max-width: 18.75em;
}
- .bottom-panel .window-button:hover > StWidget,
- .bottom-panel .window-picker-toggle:hover > StWidget {
- background-color: #f9f9f9;
- }
+ .window-button > StWidget {
+ color: #000;
+ background-color: transparent;
+}
- .bottom-panel .window-button:active > StWidget,
- .bottom-panel .window-button:focus > StWidget {
- box-shadow: inset 0 1px 3px rgba(0,0,0,0.1);
- }
+.window-button > StWidget {
+ -st-natural-width: 18.75em;
+ max-width: 18.75em;
+}
- .bottom-panel .window-button.focused > StWidget,
- .bottom-panel .window-picker-toggle:checked > StWidget {
- background-color: #ccc;
- box-shadow: inset 0 1px 3px rgba(0,0,0,0.1);
- }
+.window-button:hover > StWidget {
+ background-color: st-darken(#eee,5%);
+}
- .bottom-panel .window-button.focused:hover > StWidget {
- background-color: #e9e9e9;
+.window-button:active > StWidget,
+.window-button:focus > StWidget {
+ background-color: st-darken(#eee, 10%);
+}
+
+.window-button.focused > StWidget {
+ background-color: st-darken(#eee,15%);
+}
+
+ .window-button.focused:hover > StWidget {
+ background-color: st-darken(#eee, 20%);
}
- .bottom-panel .window-button.minimized > StWidget {
- color: #888;
- box-shadow: none;
+ .window-button.focused:active > StWidget {
+ background-color: st-darken(#eee, 25%);
}
+
+.window-button.minimized > StWidget {
+ color: #aaa;
+ background-color: #f9f9f9;
+}
+
+.window-button.minimized:active > StWidget {
+ color: #aaa;
+ background-color: #f9f9f9;
+}
diff --git a/extensions/window-list/stylesheet.css b/extensions/window-list/stylesheet.css
index 4ba47f07..b9087971 100644
--- a/extensions/window-list/stylesheet.css
+++ b/extensions/window-list/stylesheet.css
@@ -1,3 +1,9 @@
+/*
+ * SPDX-FileCopyrightText: 2012 Florian Müllner <fmuellner@gnome.org>
+ * SPDX-FileCopyrightText: 2013 Giovanni Campagna <gcampagna@src.gnome.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
@import url("stylesheet-workspace-switcher-dark.css");
.window-list {
@@ -5,8 +11,14 @@
font-size: 10pt;
}
+.bottom-panel {
+ background-color: #000000;
+ border-top-width: 0px;
+ height: 2.45em;
+}
+
.window-button {
- padding: 1px;
+ padding: 4px, 3px;
}
.window-button:first-child:ltr {
@@ -21,22 +33,12 @@
spacing: 4px;
}
-.window-button > StWidget,
-.window-picker-toggle > StWidget {
- color: #bbb;
- background-color: black;
- border-radius: 2px;
+.window-button > StWidget {
+ color: #fff;
+ background-color: transparent;
+ border-radius: 4px;
padding: 3px 6px 1px;
- box-shadow: inset 1px 1px 4px rgba(255,255,255,0.5);
- text-shadow: 1px 1px 4px rgba(0,0,0,0.8);
-}
-
-.window-picker-toggle {
- padding: 3px;
-}
-
-.window-picker-toggle > StWidet {
- border: 1px solid rgba(255,255,255,0.3);
+ transition: 100ms ease;
}
.window-button > StWidget {
@@ -44,35 +46,35 @@
max-width: 18.75em;
}
-.window-button:hover > StWidget,
-.window-picker-toggle:hover > StWidget {
- color: white;
- background-color: #1f1f1f;
+.window-button:hover > StWidget {
+ background-color: #303030;
}
.window-button:active > StWidget,
.window-button:focus > StWidget {
- box-shadow: inset 2px 2px 4px rgba(255,255,255,0.5);
+ background-color: st-lighten(#303030, 5%);
}
-.window-button.focused > StWidget,
-.window-picker-toggle:checked > StWidget {
- color: white;
- box-shadow: inset 1px 1px 4px rgba(255,255,255,0.7);
+.window-button.focused > StWidget {
+ background-color: #5b5b5b;
}
-.window-button.focused:active > StWidget,
-.window-picker-toggle:checked:active > StWidget {
- box-shadow: inset 2px 2px 4px rgba(255,255,255,0.7);
-}
+ .window-button.focused:hover > StWidget {
+ background-color: st-lighten(#5b5b5b, 5%);
+ }
+
+ .window-button.focused:active > StWidget {
+ background-color: st-lighten(#5b5b5b, 10%);
+ }
.window-button.minimized > StWidget {
color: #666;
- box-shadow: inset -1px -1px 4px rgba(255,255,255,0.5);
+ background-color: #161616;
}
.window-button.minimized:active > StWidget {
- box-shadow: inset -2px -2px 4px rgba(255,255,255,0.5);
+ color: #666;
+ background-color: #161616;
}
.window-button-icon {
@@ -80,38 +82,6 @@
height: 24px;
}
-.window-list-workspace-indicator .status-label-bin {
- background-color: rgba(200, 200, 200, .3);
- border: 1px solid #cccccc;
- padding: 0 3px;
- margin: 3px;
-}
-
-.window-list-workspace-indicator .workspaces-box {
- spacing: 3px;
- padding: 3px;
-}
-
-.window-list-workspace-indicator .workspace {
- border: 2px solid #000;
- width: 52px;
- border-radius: 4px;
- background-color: #595959;
-}
-
-.window-list-workspace-indicator .workspace.active {
- border-color: #fff;
-}
-
-.window-list-workspace-indicator-window-preview {
- background-color: #bebebe;
- border: 1px solid #828282;
-}
-
-.window-list-workspace-indicator-window-preview.active {
- background-color: #d4d4d4;
-}
-
.notification {
font-weight: normal;
}
diff --git a/extensions/workspace-indicator/stylesheet-dark.css b/extensions/workspace-indicator/stylesheet-dark.css
index 017d844a..872c6afc 100644
--- a/extensions/workspace-indicator/stylesheet-dark.css
+++ b/extensions/workspace-indicator/stylesheet-dark.css
@@ -39,7 +39,7 @@
.workspace-indicator-menu .workspace,
.workspace-indicator .workspace {
- border: 2px solid transparent;
+ border: 1px solid transparent;
border-radius: 4px;
background-color: #3f3f3f;
}
@@ -55,7 +55,7 @@
.workspace-indicator-menu .workspace.active,
.workspace-indicator .workspace.active {
- border-color: #9f9f9f;
+ border-color: #fff;
}
.workspace-indicator-window-preview {
--
2.47.0
From b36d05f6d827a9063fb35a5120207718a2f30af1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Wed, 25 Sep 2024 03:36:08 +0200
Subject: [PATCH 02/24] window-list: Small stylesheet cleanup
The light stylesheet duplicates some declarations, and the
last occurrence matches what we already inherit from the
dark stylesheet.
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/337>
---
extensions/window-list/classic.css | 10 ----------
1 file changed, 10 deletions(-)
diff --git a/extensions/window-list/classic.css b/extensions/window-list/classic.css
index 088c9478..3a6ffd02 100644
--- a/extensions/window-list/classic.css
+++ b/extensions/window-list/classic.css
@@ -21,21 +21,11 @@
text-shadow: none;
}
- .bottom-panel .window-button > StWidget {
- -st-natural-width: 18.7em;
- max-width: 18.75em;
- }
-
.window-button > StWidget {
color: #000;
background-color: transparent;
}
-.window-button > StWidget {
- -st-natural-width: 18.75em;
- max-width: 18.75em;
-}
-
.window-button:hover > StWidget {
background-color: st-darken(#eee,5%);
}
--
2.47.0
From ad30fc0976bface7944beacc276c4ca85961f0d0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Tue, 25 Jun 2024 19:24:35 +0200
Subject: [PATCH 03/24] window-list: Don't use homogeneous layout
We want all buttons in the window list to have the same size,
but that's already achieved via max/natural-width in the CSS.
Not enforcing the equal size via the layout manager will allow
buttons to temporarily have a different size when we start
animating additions and removals.
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/325>
---
extensions/window-list/extension.js | 9 +--------
1 file changed, 1 insertion(+), 8 deletions(-)
diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js
index 688ca761..a59307e2 100644
--- a/extensions/window-list/extension.js
+++ b/extensions/window-list/extension.js
@@ -753,22 +753,15 @@ class WindowList extends St.Widget {
toggle.connect('notify::checked',
this._updateWindowListVisibility.bind(this));
- let layout = new Clutter.BoxLayout({ homogeneous: true });
- this._windowList = new St.Widget({
+ this._windowList = new St.BoxLayout({
style_class: 'window-list',
reactive: true,
- layout_manager: layout,
x_align: Clutter.ActorAlign.START,
x_expand: true,
y_expand: true,
});
box.add_child(this._windowList);
- this._windowList.connect('style-changed', () => {
- let node = this._windowList.get_theme_node();
- let spacing = node.get_length('spacing');
- this._windowList.layout_manager.spacing = spacing;
- });
this._windowList.connect('scroll-event', this._onScrollEvent.bind(this));
let indicatorsBox = new St.BoxLayout({ x_align: Clutter.ActorAlign.END });
--
2.47.0
From 987a526802bae4970cc41c7504f98b5ac568d62d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Tue, 18 Jun 2024 18:55:05 +0200
Subject: [PATCH 04/24] window-list: Don't hide window button while unmanaging
This will allow to animate the transition.
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/325>
---
extensions/window-list/extension.js | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js
index a59307e2..8ac59dd0 100644
--- a/extensions/window-list/extension.js
+++ b/extensions/window-list/extension.js
@@ -417,6 +417,10 @@ class WindowButton extends BaseButton {
});
this._updateVisibility();
+ this._unmanaging = false;
+ this._unmanagingId = metaWindow.connect('unmanaging',
+ () => (this._unmanaging = true));
+
this._windowTitle = new WindowTitle(this.metaWindow);
this.set_child(this._windowTitle);
this.label_actor = this._windowTitle.label_actor;
@@ -466,6 +470,9 @@ class WindowButton extends BaseButton {
}
_updateVisibility() {
+ if (this._unmanaging)
+ return;
+
this.visible = this._isWindowVisible(this.metaWindow);
}
@@ -476,6 +483,7 @@ class WindowButton extends BaseButton {
_onDestroy() {
super._onDestroy();
this.metaWindow.disconnect(this._skipTaskbarId);
+ this.metaWindow.disconnect(this._unmanagingId);
this.metaWindow.disconnect(this._workspaceChangedId);
global.display.disconnect(this._notifyFocusId);
this._contextMenu.destroy();
--
2.47.0
From 9d37d880dacb3a29c4976b5442128e7269048d0e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Tue, 18 Jun 2024 18:59:38 +0200
Subject: [PATCH 05/24] window-list: Animate buttons in and out
Buttons are currently added and removed from the list without
any transitions, which gives the list a "jumpy" feel. Instead,
do what we do elsewhere and smoothly animate additions and
removals by re-using the dash's ItemContainer class.
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/325>
---
extensions/window-list/extension.js | 34 ++++++++++++++++-------------
1 file changed, 19 insertions(+), 15 deletions(-)
diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js
index 8ac59dd0..cb9e7160 100644
--- a/extensions/window-list/extension.js
+++ b/extensions/window-list/extension.js
@@ -6,6 +6,7 @@ const ExtensionUtils = imports.misc.extensionUtils;
const Main = imports.ui.main;
const Overview = imports.ui.overview;
const PopupMenu = imports.ui.popupMenu;
+const { DashItemContainer } = imports.ui.dash;
const Me = ExtensionUtils.getCurrentExtension();
const { WindowPicker, WindowPickerToggle } = Me.imports.windowPicker;
@@ -229,22 +230,25 @@ const BaseButton = GObject.registerClass({
GObject.ParamFlags.READWRITE,
false),
},
-}, class BaseButton extends St.Button {
+}, class BaseButton extends DashItemContainer {
_init(perMonitor, monitorIndex) {
this._perMonitor = perMonitor;
this._monitorIndex = monitorIndex;
this._ignoreWorkspace = false;
- super._init({
+ super._init();
+
+ this._button = new St.Button({
style_class: 'window-button',
can_focus: true,
x_expand: true,
button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
});
+ this.setChild(this._button);
this.connect('notify::allocation',
this._updateIconGeometry.bind(this));
- this.connect('clicked', this._onClicked.bind(this));
+ this._button.connect('clicked', this._onClicked.bind(this));
this.connect('destroy', this._onDestroy.bind(this));
this.connect('popup-menu', this._onPopupMenu.bind(this));
@@ -422,7 +426,7 @@ class WindowButton extends BaseButton {
() => (this._unmanaging = true));
this._windowTitle = new WindowTitle(this.metaWindow);
- this.set_child(this._windowTitle);
+ this._button.set_child(this._windowTitle);
this.label_actor = this._windowTitle.label_actor;
this._contextMenu = new WindowContextMenu(this, this.metaWindow);
@@ -558,7 +562,7 @@ class AppButton extends BaseButton {
this._updateVisibility();
let stack = new St.Widget({ layout_manager: new Clutter.BinLayout() });
- this.set_child(stack);
+ this._button.set_child(stack);
this._singleWindowTitle = new St.Bin({
x_expand: true,
@@ -851,7 +855,7 @@ class WindowList extends St.Widget {
this._windowSignals = new Map();
this._windowCreatedId = global.display.connect(
- 'window-created', (dsp, win) => this._addWindow(win));
+ 'window-created', (dsp, win) => this._addWindow(win, true));
this._dragBeginId = Main.xdndHandler.connect('drag-begin',
this._monitorDrag.bind(this));
@@ -980,14 +984,14 @@ class WindowList extends St.Widget {
w2.metaWindow.get_stable_sequence();
});
for (let i = 0; i < windows.length; i++)
- this._addWindow(windows[i].metaWindow);
+ this._addWindow(windows[i].metaWindow, false);
} else {
let apps = this._appSystem.get_running().sort((a1, a2) => {
return _getAppStableSequence(a1) -
_getAppStableSequence(a2);
});
for (let i = 0; i < apps.length; i++)
- this._addApp(apps[i]);
+ this._addApp(apps[i], false);
}
}
@@ -1001,26 +1005,26 @@ class WindowList extends St.Widget {
return;
if (app.state === Shell.AppState.RUNNING)
- this._addApp(app);
+ this._addApp(app, true);
else if (app.state === Shell.AppState.STOPPED)
this._removeApp(app);
}
- _addApp(app) {
+ _addApp(app, animate) {
let button = new AppButton(app, this._perMonitor, this._monitor.index);
this._settings.bind('display-all-workspaces',
button, 'ignore-workspace', Gio.SettingsBindFlags.GET);
this._windowList.add_child(button);
+ button.show(animate);
}
_removeApp(app) {
let children = this._windowList.get_children();
let child = children.find(c => c.app === app);
- if (child)
- child.destroy();
+ child?.animateOutAndDestroy();
}
- _addWindow(win) {
+ _addWindow(win, animate) {
if (!this._grouped)
this._checkGrouping();
@@ -1038,6 +1042,7 @@ class WindowList extends St.Widget {
this._settings.bind('display-all-workspaces',
button, 'ignore-workspace', Gio.SettingsBindFlags.GET);
this._windowList.add_child(button);
+ button.show(animate);
}
_removeWindow(win) {
@@ -1054,8 +1059,7 @@ class WindowList extends St.Widget {
let children = this._windowList.get_children();
let child = children.find(c => c.metaWindow === win);
- if (child)
- child.destroy();
+ child?.animateOutAndDestroy();
}
_monitorDrag() {
--
2.47.0
From 1f2213658f6d80ab7a9710dd5aa773499747efce Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Tue, 18 Jun 2024 20:08:56 +0200
Subject: [PATCH 06/24] window-list: Replace custom tooltip implementation
DashItemContainer already has support for showing a tooltip-like
label, so now that we use that for animating items, we can use
it for tooltips as well.
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/325>
---
extensions/window-list/extension.js | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js
index cb9e7160..0dd3775e 100644
--- a/extensions/window-list/extension.js
+++ b/extensions/window-list/extension.js
@@ -246,6 +246,13 @@ const BaseButton = GObject.registerClass({
});
this.setChild(this._button);
+ this._button.connect('notify::hover', () => {
+ if (this._button.hover)
+ this.showLabel();
+ else
+ this.hideLabel();
+ });
+
this.connect('notify::allocation',
this._updateIconGeometry.bind(this));
this._button.connect('clicked', this._onClicked.bind(this));
@@ -287,6 +294,18 @@ const BaseButton = GObject.registerClass({
this._updateVisibility();
}
+ showLabel() {
+ const [, , preferredTitleWidth] = this.label_actor.get_preferred_size();
+ const maxTitleWidth = this.label_actor.allocation.get_width();
+ const isTitleFullyShown = preferredTitleWidth <= maxTitleWidth;
+
+ const labelText = isTitleFullyShown
+ ? '' : this.label_actor.text;
+
+ this.setLabelText(labelText);
+ super.showLabel();
+ }
+
_setLongPressTimeout() {
if (this._longPressTimeoutId)
return;
--
2.47.0
From 59d45cbef4ab701146325c46aea8bc6ffd13ca1f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Sat, 13 Jul 2024 00:11:19 +0200
Subject: [PATCH 07/24] window-list: Fix .focused styling
Commit 039c66e7b7c wrapped the button in a container to
animate transitions, but didn't adjust the `.focused`
styling to still apply to the button (where it is
expected) rather than the wrapper.
Fix that.
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/328>
---
extensions/window-list/extension.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js
index 0dd3775e..ec37f50f 100644
--- a/extensions/window-list/extension.js
+++ b/extensions/window-list/extension.js
@@ -378,9 +378,9 @@ const BaseButton = GObject.registerClass({
_updateStyle() {
if (this._isFocused())
- this.add_style_class_name('focused');
+ this._button.add_style_class_name('focused');
else
- this.remove_style_class_name('focused');
+ this._button.remove_style_class_name('focused');
}
_windowEnteredOrLeftMonitor(_metaDisplay, _monitorIndex, _metaWindow) {
--
2.47.0
From 1c244245c09bc51247cb66786c0c303ad70a7c34 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Wed, 25 Sep 2024 02:23:41 +0200
Subject: [PATCH 08/24] window-list: Split out AppTitle class
Even though it's just a box with icon and label, it's cleaner to
have a dedicated class.
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/337>
---
extensions/window-list/extension.js | 65 +++++++++++++++++++----------
1 file changed, 42 insertions(+), 23 deletions(-)
diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js
index ec37f50f..9ced0940 100644
--- a/extensions/window-list/extension.js
+++ b/extensions/window-list/extension.js
@@ -221,6 +221,47 @@ class WindowTitle extends St.BoxLayout {
}
});
+const AppTitle = GObject.registerClass(
+class AppTitle extends St.BoxLayout {
+ _init(app) {
+ super._init({
+ style_class: 'window-button-box',
+ x_expand: true,
+ y_expand: true,
+ });
+
+ this._app = app;
+
+ const icon = new St.Bin({
+ style_class: 'window-button-icon',
+ child: app.create_icon_texture(ICON_TEXTURE_SIZE),
+ });
+ this.add_child(icon);
+
+ let label = new St.Label({
+ text: app.get_name(),
+ y_align: Clutter.ActorAlign.CENTER,
+ });
+ this.add_child(label);
+ this.label_actor = label;
+
+ this._textureCache = St.TextureCache.get_default();
+ this._iconThemeChangedId =
+ this._textureCache.connect('icon-theme-changed', () => {
+ icon.child = app.create_icon_texture(ICON_TEXTURE_SIZE);
+ });
+
+ this.connect('destroy', this._onDestroy.bind(this));
+ }
+
+ _onDestroy() {
+ if (this._iconThemeChangedId)
+ this._textureCache.disconnect(this._iconThemeChangedId);
+ this._iconThemeChangedId = 0;
+ this._textureCache = null;
+ }
+});
+
const BaseButton = GObject.registerClass({
GTypeFlags: GObject.TypeFlags.ABSTRACT,
@@ -588,25 +629,9 @@ class AppButton extends BaseButton {
});
stack.add_actor(this._singleWindowTitle);
- this._multiWindowTitle = new St.BoxLayout({
- style_class: 'window-button-box',
- x_expand: true,
- });
+ this._multiWindowTitle = new AppTitle(app);
stack.add_actor(this._multiWindowTitle);
- this._icon = new St.Bin({
- style_class: 'window-button-icon',
- child: app.create_icon_texture(ICON_TEXTURE_SIZE),
- });
- this._multiWindowTitle.add(this._icon);
-
- let label = new St.Label({
- text: app.get_name(),
- y_align: Clutter.ActorAlign.CENTER,
- });
- this._multiWindowTitle.add(label);
- this._multiWindowTitle.label_actor = label;
-
this._menuManager = new PopupMenu.PopupMenuManager(this);
this._menu = new PopupMenu.PopupMenu(this, 0.5, St.Side.BOTTOM);
this._menu.connect('open-state-changed', _onMenuStateChanged);
@@ -620,12 +645,6 @@ class AppButton extends BaseButton {
this._appContextMenu.actor.hide();
Main.uiGroup.add_actor(this._appContextMenu.actor);
- this._textureCache = St.TextureCache.get_default();
- this._iconThemeChangedId =
- this._textureCache.connect('icon-theme-changed', () => {
- this._icon.child = app.create_icon_texture(ICON_TEXTURE_SIZE);
- });
-
this._windowsChangedId = this.app.connect(
'windows-changed', this._windowsChanged.bind(this));
this._windowsChanged();
--
2.47.0
From 04fe9d769db7ef66fb90d749b93a4e6aa1b6b2ac Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Wed, 25 Sep 2024 02:55:14 +0200
Subject: [PATCH 09/24] window-list: Simplify app button
Depending on the number of windows, the button either shows the
title of the lone window, or the app title for multiple windows.
While we always recreate the single-window title, we only create
the app title once and hide it as necessary. Avoiding re-creating
a simple actor 50% of mode transitions isn't worth the additional
complexity, so just handle both single- and multi-window titles
the same way.
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/337>
---
extensions/window-list/extension.js | 69 ++++++++++-------------------
1 file changed, 24 insertions(+), 45 deletions(-)
diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js
index 9ced0940..413e00ec 100644
--- a/extensions/window-list/extension.js
+++ b/extensions/window-list/extension.js
@@ -621,17 +621,6 @@ class AppButton extends BaseButton {
this.app = app;
this._updateVisibility();
- let stack = new St.Widget({ layout_manager: new Clutter.BinLayout() });
- this._button.set_child(stack);
-
- this._singleWindowTitle = new St.Bin({
- x_expand: true,
- });
- stack.add_actor(this._singleWindowTitle);
-
- this._multiWindowTitle = new AppTitle(app);
- stack.add_actor(this._multiWindowTitle);
-
this._menuManager = new PopupMenu.PopupMenuManager(this);
this._menu = new PopupMenu.PopupMenu(this, 0.5, St.Side.BOTTOM);
this._menu.connect('open-state-changed', _onMenuStateChanged);
@@ -640,11 +629,6 @@ class AppButton extends BaseButton {
this._menuManager.addMenu(this._menu);
Main.uiGroup.add_actor(this._menu.actor);
- this._appContextMenu = new AppContextMenu(this);
- this._appContextMenu.connect('open-state-changed', _onMenuStateChanged);
- this._appContextMenu.actor.hide();
- Main.uiGroup.add_actor(this._appContextMenu.actor);
-
this._windowsChangedId = this.app.connect(
'windows-changed', this._windowsChanged.bind(this));
this._windowsChanged();
@@ -691,37 +675,32 @@ class AppButton extends BaseButton {
}
_windowsChanged() {
- let windows = this.getWindowList();
- this._singleWindowTitle.visible = windows.length === 1;
- this._multiWindowTitle.visible = !this._singleWindowTitle.visible;
-
- if (this._singleWindowTitle.visible) {
- if (!this._windowTitle) {
- this.metaWindow = windows[0];
- this._windowTitle = new WindowTitle(this.metaWindow);
- this._singleWindowTitle.child = this._windowTitle;
- this._windowContextMenu = new WindowContextMenu(this, this.metaWindow);
- this._windowContextMenu.connect(
- 'open-state-changed', _onMenuStateChanged);
- Main.uiGroup.add_actor(this._windowContextMenu.actor);
- this._windowContextMenu.actor.hide();
- this._contextMenuManager.addMenu(this._windowContextMenu);
- }
- this._contextMenuManager.removeMenu(this._appContextMenu);
- this._contextMenu = this._windowContextMenu;
- this.label_actor = this._windowTitle.label_actor;
+ const windows = this.getWindowList();
+ const singleWindowMode = windows.length === 1;
+
+ if (this._singleWindowMode === singleWindowMode)
+ return;
+
+ this._singleWindowMode = singleWindowMode;
+
+ this._button.child?.destroy();
+ this._contextMenu?.destroy();
+
+ if (this._singleWindowMode) {
+ const [window] = windows;
+ this._button.child = new WindowTitle(window);
+ this._contextMenu = new WindowContextMenu(this, window);
} else {
- if (this._windowTitle) {
- this.metaWindow = null;
- this._singleWindowTitle.child = null;
- this._windowTitle = null;
- this._windowContextMenu.destroy();
- this._windowContextMenu = null;
- }
- this._contextMenu = this._appContextMenu;
- this._contextMenuManager.addMenu(this._appContextMenu);
- this.label_actor = this._multiWindowTitle.label_actor;
+ this._button.child = new AppTitle(this.app);
+ this._contextMenu = new AppContextMenu(this);
}
+
+ this.label_actor = this._button.child.label_actor;
+
+ this._contextMenu.connect('open-state-changed', _onMenuStateChanged);
+ Main.uiGroup.add_child(this._contextMenu.actor);
+ this._contextMenu.actor.hide();
+ this._contextMenuManager.addMenu(this._contextMenu);
}
_onClicked(actor, button) {
--
2.47.0
From 83ddccf1dfc4f04cc6be6cfe94fabed1cb78519a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Mon, 7 Oct 2024 17:22:04 +0200
Subject: [PATCH 10/24] window-list: Fix minimized styling
Commit 039c66e7b7c wrapped the button in a container to
animate transitions, but didn't adjust the `.minimized`
styling to still apply to the button (where it is
expected) rather than the wrapper.
Fix this just like commit c72b8b21 did for the
`.focused` styling.
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/342>
---
extensions/window-list/extension.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js
index 413e00ec..49abee7a 100644
--- a/extensions/window-list/extension.js
+++ b/extensions/window-list/extension.js
@@ -523,9 +523,9 @@ class WindowButton extends BaseButton {
super._updateStyle();
if (this.metaWindow.minimized)
- this.add_style_class_name('minimized');
+ this._button.add_style_class_name('minimized');
else
- this.remove_style_class_name('minimized');
+ this._button.remove_style_class_name('minimized');
}
_windowEnteredOrLeftMonitor(metaDisplay, monitorIndex, metaWindow) {
--
2.47.0
From 8fb8f2b466a45cb3651d9f0d7b09f00d15ac3f7e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Mon, 7 Oct 2024 17:10:43 +0200
Subject: [PATCH 11/24] window-list: Fix active state
Commit c72b8b21 fixed the styling of the active window's button,
but missed that the `active` property uses the style information
as well.
Adjust it to use the correct actor when checking for the style class.
Closes https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/issues/529
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/342>
---
extensions/window-list/extension.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js
index 49abee7a..c656a769 100644
--- a/extensions/window-list/extension.js
+++ b/extensions/window-list/extension.js
@@ -316,7 +316,7 @@ const BaseButton = GObject.registerClass({
}
get active() {
- return this.has_style_class_name('focused');
+ return this._button.has_style_class_name('focused');
}
// eslint-disable-next-line camelcase
--
2.47.0
From 2d9f3f4185206b5607d3043018af049f40055368 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Tue, 15 Oct 2024 17:48:52 +0200
Subject: [PATCH 12/24] window-list: Remove outdated style
A long time ago, the window list used to embed the bottom message
tray, which caused notifications to inherit the window-list's
font style.
Since that's no longer the case, we have no business in messing
with notification styling, so stop doing that.
---
extensions/window-list/stylesheet.css | 4 ----
1 file changed, 4 deletions(-)
diff --git a/extensions/window-list/stylesheet.css b/extensions/window-list/stylesheet.css
index b9087971..f02fca60 100644
--- a/extensions/window-list/stylesheet.css
+++ b/extensions/window-list/stylesheet.css
@@ -81,7 +81,3 @@
width: 24px;
height: 24px;
}
-
-.notification {
- font-weight: normal;
-}
--
2.47.0
From 543caf907e3bfe8e5b5c558e4b1bdda892e78caf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Thu, 26 Sep 2024 19:07:11 +0200
Subject: [PATCH 13/24] window-list: Split out some common code
Adding an app button and adding a window button involves some
shared steps, move those to a shared `_addButton()` method.
---
extensions/window-list/extension.js | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js
index c656a769..215dd15c 100644
--- a/extensions/window-list/extension.js
+++ b/extensions/window-list/extension.js
@@ -1027,14 +1027,18 @@ class WindowList extends St.Widget {
this._removeApp(app);
}
- _addApp(app, animate) {
- let button = new AppButton(app, this._perMonitor, this._monitor.index);
+ _addButton(button, animate) {
this._settings.bind('display-all-workspaces',
button, 'ignore-workspace', Gio.SettingsBindFlags.GET);
this._windowList.add_child(button);
button.show(animate);
}
+ _addApp(app, animate) {
+ const button = new AppButton(app, this._perMonitor, this._monitor.index);
+ this._addButton(button, animate);
+ }
+
_removeApp(app) {
let children = this._windowList.get_children();
let child = children.find(c => c.app === app);
@@ -1055,11 +1059,8 @@ class WindowList extends St.Widget {
this._windowSignals.set(
win, win.connect('unmanaged', () => this._removeWindow(win)));
- let button = new WindowButton(win, this._perMonitor, this._monitor.index);
- this._settings.bind('display-all-workspaces',
- button, 'ignore-workspace', Gio.SettingsBindFlags.GET);
- this._windowList.add_child(button);
- button.show(animate);
+ const button = new WindowButton(win, this._perMonitor, this._monitor.index);
+ this._addButton(button, animate);
}
_removeWindow(win) {
--
2.47.0
From 6015e2021f157d5dc13ea20446d15be9f16a5717 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Thu, 3 Oct 2024 17:19:31 +0200
Subject: [PATCH 14/24] window-list: Split out common TitleWidget class
Both app- and window title use the same structure, so add a shared
base class.
---
extensions/window-list/extension.js | 61 ++++++++++++++---------------
1 file changed, 30 insertions(+), 31 deletions(-)
diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js
index 215dd15c..1717d390 100644
--- a/extensions/window-list/extension.js
+++ b/extensions/window-list/extension.js
@@ -133,22 +133,35 @@ class WindowContextMenu extends PopupMenu.PopupMenu {
}
}
-const WindowTitle = GObject.registerClass(
-class WindowTitle extends St.BoxLayout {
- _init(metaWindow) {
- this._metaWindow = metaWindow;
-
+const TitleWidget = GObject.registerClass({
+ GTypeFlags: GObject.TypeFlags.ABSTRACT,
+}, class TitleWidget extends St.BoxLayout {
+ _init() {
super._init({
style_class: 'window-button-box',
x_expand: true,
y_expand: true,
});
- this._icon = new St.Bin({ style_class: 'window-button-icon' });
- this.add(this._icon);
- this.label_actor = new St.Label({ y_align: Clutter.ActorAlign.CENTER });
- this.label_actor.clutter_text.single_line_mode = true;
- this.add(this.label_actor);
+ this._icon = new St.Bin({
+ style_class: 'window-button-icon',
+ });
+ this.add_child(this._icon);
+
+ this._label = new St.Label({
+ y_align: Clutter.ActorAlign.CENTER,
+ });
+ this.add_child(this._label);
+ this.label_actor = this._label;
+ }
+});
+
+const WindowTitle = GObject.registerClass(
+class WindowTitle extends TitleWidget {
+ _init(metaWindow) {
+ super._init();
+
+ this._metaWindow = metaWindow;
this._textureCache = St.TextureCache.get_default();
this._iconThemeChangedId = this._textureCache.connect(
@@ -178,9 +191,9 @@ class WindowTitle extends St.BoxLayout {
return;
if (this._metaWindow.minimized)
- this.label_actor.text = '[%s]'.format(this._metaWindow.title);
+ this._label.text = '[%s]'.format(this._metaWindow.title);
else
- this.label_actor.text = this._metaWindow.title;
+ this._label.text = this._metaWindow.title;
}
_updateIcon() {
@@ -222,33 +235,19 @@ class WindowTitle extends St.BoxLayout {
});
const AppTitle = GObject.registerClass(
-class AppTitle extends St.BoxLayout {
+class AppTitle extends TitleWidget {
_init(app) {
- super._init({
- style_class: 'window-button-box',
- x_expand: true,
- y_expand: true,
- });
+ super._init();
this._app = app;
- const icon = new St.Bin({
- style_class: 'window-button-icon',
- child: app.create_icon_texture(ICON_TEXTURE_SIZE),
- });
- this.add_child(icon);
-
- let label = new St.Label({
- text: app.get_name(),
- y_align: Clutter.ActorAlign.CENTER,
- });
- this.add_child(label);
- this.label_actor = label;
+ this._icon.child = app.create_icon_texture(ICON_TEXTURE_SIZE);
+ this._label.text = app.get_name();
this._textureCache = St.TextureCache.get_default();
this._iconThemeChangedId =
this._textureCache.connect('icon-theme-changed', () => {
- icon.child = app.create_icon_texture(ICON_TEXTURE_SIZE);
+ this._icon.child = app.create_icon_texture(ICON_TEXTURE_SIZE);
});
this.connect('destroy', this._onDestroy.bind(this));
--
2.47.0
From 065d5175fdd69ee807aace784c4020f2a18c35b9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Thu, 3 Oct 2024 17:27:57 +0200
Subject: [PATCH 15/24] window-list: Add TitleWidget:abstract-label property
When true, the real label is replaced by a more abstract
representation. When used as drag actor, the focus is not
on identifying the window/app, but about picking a drop
location, and the reduced style helps with that.
---
extensions/window-list/extension.js | 22 ++++++++++++++++++++++
extensions/window-list/stylesheet.css | 6 ++++++
2 files changed, 28 insertions(+)
diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js
index 1717d390..31e69c6a 100644
--- a/extensions/window-list/extension.js
+++ b/extensions/window-list/extension.js
@@ -135,6 +135,12 @@ class WindowContextMenu extends PopupMenu.PopupMenu {
const TitleWidget = GObject.registerClass({
GTypeFlags: GObject.TypeFlags.ABSTRACT,
+ Properties: {
+ 'abstract-label': GObject.ParamSpec.boolean(
+ 'abstract-label', '', '',
+ GObject.ParamFlags.READWRITE,
+ false),
+ },
}, class TitleWidget extends St.BoxLayout {
_init() {
super._init({
@@ -153,6 +159,22 @@ const TitleWidget = GObject.registerClass({
});
this.add_child(this._label);
this.label_actor = this._label;
+
+ this.bind_property('abstract-label',
+ this._label, 'visible',
+ GObject.BindingFlags.SYNC_CREATE |
+ GObject.BindingFlags.INVERT_BOOLEAN);
+
+ this._abstractLabel = new St.Widget({
+ style_class: 'window-button-abstract-label',
+ x_expand: true,
+ y_expand: true,
+ });
+ this.add_child(this._abstractLabel);
+
+ this.bind_property('abstract-label',
+ this._abstractLabel, 'visible',
+ GObject.BindingFlags.SYNC_CREATE);
}
});
diff --git a/extensions/window-list/stylesheet.css b/extensions/window-list/stylesheet.css
index f02fca60..fce6bcc5 100644
--- a/extensions/window-list/stylesheet.css
+++ b/extensions/window-list/stylesheet.css
@@ -81,3 +81,9 @@
width: 24px;
height: 24px;
}
+
+.window-button-abstract-label {
+ background-color: #888;
+ border-radius: 99px;
+ margin: 6px;
+}
--
2.47.0
From ff26402bcd0b3df00c61728bcb76103cc0855147 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Wed, 25 Sep 2024 03:20:52 +0200
Subject: [PATCH 16/24] window-list: Split out `_createTitleActor()` hook
This will allow creating a suitable drag actor that matches the
current title. In particular this allows for a drag actor that
isn't based on `ClutterClone`, and therefore doesn't inherit
focus/active/minimize/etc. styles that don't make sense outside
the actual window list.
---
extensions/window-list/extension.js | 23 ++++++++++++++++++++---
1 file changed, 20 insertions(+), 3 deletions(-)
diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js
index 31e69c6a..075edb2d 100644
--- a/extensions/window-list/extension.js
+++ b/extensions/window-list/extension.js
@@ -418,6 +418,11 @@ const BaseButton = GObject.registerClass({
this._onClicked(this, 1);
}
+ _createTitleActor() {
+ throw new GObject.NotImplementedError(
+ `_createTitleActor in ${this.constructor.name}`);
+ }
+
_onClicked(_actor, _button) {
throw new GObject.NotImplementedError(
`_onClicked in ${this.constructor.name}`);
@@ -506,7 +511,7 @@ class WindowButton extends BaseButton {
this._unmanagingId = metaWindow.connect('unmanaging',
() => (this._unmanaging = true));
- this._windowTitle = new WindowTitle(this.metaWindow);
+ this._windowTitle = this._createTitleActor();
this._button.set_child(this._windowTitle);
this.label_actor = this._windowTitle.label_actor;
@@ -524,6 +529,10 @@ class WindowButton extends BaseButton {
this._updateStyle();
}
+ _createTitleActor() {
+ return new WindowTitle(this.metaWindow);
+ }
+
_onClicked(actor, button) {
if (this._contextMenu.isOpen) {
this._contextMenu.close();
@@ -709,13 +718,12 @@ class AppButton extends BaseButton {
if (this._singleWindowMode) {
const [window] = windows;
- this._button.child = new WindowTitle(window);
this._contextMenu = new WindowContextMenu(this, window);
} else {
- this._button.child = new AppTitle(this.app);
this._contextMenu = new AppContextMenu(this);
}
+ this._button.child = this._createTitleActor();
this.label_actor = this._button.child.label_actor;
this._contextMenu.connect('open-state-changed', _onMenuStateChanged);
@@ -724,6 +732,15 @@ class AppButton extends BaseButton {
this._contextMenuManager.addMenu(this._contextMenu);
}
+ _createTitleActor() {
+ if (this._singleWindowMode) {
+ const [window] = this.getWindowList();
+ return new WindowTitle(window);
+ } else {
+ return new AppTitle(this.app);
+ }
+ }
+
_onClicked(actor, button) {
let menuWasOpen = this._menu.isOpen;
if (menuWasOpen)
--
2.47.0
From 69d981b29008b6143fcfadb4ed1bbca762179f94 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Wed, 19 Jun 2024 13:01:37 +0200
Subject: [PATCH 17/24] window-list: Rename XDND related methods and props
The window list buttons themselves will become draggable, so
include "xdnd" in the existing drag handling to disambiguate
it.
---
extensions/window-list/extension.js | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js
index 075edb2d..c203453b 100644
--- a/extensions/window-list/extension.js
+++ b/extensions/window-list/extension.js
@@ -913,11 +913,11 @@ class WindowList extends St.Widget {
'window-created', (dsp, win) => this._addWindow(win, true));
this._dragBeginId = Main.xdndHandler.connect('drag-begin',
- this._monitorDrag.bind(this));
+ this._monitorXdndDrag.bind(this));
this._dragEndId = Main.xdndHandler.connect('drag-end',
- this._stopMonitoringDrag.bind(this));
- this._dragMonitor = {
- dragMotion: this._onDragMotion.bind(this),
+ this._stopMonitoringXdndDrag.bind(this));
+ this._xdndDragMonitor = {
+ dragMotion: this._onXdndDragMotion.bind(this),
};
this._dndTimeoutId = 0;
@@ -1118,16 +1118,16 @@ class WindowList extends St.Widget {
child?.animateOutAndDestroy();
}
- _monitorDrag() {
- DND.addDragMonitor(this._dragMonitor);
+ _monitorXdndDrag() {
+ DND.addDragMonitor(this._xdndDragMonitor);
}
- _stopMonitoringDrag() {
- DND.removeDragMonitor(this._dragMonitor);
+ _stopMonitoringXdndDrag() {
+ DND.removeDragMonitor(this._xdndDragMonitor);
this._removeActivateTimeout();
}
- _onDragMotion(dragEvent) {
+ _onXdndDragMotion(dragEvent) {
if (Main.overview.visible ||
!this.contains(dragEvent.targetActor)) {
this._removeActivateTimeout();
@@ -1196,7 +1196,7 @@ class WindowList extends St.Widget {
global.display.disconnect(this._fullscreenChangedId);
global.display.disconnect(this._windowCreatedId);
- this._stopMonitoringDrag();
+ this._stopMonitoringXdndDrag();
Main.xdndHandler.disconnect(this._dragBeginId);
Main.xdndHandler.disconnect(this._dragEndId);
--
2.47.0
From 00a9446d601d8968a059c8e632b74a7253ceea5c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Wed, 25 Sep 2024 04:13:25 +0200
Subject: [PATCH 18/24] window-list: Allow rearranging window buttons
We currently sort buttons by the stable sequence to get a persistent
and predictable order. However some users want to customize that
order, and rearrange the buttons as they see fit.
Support that use case by implementing drag-and-drop behavior based
on the overview's dash.
Closes https://gitlab.gnome.org/GNOME/gnome-shell-extensions/issues/4
---
extensions/window-list/classic.css | 5 +
extensions/window-list/extension.js | 140 ++++++++++++++++++++++++++
extensions/window-list/stylesheet.css | 14 ++-
3 files changed, 157 insertions(+), 2 deletions(-)
diff --git a/extensions/window-list/classic.css b/extensions/window-list/classic.css
index 3a6ffd02..74945657 100644
--- a/extensions/window-list/classic.css
+++ b/extensions/window-list/classic.css
@@ -56,3 +56,8 @@
color: #aaa;
background-color: #f9f9f9;
}
+
+.window-button-drag-actor {
+ background-color: #ddd;
+ border-color: #888;
+}
diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js
index c203453b..b4c027fd 100644
--- a/extensions/window-list/extension.js
+++ b/extensions/window-list/extension.js
@@ -18,6 +18,8 @@ const _ = Gettext.gettext;
const ICON_TEXTURE_SIZE = 24;
const DND_ACTIVATE_TIMEOUT = 500;
+const MIN_DRAG_UPDATE_INTERVAL = 500 * GLib.TIME_SPAN_MILLISECOND;
+
const GroupingMode = {
NEVER: 0,
AUTO: 1,
@@ -25,6 +27,14 @@ const GroupingMode = {
};
+const DragPlaceholderItem = GObject.registerClass(
+class DragPlaceholderItem extends DashItemContainer {
+ _init() {
+ super._init();
+ this.setChild(new St.Bin({ style_class: 'placeholder' }));
+ }
+});
+
function _minimizeOrActivateWindow(window) {
let focusWindow = global.display.focus_window;
if (focusWindow === window ||
@@ -283,6 +293,18 @@ class AppTitle extends TitleWidget {
}
});
+const DragActor = GObject.registerClass(
+class DragActor extends St.Bin {
+ _init(source, titleActor) {
+ super._init({
+ style_class: 'window-button-drag-actor',
+ child: titleActor,
+ width: source.width,
+ });
+
+ this.source = source;
+ }
+});
const BaseButton = GObject.registerClass({
GTypeFlags: GObject.TypeFlags.ABSTRACT,
@@ -292,6 +314,10 @@ const BaseButton = GObject.registerClass({
GObject.ParamFlags.READWRITE,
false),
},
+ Signals: {
+ 'drag-begin': {},
+ 'drag-end': {},
+ },
}, class BaseButton extends DashItemContainer {
_init(perMonitor, monitorIndex) {
this._perMonitor = perMonitor;
@@ -334,6 +360,15 @@ const BaseButton = GObject.registerClass({
'window-left-monitor',
this._windowEnteredOrLeftMonitor.bind(this));
}
+
+ this._button._delegate = this;
+ this._draggable = DND.makeDraggable(this._button);
+ this._draggable.connect('drag-begin', () => {
+ this._removeLongPressTimeout();
+ this.emit('drag-begin');
+ });
+ this._draggable.connect('drag-cancelled', () => this.emit('drag-end'));
+ this._draggable.connect('drag-end', () => this.emit('drag-end'));
}
get active() {
@@ -418,6 +453,17 @@ const BaseButton = GObject.registerClass({
this._onClicked(this, 1);
}
+ getDragActor() {
+ const titleActor = this._createTitleActor();
+ titleActor.set({ abstractLabel: true });
+
+ return new DragActor(this, titleActor);
+ }
+
+ getDragActorSource() {
+ return this;
+ }
+
_createTitleActor() {
throw new GObject.NotImplementedError(
`_createTitleActor in ${this.constructor.name}`);
@@ -920,9 +966,19 @@ class WindowList extends St.Widget {
dragMotion: this._onXdndDragMotion.bind(this),
};
+ this._itemDragMonitor = {
+ dragMotion: this._onItemDragMotion.bind(this),
+ };
+
this._dndTimeoutId = 0;
this._dndWindow = null;
+ this._dragPlaceholder = null;
+ this._dragPlaceholderPos = -1;
+ this._lastPlaceholderUpdate = 0;
+
+ this._delegate = this;
+
this._settings = ExtensionUtils.getSettings();
this._settings.connect('changed::grouping-mode',
() => this._groupingModeChanged());
@@ -1068,6 +1124,14 @@ class WindowList extends St.Widget {
_addButton(button, animate) {
this._settings.bind('display-all-workspaces',
button, 'ignore-workspace', Gio.SettingsBindFlags.GET);
+
+ button.connect('drag-begin',
+ () => this._monitorItemDrag());
+ button.connect('drag-end', () => {
+ this._stopMonitoringItemDrag();
+ this._clearDragPlaceholder();
+ });
+
this._windowList.add_child(button);
button.show(animate);
}
@@ -1118,6 +1182,82 @@ class WindowList extends St.Widget {
child?.animateOutAndDestroy();
}
+ _clearDragPlaceholder() {
+ this._dragPlaceholder?.animateOutAndDestroy();
+ this._dragPlaceholder = null;
+ this._dragPlaceholderPos = -1;
+ }
+
+ handleDragOver(source, _actor, x, _y, _time) {
+ if (!(source instanceof BaseButton))
+ return DND.DragMotionResult.NO_DROP;
+
+ const buttons = this._windowList.get_children().filter(c => c instanceof BaseButton);
+ const buttonPos = buttons.indexOf(source);
+ const numButtons = buttons.length;
+ let boxWidth = this._windowList.width;
+
+ // Transform to window list coordinates for index calculation
+ // (mostly relevant for RTL to discard workspace indicator etc.)
+ x -= this._windowList.x;
+
+ const rtl = this.text_direction === Clutter.TextDirection.RTL;
+ let pos = rtl
+ ? numButtons - Math.round(x * numButtons / boxWidth)
+ : Math.round(x * numButtons / boxWidth);
+
+ pos = Math.clamp(pos, 0, numButtons);
+
+ const timeDelta =
+ GLib.get_monotonic_time() - this._lastPlaceholderUpdate;
+
+ if (pos !== this._dragPlaceholderPos && timeDelta >= MIN_DRAG_UPDATE_INTERVAL) {
+ this._clearDragPlaceholder();
+ this._dragPlaceholderPos = pos;
+
+ this._lastPlaceholderUpdate = GLib.get_monotonic_time();
+
+ // Don't allow positioning before or after self
+ if (pos === buttonPos || pos === buttonPos + 1)
+ return DND.DragMotionResult.CONTINUE;
+
+ this._dragPlaceholder = new DragPlaceholderItem();
+ const sibling = buttons[pos];
+ if (sibling)
+ this._windowList.insert_child_below(this._dragPlaceholder, sibling);
+ else
+ this._windowList.insert_child_above(this._dragPlaceholder, null);
+ this._dragPlaceholder.show(true);
+ }
+
+ return this._dragPlaceholder
+ ? DND.DragMotionResult.MOVE_DROP
+ : DND.DragMotionResult.NO_DROP;
+ }
+
+ acceptDrop(source, _actor, _x, _y, _time) {
+ if (this._dragPlaceholderPos >= 0)
+ this._windowList.set_child_at_index(source, this._dragPlaceholderPos);
+
+ this._clearDragPlaceholder();
+
+ return true;
+ }
+
+ _monitorItemDrag() {
+ DND.addDragMonitor(this._itemDragMonitor);
+ }
+
+ _stopMonitoringItemDrag() {
+ DND.removeDragMonitor(this._itemDragMonitor);
+ }
+
+ _onItemDragMotion(dragEvent) {
+ if (!this._windowList.contains(dragEvent.targetActor))
+ this._clearDragPlaceholder();
+ return DND.DragMotionResult.CONTINUE;
+ }
+
_monitorXdndDrag() {
DND.addDragMonitor(this._xdndDragMonitor);
}
diff --git a/extensions/window-list/stylesheet.css b/extensions/window-list/stylesheet.css
index fce6bcc5..c92081d2 100644
--- a/extensions/window-list/stylesheet.css
+++ b/extensions/window-list/stylesheet.css
@@ -17,10 +17,19 @@
height: 2.45em;
}
-.window-button {
+.window-button,
+.window-button-drag-actor {
padding: 4px, 3px;
}
+.window-button-drag-actor {
+ background-color: #444;
+ border-radius: 7px;
+ border-width: 2px;
+ border-color: #fff;
+ box-shadow: 0 1px 2px rgba(0,0,0,0.1);
+}
+
.window-button:first-child:ltr {
padding-left: 2px;
}
@@ -41,7 +50,8 @@
transition: 100ms ease;
}
-.window-button > StWidget {
+.window-button > StWidget,
+.window-list .placeholder {
-st-natural-width: 18.75em;
max-width: 18.75em;
}
--
2.47.0
From 99d4e460e634c567bd920a73e6d781bfae73e63d Mon Sep 17 00:00:00 2001
From: Jakub Steiner <jimmac@gmail.com>
Date: Thu, 3 Oct 2024 14:18:32 +0200
Subject: [PATCH 19/24] window-list: Indicate drop target more prominently
The drop target is the main focus of the drag operation, so make
its styling more prominent.
---
extensions/window-list/classic.css | 5 ++++-
extensions/window-list/stylesheet.css | 6 ++++++
2 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/extensions/window-list/classic.css b/extensions/window-list/classic.css
index 74945657..1147984c 100644
--- a/extensions/window-list/classic.css
+++ b/extensions/window-list/classic.css
@@ -23,7 +23,6 @@
.window-button > StWidget {
color: #000;
- background-color: transparent;
}
.window-button:hover > StWidget {
@@ -61,3 +60,7 @@
background-color: #ddd;
border-color: #888;
}
+
+.window-list .placeholder {
+ border-color: rgba(0,0,0,0.5);
+}
diff --git a/extensions/window-list/stylesheet.css b/extensions/window-list/stylesheet.css
index c92081d2..4c06ebc0 100644
--- a/extensions/window-list/stylesheet.css
+++ b/extensions/window-list/stylesheet.css
@@ -56,6 +56,12 @@
max-width: 18.75em;
}
+.window-list .placeholder {
+ border: 1px solid rgba(255,255,255,0.4);
+ border-radius: 7px;
+ margin: 4px;
+}
+
.window-button:hover > StWidget {
background-color: #303030;
}
--
2.47.0
From a4f268ef85d14e4b4477140c3a6238c97c89efd7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Thu, 3 Oct 2024 17:05:42 +0200
Subject: [PATCH 20/24] window-list: Fade out drag source during drag
During a drag operation, the focus is on the where to drop the dragged
item, not to identify it or its origin.
---
extensions/window-list/extension.js | 18 ++++++++++++++++--
1 file changed, 16 insertions(+), 2 deletions(-)
diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js
index b4c027fd..7a7cecf0 100644
--- a/extensions/window-list/extension.js
+++ b/extensions/window-list/extension.js
@@ -20,6 +20,9 @@ const DND_ACTIVATE_TIMEOUT = 500;
const MIN_DRAG_UPDATE_INTERVAL = 500 * GLib.TIME_SPAN_MILLISECOND;
+const DRAG_OPACITY = 0.3;
+const DRAG_FADE_DURATION = 200;
+
const GroupingMode = {
NEVER: 0,
AUTO: 1,
@@ -1125,9 +1128,20 @@ class WindowList extends St.Widget {
this._settings.bind('display-all-workspaces',
button, 'ignore-workspace', Gio.SettingsBindFlags.GET);
- button.connect('drag-begin',
- () => this._monitorItemDrag());
+ button.connect('drag-begin', () => {
+ button.ease({
+ opacity: 255 * DRAG_OPACITY,
+ duration: DRAG_FADE_DURATION,
+ });
+
+ this._monitorItemDrag();
+ });
button.connect('drag-end', () => {
+ button.ease({
+ opacity: 255,
+ duration: DRAG_FADE_DURATION,
+ });
+
this._stopMonitoringItemDrag();
this._clearDragPlaceholder();
});
--
2.47.0
From 37be1408c88d7fa91d833df714a9a7f0a3039670 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Wed, 9 Oct 2024 19:15:16 +0200
Subject: [PATCH 21/24] window-list: Shrink drag-actor size during drags
Like the previous commit, this helps with putting the focus on
the target location instead of the dragged item.
---
extensions/window-list/extension.js | 32 +++++++++++++++++++++++++++--
1 file changed, 30 insertions(+), 2 deletions(-)
diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js
index 7a7cecf0..d56eb38c 100644
--- a/extensions/window-list/extension.js
+++ b/extensions/window-list/extension.js
@@ -23,6 +23,8 @@ const MIN_DRAG_UPDATE_INTERVAL = 500 * GLib.TIME_SPAN_MILLISECOND;
const DRAG_OPACITY = 0.3;
const DRAG_FADE_DURATION = 200;
+const DRAG_RESIZE_DURATION = 400;
+
const GroupingMode = {
NEVER: 0,
AUTO: 1,
@@ -307,6 +309,23 @@ class DragActor extends St.Bin {
this.source = source;
}
+
+ setTargetWidth(width) {
+ const currentWidth = this.width;
+
+ // set width immediately so shell's DND code uses correct values
+ this.set({ width });
+
+ // then transition from the original to the new width
+ Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
+ this.set({ width: currentWidth });
+ this.ease({
+ width,
+ duration: DRAG_RESIZE_DURATION,
+ });
+ return GLib.SOURCE_REMOVE;
+ });
+ }
});
const BaseButton = GObject.registerClass({
@@ -370,7 +389,10 @@ const BaseButton = GObject.registerClass({
this._removeLongPressTimeout();
this.emit('drag-begin');
});
- this._draggable.connect('drag-cancelled', () => this.emit('drag-end'));
+ this._draggable.connect('drag-cancelled', () => {
+ this._draggable._dragActor?.setTargetWidth(this.width);
+ this.emit('drag-end');
+ });
this._draggable.connect('drag-end', () => this.emit('drag-end'));
}
@@ -460,7 +482,13 @@ const BaseButton = GObject.registerClass({
const titleActor = this._createTitleActor();
titleActor.set({ abstractLabel: true });
- return new DragActor(this, titleActor);
+ const dragActor = new DragActor(this, titleActor);
+
+ const [, natWidth] = this.get_preferred_width(-1);
+ const targetWidth = Math.min(natWidth / 2, this.width);
+ dragActor.setTargetWidth(targetWidth);
+
+ return dragActor;
}
getDragActorSource() {
--
2.47.0
From 30857e997ccbe9dddc4d43a929f80e2121246cf6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Tue, 8 Oct 2024 19:25:53 +0200
Subject: [PATCH 22/24] window-list: Handle DND events near the drop target
Even with the previous change, the dragged actor has the tendency
of obscuring the possible drop target. To alleviate this, handle
DND events near drop targets as if they occurred on the target.
---
extensions/window-list/extension.js | 26 ++++++++++++++++++++++++--
1 file changed, 24 insertions(+), 2 deletions(-)
diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js
index d56eb38c..5046d415 100644
--- a/extensions/window-list/extension.js
+++ b/extensions/window-list/extension.js
@@ -25,6 +25,8 @@ const DRAG_FADE_DURATION = 200;
const DRAG_RESIZE_DURATION = 400;
+const DRAG_PROXIMITY_THRESHOLD = 30;
+
const GroupingMode = {
NEVER: 0,
AUTO: 1,
@@ -999,6 +1001,7 @@ class WindowList extends St.Widget {
this._itemDragMonitor = {
dragMotion: this._onItemDragMotion.bind(this),
+ dragDrop: this._onItemDragDrop.bind(this),
};
this._dndTimeoutId = 0;
@@ -1295,11 +1298,30 @@ class WindowList extends St.Widget {
}
_onItemDragMotion(dragEvent) {
- if (!this._windowList.contains(dragEvent.targetActor))
- this._clearDragPlaceholder();
+ const { source, targetActor, dragActor, x, y } = dragEvent;
+
+ const hasTarget = this._windowList.contains(targetActor);
+ const isNear = Math.abs(y - this.y) < DRAG_PROXIMITY_THRESHOLD;
+
+ if (hasTarget || isNear)
+ return this.handleDragOver(source, dragActor, x, y);
+
+ this._clearDragPlaceholder();
return DND.DragMotionResult.CONTINUE;
}
+ _onItemDragDrop(dropEvent) {
+ if (this._dragPlaceholderPos < 0)
+ return DND.DragDropResult.CONTINUE;
+
+ const { source } = dropEvent.dropActor;
+ this.acceptDrop(source);
+ dropEvent.dropActor.destroy();
+ // HACK: SUCESS would make more sense, but results in gnome-shell
+ // skipping all drag-end code
+ return DND.DragDropResult.CONTINUE;
+ }
+
_monitorXdndDrag() {
DND.addDragMonitor(this._xdndDragMonitor);
}
--
2.47.0
From 36bf2c49de46907e3f9a5804fe925a42f4fca806 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Wed, 26 Jun 2024 00:58:18 +0200
Subject: [PATCH 23/24] window-list: Add `id` property to buttons
A string ID that uniquely identifies a button will allow to
serialize/deserialize the positions in the next commit.
---
extensions/window-list/extension.js | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js
index 5046d415..c945d380 100644
--- a/extensions/window-list/extension.js
+++ b/extensions/window-list/extension.js
@@ -608,6 +608,10 @@ class WindowButton extends BaseButton {
this._updateStyle();
}
+ get id() {
+ return `window:${this.metaWindow.get_id()}`;
+ }
+
_createTitleActor() {
return new WindowTitle(this.metaWindow);
}
@@ -748,6 +752,10 @@ class AppButton extends BaseButton {
this._updateStyle();
}
+ get id() {
+ return `app:${this.app.get_id()}`;
+ }
+
_windowEnteredOrLeftMonitor(metaDisplay, monitorIndex, metaWindow) {
if (this._windowTracker.get_window_app(metaWindow) === this.app &&
monitorIndex === this._monitorIndex) {
--
2.47.0
From 501d72e4d9d52e40a97184da5417879d24515173 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Tue, 24 Sep 2024 20:31:06 +0200
Subject: [PATCH 24/24] window-list: Save and restore positions as runtime
state
While it doesn't make sense for window list positions to be truly
persistent like dash items, some persistence is desirable.
Otherwise any manually set position is lost when the extension
is disabled, for example when locking the screen.
To address this, serialize the positions as runtime state on drop,
and restore them when populating the list.
---
extensions/window-list/extension.js | 28 ++++++++++++++++++++++++++++
1 file changed, 28 insertions(+)
diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js
index c945d380..83262e1c 100644
--- a/extensions/window-list/extension.js
+++ b/extensions/window-list/extension.js
@@ -27,6 +27,8 @@ const DRAG_RESIZE_DURATION = 400;
const DRAG_PROXIMITY_THRESHOLD = 30;
+const SAVED_POSITIONS_KEY = 'window-list-positions';
+
const GroupingMode = {
NEVER: 0,
AUTO: 1,
@@ -1146,6 +1148,8 @@ class WindowList extends St.Widget {
for (let i = 0; i < apps.length; i++)
this._addApp(apps[i], false);
}
+
+ this._restorePositions();
}
_updateKeyboardAnchor() {
@@ -1294,9 +1298,33 @@ class WindowList extends St.Widget {
this._clearDragPlaceholder();
+ this._savePositions();
+
return true;
}
+ _getPositionStateKey() {
+ return `${SAVED_POSITIONS_KEY}:${this._monitor.index}`;
+ }
+
+ _savePositions() {
+ const buttons = this._windowList.get_children()
+ .filter(b => b instanceof BaseButton);
+ global.set_runtime_state(this._getPositionStateKey(),
+ new GLib.Variant('as', buttons.map(b => b.id)));
+ }
+
+ _restorePositions() {
+ const positions = global.get_runtime_state('as',
+ this._getPositionStateKey())?.deepUnpack() ?? [];
+
+ for (const button of this._windowList.get_children()) {
+ const pos = positions.indexOf(button.id);
+ if (pos > -1)
+ this._windowList.set_child_at_index(button, pos);
+ }
+ }
+
_monitorItemDrag() {
DND.addDragMonitor(this._itemDragMonitor);
}
--
2.47.0