quickSettings: Add menu support to items

Now that the QuickSettingsMenu supports child menus, it is time
to add menu support to quick toggles.

Do that by introducing a QuickSettingsItem parent class with a
construct-only :has-menu property, as that will allow including
menus in items that aren't following the standard icon+label
pattern of QuickToggle (yes, we'll have some of those).

A common parent class also allows us to control the type of
menu that is created. That's important, as we need another
custom menu type to

- constrain the menu's y position to place it underneath
  the source actor

- use open/close animations taylored for this use case

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2393>
This commit is contained in:
Florian Müllner 2022-07-29 15:22:37 +02:00 committed by Marge Bot
parent 24e842c52e
commit e0c61f53f1
2 changed files with 125 additions and 2 deletions

View File

@ -20,3 +20,12 @@
.quick-toggle-label { font-weight: bold; } .quick-toggle-label { font-weight: bold; }
.quick-toggle-icon { icon-size: $base_icon_size; } .quick-toggle-icon { icon-size: $base_icon_size; }
} }
.quick-toggle-menu {
@include card();
padding: 1.5 * $base_padding;
}
.quick-toggle-menu-container {
padding: 2 * $base_padding $base_padding 0;
}

View File

@ -4,8 +4,32 @@ const {Atk, Clutter, Gio, GLib, GObject, Graphene, Pango, St} = imports.gi;
const Main = imports.ui.main; const Main = imports.ui.main;
const PopupMenu = imports.ui.popupMenu; const PopupMenu = imports.ui.popupMenu;
const {POPUP_ANIMATION_TIME} = imports.ui.boxpointer; const {PopupAnimation} = imports.ui.boxpointer;
const DIM_BRIGHTNESS = -0.4; const DIM_BRIGHTNESS = -0.4;
const POPUP_ANIMATION_TIME = 400;
var QuickSettingsItem = GObject.registerClass({
Properties: {
'has-menu': GObject.ParamSpec.boolean(
'has-menu', 'has-menu', 'has-menu',
GObject.ParamFlags.READWRITE |
GObject.ParamFlags.CONSTRUCT_ONLY,
false),
},
}, class QuickSettingsItem extends St.Button {
_init(params) {
super._init(params);
if (this.hasMenu) {
this.menu = new QuickToggleMenu(this);
this.menu.actor.hide();
this._menuManager = new PopupMenu.PopupMenuManager(this);
this._menuManager.addMenu(this.menu);
}
}
});
var QuickToggle = GObject.registerClass({ var QuickToggle = GObject.registerClass({
Properties: { Properties: {
@ -17,7 +41,7 @@ var QuickToggle = GObject.registerClass({
GObject.ParamFlags.READWRITE, GObject.ParamFlags.READWRITE,
''), ''),
}, },
}, class QuickToggle extends St.Button { }, class QuickToggle extends QuickSettingsItem {
_init(params) { _init(params) {
super._init({ super._init({
style_class: 'quick-toggle button', style_class: 'quick-toggle button',
@ -70,6 +94,96 @@ var QuickToggle = GObject.registerClass({
} }
}); });
class QuickToggleMenu extends PopupMenu.PopupMenuBase {
constructor(sourceActor) {
super(sourceActor, 'quick-toggle-menu');
const constraints = new Clutter.BindConstraint({
coordinate: Clutter.BindCoordinate.Y,
source: sourceActor,
});
sourceActor.bind_property('height',
constraints, 'offset',
GObject.BindingFlags.DEFAULT);
this.actor = new St.Widget({
layout_manager: new Clutter.BinLayout(),
style_class: 'quick-toggle-menu-container',
reactive: true,
x_expand: true,
y_expand: false,
constraints,
});
this.actor._delegate = this;
this.actor.add_child(this.box);
global.focus_manager.add_group(this.actor);
}
open(animate) {
if (this.isOpen)
return;
this.actor.show();
this.isOpen = true;
this.actor.height = -1;
const [targetHeight] = this.actor.get_preferred_height(-1);
const duration = animate !== PopupAnimation.NONE
? POPUP_ANIMATION_TIME / 2
: 0;
this.actor.height = 0;
this.box.opacity = 0;
this.actor.ease({
duration,
height: targetHeight,
onComplete: () => {
this.box.ease({
duration,
opacity: 255,
});
this.actor.height = -1;
},
});
this.emit('open-state-changed', true);
}
close(animate) {
if (!this.isOpen)
return;
const duration = animate !== PopupAnimation.NONE
? POPUP_ANIMATION_TIME / 2
: 0;
this.box.ease({
duration,
opacity: 0,
onComplete: () => {
this.actor.ease({
duration,
height: 0,
onComplete: () => {
this.actor.hide();
this.emit('menu-closed');
},
});
},
});
this.isOpen = false;
this.emit('open-state-changed', false);
}
// expected on toplevel menus
_setOpenedSubMenu(submenu) {
this._openedSubMenu?.close(true);
this._openedSubMenu = submenu;
}
}
const QuickSettingsLayoutMeta = GObject.registerClass({ const QuickSettingsLayoutMeta = GObject.registerClass({
Properties: { Properties: {
'column-span': GObject.ParamSpec.int( 'column-span': GObject.ParamSpec.int(