From e0c61f53f1dd91541d0e4e6271f9627dd13c088b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Fri, 29 Jul 2022 15:22:37 +0200 Subject: [PATCH] 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: --- .../widgets/_quick-settings.scss | 9 ++ js/ui/quickSettings.js | 118 +++++++++++++++++- 2 files changed, 125 insertions(+), 2 deletions(-) diff --git a/data/theme/gnome-shell-sass/widgets/_quick-settings.scss b/data/theme/gnome-shell-sass/widgets/_quick-settings.scss index 8dc5ff850..05d849514 100644 --- a/data/theme/gnome-shell-sass/widgets/_quick-settings.scss +++ b/data/theme/gnome-shell-sass/widgets/_quick-settings.scss @@ -20,3 +20,12 @@ .quick-toggle-label { font-weight: bold; } .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; + } diff --git a/js/ui/quickSettings.js b/js/ui/quickSettings.js index 0e1b83cef..ae097ca64 100644 --- a/js/ui/quickSettings.js +++ b/js/ui/quickSettings.js @@ -4,8 +4,32 @@ const {Atk, Clutter, Gio, GLib, GObject, Graphene, Pango, St} = imports.gi; const Main = imports.ui.main; const PopupMenu = imports.ui.popupMenu; -const {POPUP_ANIMATION_TIME} = imports.ui.boxpointer; +const {PopupAnimation} = imports.ui.boxpointer; + 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({ Properties: { @@ -17,7 +41,7 @@ var QuickToggle = GObject.registerClass({ GObject.ParamFlags.READWRITE, ''), }, -}, class QuickToggle extends St.Button { +}, class QuickToggle extends QuickSettingsItem { _init(params) { super._init({ 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({ Properties: { 'column-span': GObject.ParamSpec.int(