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

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/338>
This commit is contained in:
Florian Müllner
2024-09-25 04:13:25 +02:00
committed by Marge Bot
parent 911387bc49
commit 3461a0523c
3 changed files with 164 additions and 2 deletions
+147
View File
@@ -26,12 +26,25 @@ import {WorkspaceIndicator} from './workspaceIndicator.js';
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,
ALWAYS: 2,
};
class DragPlaceholderItem extends DashItemContainer {
static {
GObject.registerClass(this);
}
constructor() {
super();
this.setChild(new St.Bin({style_class: 'placeholder'}));
}
}
/**
* @param {Shell.App} app - an app
* @returns {number} - the smallest stable sequence of the app's windows
@@ -220,6 +233,22 @@ class AppTitle extends TitleWidget {
}
}
class DragActor extends St.Bin {
static {
GObject.registerClass(this);
}
constructor(source, titleActor) {
super({
style_class: 'window-button-drag-actor',
child: titleActor,
width: source.width,
});
this.source = source;
}
}
class BaseButton extends DashItemContainer {
static {
GObject.registerClass({
@@ -230,6 +259,10 @@ class BaseButton extends DashItemContainer {
GObject.ParamFlags.READWRITE,
false),
},
Signals: {
'drag-begin': {},
'drag-end': {},
},
}, this);
}
@@ -274,6 +307,15 @@ class BaseButton extends DashItemContainer {
this._windowEnteredOrLeftMonitor.bind(this),
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() {
@@ -357,6 +399,17 @@ class BaseButton extends DashItemContainer {
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}`);
@@ -874,9 +927,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 = settings;
this._settings.connectObject('changed::grouping-mode',
() => this._groupingModeChanged(), this);
@@ -1009,6 +1072,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);
}
@@ -1059,6 +1130,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] ?? null;
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);
}
+12 -2
View File
@@ -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;
}
@@ -56,3 +56,8 @@
color: #aaa;
background-color: #f9f9f9;
}
.window-button-drag-actor {
background-color: #ddd;
border-color: #888;
}