From 5b07dfded9842c75dc5b7ef80d5c30f6abd65029 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Wed, 26 Jun 2019 23:55:58 +0000 Subject: [PATCH] window-list: Show previews in workspace switcher Currently the new horizontal workspace switcher only shows a series of buttons, with no indication of the workspaces' contents. Go full GNOME 2 and add tiny draggable preview rectangles that represent the windows on a particular workspace. https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/74 --- extensions/window-list/classic.css | 10 ++ extensions/window-list/stylesheet.css | 10 ++ extensions/window-list/workspaceIndicator.js | 154 ++++++++++++++++++- 3 files changed, 173 insertions(+), 1 deletion(-) diff --git a/extensions/window-list/classic.css b/extensions/window-list/classic.css index c533473e..7079d3ef 100644 --- a/extensions/window-list/classic.css +++ b/extensions/window-list/classic.css @@ -56,3 +56,13 @@ .window-list-workspace-indicator .workspace.active { background-color: #ccc; } + +.window-list-window-preview { + background-color: #ededed; + border: 1px solid #ccc; +} + +.window-list-window-preview.active { + background-color: #f6f5f4; + border: 2px solid #888; +} diff --git a/extensions/window-list/stylesheet.css b/extensions/window-list/stylesheet.css index ad5978a0..79d56bad 100644 --- a/extensions/window-list/stylesheet.css +++ b/extensions/window-list/stylesheet.css @@ -121,6 +121,16 @@ background-color: rgba(200, 200, 200, .3); } +.window-list-window-preview { + background-color: #252525; + border: 1px solid #ccc; +} + +.window-list-window-preview.active { + background-color: #353535; + border: 2px solid #ccc; +} + .notification { font-weight: normal; } diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js index d6695079..84dccacc 100644 --- a/extensions/window-list/workspaceIndicator.js +++ b/extensions/window-list/workspaceIndicator.js @@ -9,16 +9,131 @@ const PopupMenu = imports.ui.popupMenu; const Gettext = imports.gettext.domain('gnome-shell-extensions'); const _ = Gettext.gettext; +let WindowPreview = GObject.registerClass({ + GTypeName: 'WindowListWindowPreview' +}, class WindowPreview extends St.Button { + _init(window) { + super._init({ + style_class: 'window-list-window-preview' + }); + + this._delegate = this; + DND.makeDraggable(this, { restoreOnSuccess: true }); + + this._window = window; + + this.connect('destroy', this._onDestroy.bind(this)); + + this._sizeChangedId = this._window.connect('size-changed', + this._relayout.bind(this)); + this._positionChangedId = this._window.connect('position-changed', + this._relayout.bind(this)); + this._minimizedChangedId = this._window.connect('notify::minimized', + this._relayout.bind(this)); + this._monitorEnteredId = global.display.connect('window-entered-monitor', + this._relayout.bind(this)); + this._monitorLeftId = global.display.connect('window-left-monitor', + this._relayout.bind(this)); + + // Do initial layout when we get a parent + let id = this.connect('parent-set', () => { + this.disconnect(id); + if (!this.get_parent()) + return; + this._laterId = Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => { + this._laterId = 0; + this._relayout(); + return false; + }); + }); + + this._focusChangedId = global.display.connect('notify::focus-window', + this._onFocusChanged.bind(this)); + this._onFocusChanged(); + } + + // needed for DND + get realWindow() { + return this._window.get_compositor_private(); + } + + _onDestroy() { + this._window.disconnect(this._sizeChangedId); + this._window.disconnect(this._positionChangedId); + this._window.disconnect(this._minimizedChangedId); + global.display.disconnect(this._monitorEnteredId); + global.display.disconnect(this._monitorLeftId); + global.display.disconnect(this._focusChangedId); + if (this._laterId) + Meta.later_remove(this._laterId); + } + + _onFocusChanged() { + if (global.display.focus_window == this._window) + this.add_style_class_name('active'); + else + this.remove_style_class_name('active'); + } + + _relayout() { + let monitor = Main.layoutManager.findIndexForActor(this); + this.visible = monitor == this._window.get_monitor() && + this._window.showing_on_its_workspace(); + + if (!this.visible) + return; + + let workArea = Main.layoutManager.getWorkAreaForMonitor(monitor); + let hscale = this.get_parent().allocation.get_width() / workArea.width; + let vscale = this.get_parent().allocation.get_height() / workArea.height; + + let frameRect = this._window.get_frame_rect(); + this.set_size( + Math.round(Math.min(frameRect.width, workArea.width) * hscale), + Math.round(Math.min(frameRect.height, workArea.height) * vscale)); + this.set_position( + Math.round(frameRect.x * hscale), + Math.round(frameRect.y * vscale)); + } +}); + let WorkspaceThumbnail = GObject.registerClass({ GTypeName: 'WindowListWorkspaceThumbnail' }, class WorkspaceThumbnail extends St.Button { _init(index) { super._init({ - style_class: 'workspace' + style_class: 'workspace', + child: new Clutter.Actor({ + layout_manager: new Clutter.BinLayout(), + clip_to_allocation: true + }), + x_fill: true, + y_fill: true }); + this.connect('destroy', this._onDestroy.bind(this)); + this._index = index; this._delegate = this; // needed for DND + + this._windowPreviews = new Map(); + + let workspaceManager = global.workspace_manager; + this._workspace = workspaceManager.get_workspace_by_index(index); + + this._windowAddedId = this._workspace.connect('window-added', + (ws, window) => { + this._addWindow(window); + }); + this._windowRemovedId = this._workspace.connect('window-removed', + (ws, window) => { + this._removeWindow(window); + }); + this._restackedId = global.display.connect('restacked', + this._onRestacked.bind(this)); + + this._workspace.list_windows().forEach(w => this._addWindow(w)); + this._onRestacked(); } acceptDrop(source) { @@ -37,6 +152,37 @@ let WorkspaceThumbnail = GObject.registerClass({ return DND.DragMotionResult.CONTINUE; } + _addWindow(window) { + if (this._windowPreviews.has(window)) + return; + + let preview = new WindowPreview(window); + preview.connect('clicked', (a, btn) => this.emit('clicked', btn)); + this._windowPreviews.set(window, preview); + this.child.add_child(preview); + } + + _removeWindow(window) { + let preview = this._windowPreviews.get(window); + if (!preview) + return; + + this._windowPreviews.delete(window); + preview.destroy(); + } + + _onRestacked() { + let lastPreview = null; + let windows = global.get_window_actors().map(a => a.meta_window); + for (let i = 0; i < windows.length; i++) { + let preview = this._windowPreviews.get(windows[i]); + if (!preview) + continue; + + this.child.set_child_above_sibling(preview, lastPreview); + lastPreview = preview; + } + } _moveWindow(window) { let monitorIndex = Main.layoutManager.findIndexForActor(this); @@ -50,6 +196,12 @@ let WorkspaceThumbnail = GObject.registerClass({ if (ws) ws.activate(global.get_current_time()); } + + _onDestroy() { + this._workspace.disconnect(this._windowAddedId); + this._workspace.disconnect(this._windowRemovedId); + global.display.disconnect(this._restackedId); + } }); var WorkspaceIndicator = GObject.registerClass({