[gnome-shell] quickSettings: Introduce QuickSettingsMenu



commit 47cb354e6c55fb29b1e8466fafb57a6ccc5acb69
Author: Florian Müllner <fmuellner gnome org>
Date:   Sun Jul 24 19:29:50 2022 +0200

    quickSettings: Introduce QuickSettingsMenu
    
    The quick settings menu is a popover that arranges items in a
    reflowing, homogeneous grid. Grid children may span multiple
    columns, but not rows.
    
    For now the QuickSettingsMenu that contains the grid is just a
    convenience wrapper around the layout manager that does the heavy
    lifting. The two will become more intertwined when we add support
    for menu toggles though, so the custom menu type is unfortunately
    needed.
    
    Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2392>

 data/theme/gnome-shell-sass/_widgets.scss          |   1 +
 .../gnome-shell-sass/widgets/_quick-settings.scss  |   8 +
 data/theme/meson.build                             |   1 +
 js/js-resources.gresource.xml                      |   1 +
 js/ui/quickSettings.js                             | 207 +++++++++++++++++++++
 5 files changed, 218 insertions(+)
---
diff --git a/data/theme/gnome-shell-sass/_widgets.scss b/data/theme/gnome-shell-sass/_widgets.scss
index 4ed082f1f9..875baf5827 100644
--- a/data/theme/gnome-shell-sass/_widgets.scss
+++ b/data/theme/gnome-shell-sass/_widgets.scss
@@ -33,6 +33,7 @@
 // Panel
 @import 'widgets/panel';
 @import 'widgets/corner-ripple';
+@import 'widgets/quick-settings';
 // Overview
 @import 'widgets/overview';
 @import 'widgets/window-picker';
