[gnome-shell-extensions/wip/jimmac/duller-classic-mode: 18/19] workspace-indicator: Show previews in workspace switcher



commit 73a8430edeb340815d92f99f2c956102495e9210
Author: Florian Müllner <fmuellner gnome org>
Date:   Fri Jun 28 11:33:16 2019 +0200

    workspace-indicator: 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/77

 extensions/workspace-indicator/extension.js   | 187 +++++++++++++++++++++++++-
 extensions/workspace-indicator/stylesheet.css |  22 ++-
 2 files changed, 206 insertions(+), 3 deletions(-)
---
diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js
index 346cf4b..8927f8a 100644
--- a/extensions/workspace-indicator/extension.js
+++ b/extensions/workspace-indicator/extension.js
@@ -3,6 +3,7 @@
 
 const { Clutter, Gio, GObject, Meta, St } = imports.gi;
 
+const DND = imports.ui.dnd;
 const ExtensionUtils = imports.misc.extensionUtils;
 const Main = imports.ui.main;
 const PanelMenu = imports.ui.panelMenu;
@@ -14,15 +15,186 @@ const _ = Gettext.gettext;
 const WORKSPACE_SCHEMA = 'org.gnome.desktop.wm.preferences';
 const WORKSPACE_KEY = 'workspace-names';
 
+let WindowPreview = GObject.registerClass({
+    GTypeName: 'WorkspaceIndicatorWindowPreview'
+}, class WindowPreview extends St.Button {
+    _init(window) {
+        super._init({
+            style_class: 'workspace-indicator-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: 'WorkspaceIndicatorWorkspaceThumbnail'
 }, class WorkspaceThumbnail extends St.Button {
     _init(index) {
         super._init({
             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) {
+        if (!source.realWindow)
+            return false;
+
+        let window = source.realWindow.get_meta_window();
+        this._moveWindow(window);
+        return true;
+    }
+
+    handleDragOver(source) {
+        if (source.realWindow)
+            return DND.DragMotionResult.MOVE_DROP;
+        else
+            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);
+        if (monitorIndex != window.get_monitor())
+            window.move_to_monitor(monitorIndex);
+        window.change_workspace_by_index(this._index, false);
     }
 
     on_clicked() {
@@ -30,8 +202,13 @@ 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);
+    }
+});
 
 let WorkspaceIndicator = GObject.registerClass(
 class WorkspaceIndicator extends PanelMenu.Button {
@@ -98,6 +275,8 @@ class WorkspaceIndicator extends PanelMenu.Button {
             this._settingsChangedId = 0;
         }
 
+        Main.panel.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS);
+
         super._onDestroy();
     }
 
@@ -107,6 +286,12 @@ class WorkspaceIndicator extends PanelMenu.Button {
 
         this._statusLabel.visible = vertical;
         this._thumbnailsBox.visible = !vertical;
+
+        // Disable offscreen-redirect when showing the workspace switcher
+        // so that clip-to-allocation works
+        Main.panel.set_offscreen_redirect(vertical
+            ? Clutter.OffscreenRedirect.ALWAYS
+            : Clutter.OffscreenRedirect.AUTOMATIC_FOR_OPACITY);
     }
 
     _onWorkspaceSwitched() {
diff --git a/extensions/workspace-indicator/stylesheet.css b/extensions/workspace-indicator/stylesheet.css
index a15081e..8c101e7 100644
--- a/extensions/workspace-indicator/stylesheet.css
+++ b/extensions/workspace-indicator/stylesheet.css
@@ -1,9 +1,17 @@
-.panel-workspace-indicator,
-.panel-workspace-indicator-box .workspace {
+.panel-workspace-indicator {
        padding: 0 8px;
        border: 1px solid #cccccc;
 }
 
+.panel-workspace-indicator-box {
+    padding: 2px 0;
+}
+
+.panel-workspace-indicator-box .workspace {
+    border: 1px solid #cccccc;
+    width: 48px;
+}
+
 .panel-workspace-indicator,
 .panel-workspace-indicator-box .workspace.active {
        background-color: rgba(200, 200, 200, .5);
@@ -17,3 +25,13 @@
 .panel-workspace-indicator-box .workspace:first-child {
     border-left-width: 1px;
 }
+
+.workspace-indicator-window-preview {
+    background-color: #252525;
+    border: 1px solid #ccc;
+}
+
+.workspace-indicator-window-preview {
+    background-color: #353535;
+    border: 2px solid #ccc;
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]