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
This commit is contained in:
Florian Müllner
2019-05-14 19:51:22 +02:00
parent 82d2011061
commit 92db87f7cb
5 changed files with 332 additions and 13 deletions

View File

@@ -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);
}

View File

@@ -4,10 +4,13 @@ const { Clutter, Gio, GLib, GObject, Gtk, Meta, Shell, St } = imports.gi;
const DND = imports.ui.dnd;
const ExtensionUtils = imports.misc.extensionUtils;
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 Me = ExtensionUtils.getCurrentExtension();
const { WindowPicker, WindowPickerToggle } = Me.imports.windowPicker;
const Gettext = imports.gettext.domain('gnome-shell-extensions');
const _ = Gettext.gettext;
@@ -776,6 +779,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',
@@ -920,6 +929,19 @@ class WindowList {
this._workspaceIndicator.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];
@@ -1187,7 +1209,7 @@ class WindowList {
class Extension {
constructor() {
this._windowLists = null;
this._injections = {};
this._hideOverviewOrig = Main.overview.hide;
}
enable() {
@@ -1200,6 +1222,13 @@ class Extension {
this._monitorsChangedId = 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();
}
@@ -1230,6 +1259,11 @@ class Extension {
windowList.actor.destroy();
});
this._windowLists = null;
Main.windowPicker.actor.destroy();
delete Main.windowPicker;
Main.overview.hide = this._hideOverviewOrig;
}
someWindowListContains(actor) {

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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;
}
});