diff --git a/data/theme/gnome-shell-sass/widgets/_quick-settings.scss 
b/data/theme/gnome-shell-sass/widgets/_quick-settings.scss
new file mode 100644
index 0000000000..66a0ed3080
--- /dev/null
+++ b/data/theme/gnome-shell-sass/widgets/_quick-settings.scss
@@ -0,0 +1,8 @@
+.quick-settings {
+  padding: 4 * $base_padding;
+}
+
+.quick-settings-grid {
+  spacing-rows: 3 * $base_padding;
+  spacing-columns: 2 * $base_padding;
+}
diff --git a/data/theme/meson.build b/data/theme/meson.build
index 26471b6792..fbf2938b88 100644
--- a/data/theme/meson.build
+++ b/data/theme/meson.build
@@ -29,6 +29,7 @@ theme_sources = files([
   'gnome-shell-sass/widgets/_overview.scss',
   'gnome-shell-sass/widgets/_panel.scss',
   'gnome-shell-sass/widgets/_popovers.scss',
+  'gnome-shell-sass/widgets/_quick-settings.scss',
   'gnome-shell-sass/widgets/_screen-shield.scss',
   'gnome-shell-sass/widgets/_screenshot.scss',
   'gnome-shell-sass/widgets/_scrollbars.scss',
diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml
index 642ad05c3d..87adfd1403 100644
--- a/js/js-resources.gresource.xml
+++ b/js/js-resources.gresource.xml
@@ -93,6 +93,7 @@
     <file>ui/pointerA11yTimeout.js</file>
     <file>ui/pointerWatcher.js</file>
     <file>ui/popupMenu.js</file>
+    <file>ui/quickSettings.js</file>
     <file>ui/remoteSearch.js</file>
     <file>ui/ripples.js</file>
     <file>ui/runDialog.js</file>
diff --git a/js/ui/quickSettings.js b/js/ui/quickSettings.js
new file mode 100644
index 0000000000..82a235850a
--- /dev/null
+++ b/js/ui/quickSettings.js
@@ -0,0 +1,207 @@
+/* exported QuickSettingsMenu */
+const {Clutter, GLib, GObject, St} = imports.gi;
+
+const Main = imports.ui.main;
+const PopupMenu = imports.ui.popupMenu;
+
+const QuickSettingsLayoutMeta = GObject.registerClass({
+    Properties: {
+        'column-span': GObject.ParamSpec.int(
+            'column-span', '', '',
+            GObject.ParamFlags.READWRITE,
+            1, GLib.MAXINT32, 1),
+    },
+}, class QuickSettingsLayoutMeta extends Clutter.LayoutMeta {});
+
+const QuickSettingsLayout = GObject.registerClass({
+    Properties: {
+        'row-spacing': GObject.ParamSpec.int(
+            'row-spacing', 'row-spacing', 'row-spacing',
+            GObject.ParamFlags.READWRITE,
+            0, GLib.MAXINT32, 0),
+        'column-spacing': GObject.ParamSpec.int(
+            'column-spacing', 'column-spacing', 'column-spacing',
+            GObject.ParamFlags.READWRITE,
+            0, GLib.MAXINT32, 0),
+        'n-columns': GObject.ParamSpec.int(
+            'n-columns', 'n-columns', 'n-columns',
+            GObject.ParamFlags.READWRITE,
+            1, GLib.MAXINT32, 1),
+    },
+}, class QuickSettingsLayout extends Clutter.LayoutManager {
+    _containerStyleChanged() {
+        const node = this._container.get_theme_node();
+
+        let changed = false;
+        let found, length;
+        [found, length] = node.lookup_length('spacing-rows', false);
+        changed ||= found;
+        if (found)
+            this.rowSpacing = length;
+
+        [found, length] = node.lookup_length('spacing-columns', false);
+        changed ||= found;
+        if (found)
+            this.columnSpacing = length;
+
+        if (changed)
+            this.layout_changed();
+    }
+
+    _getColSpan(container, child) {
+        const {columnSpan} = this.get_child_meta(container, child);
+        return Math.clamp(columnSpan, 1, this.nColumns);
+    }
+
+    _getMaxChildWidth(container) {
+        let [minWidth, natWidth] = [0, 0];
+
+        for (const child of container) {
+            const [childMin, childNat] = child.get_preferred_width(-1);
+            const colSpan = this._getColSpan(container, child);
+            minWidth = Math.max(minWidth, childMin / colSpan);
+            natWidth = Math.max(natWidth, childNat / colSpan);
+        }
+
+        return [minWidth, natWidth];
+    }
+
+    _getRows(container) {
+        const rows = [];
+        let lineIndex = 0;
+        let curRow;
+
+        /** private */
+        function appendRow() {
+            curRow = [];
+            rows.push(curRow);
+            lineIndex = 0;
+        }
+
+        for (const child of container) {
+            if (!child.visible)
+                continue;
+
+            if (lineIndex === 0)
+                appendRow();
+
+            const colSpan = this._getColSpan(container, child);
+            const fitsRow = lineIndex + colSpan <= this.nColumns;
+
+            if (!fitsRow)
+                appendRow();
+
+            curRow.push(child);
+            lineIndex = (lineIndex + colSpan) % this.nColumns;
+        }
+
+        return rows;
+    }
+
+    _getRowHeight(children) {
+        let [minHeight, natHeight] = [0, 0];
+
+        children.forEach(child => {
+            const [childMin, childNat] = child.get_preferred_height(-1);
+            minHeight = Math.max(minHeight, childMin);
+            natHeight = Math.max(natHeight, childNat);
+        });
+
+        return [minHeight, natHeight];
+    }
+
+    vfunc_get_child_meta_type() {
+        return QuickSettingsLayoutMeta.$gtype;
+    }
+
+    vfunc_set_container(container) {
+        this._container?.disconnectObject(this);
+
+        this._container = container;
+
+        this._container?.connectObject('style-changed',
+            () => this._containerStyleChanged(), this);
+    }
+
+    vfunc_get_preferred_width(container, _forHeight) {
+        const [childMin, childNat] = this._getMaxChildWidth(container);
+        const spacing = (this.nColumns - 1) * this.column_spacing;
+        return [this.nColumns * childMin + spacing, this.nColumns * childNat + spacing];
+    }
+
+    vfunc_get_preferred_height(container, _forWidth) {
+        const rows = this._getRows(container);
+
+        let [minHeight, natHeight] = [0, 0];
+
+        const spacing = (rows.length - 1) * this.row_spacing;
+        minHeight += spacing;
+        natHeight += spacing;
+
+        rows.forEach(row => {
+            const [rowMin, rowNat] = this._getRowHeight(row);
+            minHeight += rowMin;
+            natHeight += rowNat;
+        });
+
+        return [minHeight, natHeight];
+    }
+
+    vfunc_allocate(container, box) {
+        const rows = this._getRows(container);
+
+        const availWidth = box.get_width() - (this.nColumns - 1) * this.row_spacing;
+        const childWidth = availWidth / this.nColumns;
+
+        const isRtl = container.text_direction === Clutter.TextDirection.RTL;
+
+        const childBox = new Clutter.ActorBox();
+        let y = box.y1;
+        rows.forEach(row => {
+            const [, rowNat] = this._getRowHeight(row);
+
+            let lineIndex = 0;
+            row.forEach(child => {
+                const colSpan = this._getColSpan(container, child);
+                const width =
+                    childWidth * colSpan + this.column_spacing * (colSpan - 1);
+                let x = box.x1 + lineIndex * (childWidth + this.column_spacing);
+                if (isRtl)
+                    x = box.x2 - width - x;
+
+                childBox.set_origin(x, y);
+                childBox.set_size(width, rowNat);
+                child.allocate(childBox);
+
+                lineIndex = (lineIndex + colSpan) % this.nColumns;
+            });
+
+            y += rowNat + this.row_spacing;
+        });
+    }
+});
+
+var QuickSettingsMenu = class extends PopupMenu.PopupMenu {
+    constructor(sourceActor, nColumns = 1) {
+        super(sourceActor, 0, St.Side.TOP);
+
+        Main.layoutManager.connectObject('system-modal-opened',
+            () => this.close(), this);
+
+        this.box.add_style_class_name('quick-settings');
+
+        this._grid = new St.Widget({
+            style_class: 'quick-settings-grid',
+            layout_manager: new QuickSettingsLayout({
+                nColumns,
+            }),
+        });
+        this.box.add_child(this._grid);
+    }
+
+    addItem(item, colSpan = 1) {
+        this._grid.add_child(item);
+        this._grid.layout_manager.child_set_property(
+            this._grid, item, 'column-span', colSpan);
+    }
+};


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