[gnome-shell-extensions/wip/rstrode/heads-up-display: 24/62] window-list: Add window picker button




commit ff53c967fe7fa15858a708804d654f49d4785a6f
Author: Florian Müllner <fmuellner gnome org>
Date:   Tue May 14 19:51:22 2019 +0200

    window-list: Add window picker button
    
    With the latest changes, GNOME Classic has become so classic that it
    is bordering dull. Salvage at least a tiny piece of GNOME 3 in form
    of a window-pick button which toggles an exposé-like reduced overview.
    
    https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/73

 extensions/window-list/classic.css     |  20 ++-
 extensions/window-list/extension.js    |  36 ++++-
 extensions/window-list/meson.build     |   2 +-
 extensions/window-list/stylesheet.css  |  27 +++-
 extensions/window-list/windowPicker.js | 260 +++++++++++++++++++++++++++++++++
 5 files changed, 332 insertions(+), 13 deletions(-)
---
diff --git a/extensions/window-list/classic.css b/extensions/window-list/classic.css
index f3c44a3..c506bea 100644
--- a/extensions/window-list/classic.css
+++ b/extensions/window-list/classic.css
@@ -6,14 +6,13 @@
     height: 2.25em ;
   }
 
-  .bottom-panel .window-button > StWidget {
+  .bottom-panel .window-button > StWidget,
+  .bottom-panel .window-picker-toggle > StWidget {
     background-gradient-drection: vertical;
     background-color: #fff;
     background-gradient-start: #fff;
     background-gradient-end: #eee;
     color: #000;
-    -st-natural-width: 18.7em;
-    max-width: 18.75em;
     color: #2e3436;
     background-color: #eee;
     border-radius: 2px;
@@ -22,7 +21,17 @@
     text-shadow: 0 0 transparent;
   }
 
-  .bottom-panel .window-button:hover > StWidget {
+  .bottom-panel .window-button > StWidget {
+    -st-natural-width: 18.7em;
+    max-width: 18.75em;
+  }
+
+  .bottom-panel .window-picker-toggle > StWidet {
+    border: 1px solid rgba(0,0,0,0.3);
+  }
+
+  .bottom-panel .window-button:hover > StWidget,
+  .bottom-panel .window-picker-toggle:hover > StWidget {
     background-color: #f9f9f9;
   }
 
@@ -31,7 +40,8 @@
     box-shadow: inset 1px 1px 2px rgba(0,0,0,0.5);
   }
 
-  .bottom-panel .window-button.focused > StWidget {
+  .bottom-panel .window-button.focused > StWidget,
+  .bottom-panel .window-picker-toggle:checked > StWidget {
     background-color: #ddd;
     box-shadow: inset 1px 1px 1px rgba(0,0,0,0.5);
   }
diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js
index e1ea742..b2784b4 100644
--- a/extensions/window-list/extension.js
+++ b/extensions/window-list/extension.js
@@ -3,11 +3,14 @@ const { Clutter, Gio, GLib, GObject, Gtk, Meta, Shell, St } = imports.gi;
 
 const DND = imports.ui.dnd;
 const Main = imports.ui.main;
+const Overview = imports.ui.overview;
 const PanelMenu = imports.ui.panelMenu;
 const PopupMenu = imports.ui.popupMenu;
+const Tweener = imports.ui.tweener;
 
 const ExtensionUtils = imports.misc.extensionUtils;
 const Me = ExtensionUtils.getCurrentExtension();
+const { WindowPicker, WindowPickerToggle } = Me.imports.windowPicker;
 
 const Gettext = imports.gettext.domain('gnome-shell-extensions');
 const _ = Gettext.gettext;
@@ -787,6 +790,12 @@ class WindowList {
         let box = new St.BoxLayout({ x_expand: true, y_expand: true });
         this.actor.add_actor(box);
 
+        let toggle = new WindowPickerToggle();
+        box.add_actor(toggle);
+
+        toggle.connect('notify::checked',
+            this._updateWindowListVisibility.bind(this));
+
         let layout = new Clutter.BoxLayout({ homogeneous: true });
         this._windowList = new St.Widget({
             style_class: 'window-list',
@@ -936,6 +945,19 @@ class WindowList {
         this._workspaceIndicator.actor.visible = hasWorkspaces && workspacesOnMonitor;
     }
 
+    _updateWindowListVisibility() {
+        let visible = !Main.windowPicker.visible;
+
+        Tweener.addTween(this._windowList, {
+            opacity: visible ? 255 : 0,
+            transition: 'ease-out-quad',
+            time: Overview.ANIMATION_TIME
+        });
+
+        this._windowList.reactive = visible;
+        this._windowList.get_children().forEach(c => c.reactive = visible);
+    }
+
     _getPreferredUngroupedWindowListWidth() {
         if (this._windowList.get_n_children() == 0)
             return this._windowList.get_preferred_width(-1)[1];
@@ -1206,7 +1228,7 @@ class WindowList {
 class Extension {
     constructor() {
         this._windowLists = null;
-        this._injections = {};
+        this._hideOverviewOrig = Main.overview.hide;
     }
 
     enable() {
@@ -1221,6 +1243,13 @@ class Extension {
             Main.layoutManager.connect('monitors-changed',
                                        this._buildWindowLists.bind(this));
 
+        Main.windowPicker = new WindowPicker();
+
+        Main.overview.hide = () => {
+            Main.windowPicker.close();
+            this._hideOverviewOrig.call(Main.overview);
+        };
+
         this._buildWindowLists();
     }
 
@@ -1251,6 +1280,11 @@ class Extension {
             windowList.actor.destroy();
         });
         this._windowLists = null;
+
+        Main.windowPicker.actor.destroy();
+        delete Main.windowPicker;
+
+        Main.overview.hide = this._hideOverviewOrig;
     }
 
     someWindowListContains(actor) {
diff --git a/extensions/window-list/meson.build b/extensions/window-list/meson.build
index b4aa4db..5b1f5f5 100644
--- a/extensions/window-list/meson.build
+++ b/extensions/window-list/meson.build
@@ -4,7 +4,7 @@ extension_data += configure_file(
   configuration: metadata_conf
 )
 
-extension_sources += files('prefs.js')
+extension_sources += files('prefs.js', 'windowPicker.js')
 extension_schemas += files(metadata_conf.get('gschemaname') + '.gschema.xml')
 
 if classic_mode_enabled
diff --git a/extensions/window-list/stylesheet.css b/extensions/window-list/stylesheet.css
index f5285cb..91383ab 100644
--- a/extensions/window-list/stylesheet.css
+++ b/extensions/window-list/stylesheet.css
@@ -26,9 +26,8 @@
   spacing: 4px;
 }
 
-.window-button > StWidget {
-  -st-natural-width: 18.75em;
-  max-width: 18.75em;
+.window-button > StWidget,
+.window-picker-toggle > StWidget {
   color: #bbb;
   background-color: black;
   border-radius: 4px;
@@ -37,7 +36,21 @@
   text-shadow: 1px 1px 4px rgba(0,0,0,0.8);
 }
 
-.window-button:hover > StWidget {
+.window-picker-toggle {
+  padding: 3px;
+}
+
+.window-picker-toggle > StWidet {
+  border: 1px solid rgba(255,255,255,0.3);
+}
+
+.window-button > StWidget {
+  -st-natural-width: 18.75em;
+  max-width: 18.75em;
+}
+
+.window-button:hover > StWidget,
+.window-picker-toggle:hover > StWidget {
   color: white;
   background-color: #1f1f1f;
 }
@@ -47,12 +60,14 @@
   box-shadow: inset 2px 2px 4px rgba(255,255,255,0.5);
 }
 
-.window-button.focused > StWidget {
+.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:active > StWidget {
+.window-button.focused:active > StWidget,
+.window-picker-toggle:checked:active > StWidget {
   box-shadow: inset 2px 2px 4px rgba(255,255,255,0.7);
 }
 
diff --git a/extensions/window-list/windowPicker.js b/extensions/window-list/windowPicker.js
new file mode 100644
index 0000000..024fd80
--- /dev/null
+++ b/extensions/window-list/windowPicker.js
@@ -0,0 +1,260 @@
+/* exported WindowPicker, WindowPickerToggle */
+const { Clutter, GLib, GObject, Meta, Shell, St } = imports.gi;
+const Signals = imports.signals;
+
+const Layout = imports.ui.layout;
+const Main = imports.ui.main;
+const Overview = imports.ui.overview;
+const { WorkspacesDisplay } = imports.ui.workspacesView;
+
+let MyWorkspacesDisplay = class extends WorkspacesDisplay {
+    constructor() {
+        super();
+
+        this.actor.add_constraint(
+            new Layout.MonitorConstraint({
+                primary: true,
+                work_area: true
+            }));
+
+        this.actor.connect('destroy', this._onDestroy.bind(this));
+
+        this._workareasChangedId = global.display.connect('workareas-changed',
+            this._onWorkAreasChanged.bind(this));
+        this._onWorkAreasChanged();
+    }
+
+    show(...args) {
+        if (this._scrollEventId == 0)
+            this._scrollEventId = Main.windowPicker.connect('scroll-event',
+                this._onScrollEvent.bind(this));
+
+        super.show(...args);
+    }
+
+    hide(...args) {
+        if (this._scrollEventId > 0)
+            Main.windowPicker.disconnect(this._scrollEventId);
+        this._scrollEventId = 0;
+
+        super.hide(...args);
+    }
+
+    _onWorkAreasChanged() {
+        let { primaryIndex } = Main.layoutManager;
+        let workarea = Main.layoutManager.getWorkAreaForMonitor(primaryIndex);
+        this.setWorkspacesFullGeometry(workarea);
+    }
+
+    _updateWorkspacesViews() {
+        super._updateWorkspacesViews();
+
+        this._workspacesViews.forEach(v => {
+            Main.layoutManager.overviewGroup.remove_actor(v.actor);
+            Main.windowPicker.actor.add_actor(v.actor);
+        });
+    }
+
+    _onDestroy() {
+        if (this._workareasChangedId)
+            global.display.disconnect(this._workareasChangedId);
+        this._workareasChangedId = 0;
+    }
+};
+
+var WindowPicker = class {
+    constructor() {
+        this._visible = false;
+        this._modal = false;
+
+        this.actor = new Clutter.Actor();
+
+        this.actor.connect('destroy', this._onDestroy.bind(this));
+
+        global.bind_property('screen-width',
+            this.actor, 'width',
+            GObject.BindingFlags.SYNC_CREATE);
+        global.bind_property('screen-height',
+            this.actor, 'height',
+            GObject.BindingFlags.SYNC_CREATE);
+
+        this._backgroundGroup = new Meta.BackgroundGroup({ reactive: true });
+        this.actor.add_child(this._backgroundGroup);
+
+        this._backgroundGroup.connect('scroll-event', (a, ev) => {
+            this.emit('scroll-event', ev);
+        });
+
+        // Trick WorkspacesDisplay constructor into adding actions here
+        let addActionOrig = Main.overview.addAction;
+        Main.overview.addAction = a => this._backgroundGroup.add_action(a);
+
+        this._workspacesDisplay = new MyWorkspacesDisplay();
+        this.actor.add_child(this._workspacesDisplay.actor);
+
+        Main.overview.addAction = addActionOrig;
+
+        this._bgManagers = [];
+
+        this._monitorsChangedId = Main.layoutManager.connect('monitors-changed',
+            this._updateBackgrounds.bind(this));
+        this._updateBackgrounds();
+
+        Main.uiGroup.insert_child_below(this.actor, global.window_group);
+    }
+
+    get visible() {
+        return this._visible;
+    }
+
+    open() {
+        if (this._visible)
+            return;
+
+        this._visible = true;
+
+        if (!this._syncGrab())
+            return;
+
+        this._fakeOverviewVisible(true);
+        this._shadeBackgrounds();
+        this._fakeOverviewAnimation();
+        this._workspacesDisplay.show(false);
+
+        this.emit('open-state-changed', this._visible);
+    }
+
+    close() {
+        if (!this._visible)
+            return;
+
+        this._visible = false;
+
+        if (!this._syncGrab())
+            return;
+
+        this._workspacesDisplay.animateFromOverview(false);
+        this._unshadeBackgrounds();
+        this._fakeOverviewAnimation(() => {
+            this._workspacesDisplay.hide();
+            this._fakeOverviewVisible(false);
+        });
+
+        this.emit('open-state-changed', this._visible);
+    }
+
+    _fakeOverviewAnimation(onComplete) {
+        Main.overview.animationInProgress = true;
+        GLib.timeout_add(
+            GLib.PRIORITY_DEFAULT,
+            Overview.ANIMATION_TIME * 1000,
+            () => {
+                Main.overview.animationInProgress = false;
+                if (onComplete)
+                    onComplete();
+            });
+    }
+
+    _fakeOverviewVisible(visible) {
+        // Fake overview state for WorkspacesDisplay
+        Main.overview.visible = visible;
+
+        // Hide real windows
+        Main.layoutManager._inOverview = visible;
+        Main.layoutManager._updateVisibility();
+    }
+
+    _syncGrab() {
+        if (this._visible) {
+            if (this._modal)
+                return true;
+
+            this._modal = Main.pushModal(this.actor, {
+                actionMode: Shell.ActionMode.OVERVIEW
+            });
+
+            if (!this._modal) {
+                this.hide();
+                return false;
+            }
+        } else if (this._modal) {
+            Main.popModal(this.actor);
+            this._modal = false;
+        }
+        return true;
+    }
+
+    _onDestroy() {
+        if (this._monitorsChangedId)
+            Main.layoutManager.disconnect(this._monitorsChangedId);
+        this._monitorsChangedId = 0;
+    }
+
+    _updateBackgrounds() {
+        Main.overview._updateBackgrounds.call(this);
+    }
+
+    _shadeBackgrounds() {
+        Main.overview._shadeBackgrounds.call(this);
+    }
+
+    _unshadeBackgrounds() {
+        Main.overview._unshadeBackgrounds.call(this);
+    }
+};
+Signals.addSignalMethods(WindowPicker.prototype);
+
+var WindowPickerToggle = GObject.registerClass(
+class WindowPickerToggle extends St.Button {
+    _init() {
+        let iconBin = new St.Widget({
+            layout_manager: new Clutter.BinLayout()
+        });
+        iconBin.add_child(new St.Icon({
+            icon_name: 'focus-windows-symbolic',
+            icon_size: 16,
+            x_expand: true,
+            y_expand: true,
+            x_align: Clutter.ActorAlign.CENTER,
+            y_align: Clutter.ActorAlign.CENTER
+        }));
+        super._init({
+            style_class: 'window-picker-toggle',
+            child: iconBin,
+            visible: !Main.sessionMode.hasOverview,
+            x_fill: true,
+            y_fill: true,
+            toggle_mode: true
+        });
+
+        this._overlayKeyId = 0;
+
+        this.connect('destroy', this._onDestroy.bind(this));
+
+        this.connect('notify::checked', () => {
+            if (this.checked)
+                Main.windowPicker.open();
+            else
+                Main.windowPicker.close();
+        });
+
+        if (!Main.sessionMode.hasOverview) {
+            this._overlayKeyId = global.display.connect('overlay-key', () => {
+                if (!Main.windowPicker.visible)
+                    Main.windowPicker.open();
+                else
+                    Main.windowPicker.close();
+            });
+        }
+
+        Main.windowPicker.connect('open-state-changed', () => {
+            this.checked = Main.windowPicker.visible;
+        });
+    }
+
+    _onDestroy() {
+        if (this._overlayKeyId)
+            global.display.disconnect(this._overlayKeyId);
+        this._overlayKeyId == 0;
+    }
+});


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