From 2a137d1ac9c65589963a563751d7a2f2258ca18e Mon Sep 17 00:00:00 2001 From: Marco Allegretti Date: Thu, 16 Apr 2026 13:41:24 +0200 Subject: [PATCH] Promote management tiles to status rows in convergence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wi-Fi, Bluetooth, Sound and Battery behave differently from toggle tiles: tapping them should open a detail panel, not flip a switch. On a phone the long-press convention handles this, but with a mouse long-press is unnatural. Pull these four tiles out of the grid when convergence mode is active and show them as full-width rows above the remaining tiles. Each row has two click zones — a toggle pill on the left that still switches the service on/off, and a detail area on the right (name, status, chevron) that opens the Plasma applet popup. The grid hides the duplicates so they only appear once. --- components/mobileshell/CMakeLists.txt | 1 + .../actiondrawer/private/QuickSettings.qml | 44 +++- .../private/QuickSettingsStatusRow.qml | 217 ++++++++++++++++++ 3 files changed, 260 insertions(+), 2 deletions(-) create mode 100644 components/mobileshell/qml/actiondrawer/private/QuickSettingsStatusRow.qml diff --git a/components/mobileshell/CMakeLists.txt b/components/mobileshell/CMakeLists.txt index 7ceba995..151c9464 100644 --- a/components/mobileshell/CMakeLists.txt +++ b/components/mobileshell/CMakeLists.txt @@ -137,6 +137,7 @@ ecm_target_qml_sources(mobileshellplugin SOURCES qml/actiondrawer/private/QuickSettingsDrawer.qml qml/actiondrawer/private/QuickSettingsFullDelegate.qml qml/actiondrawer/private/QuickSettingsMinimizedDelegate.qml + qml/actiondrawer/private/QuickSettingsStatusRow.qml qml/actiondrawer/private/QuickSettingsPanel.qml qml/actiondrawer/private/ContentContainer.qml qml/actiondrawer/private/DetailPopup.qml diff --git a/components/mobileshell/qml/actiondrawer/private/QuickSettings.qml b/components/mobileshell/qml/actiondrawer/private/QuickSettings.qml index 087c3223..42c1a588 100644 --- a/components/mobileshell/qml/actiondrawer/private/QuickSettings.qml +++ b/components/mobileshell/qml/actiondrawer/private/QuickSettings.qml @@ -54,6 +54,16 @@ Item { readonly property int pageSize: rowCount * columnCount readonly property int quickSettingsCount: quickSettingsModel.count + // Management tiles — promoted to full-width status rows in convergence. + readonly property var __managementCommands: ({ + "plasma-open-settings kcm_mobile_wifi": "org.kde.plasma.networkmanagement", + "plasma-open-settings kcm_bluetooth": "org.kde.plasma.bluetooth", + "plasma-open-settings kcm_pulseaudio": "org.kde.plasma.volume", + "plasma-open-settings kcm_mobile_power": "org.kde.plasma.battery", + }) + readonly property bool isConvergence: ShellSettings.Settings.convergenceModeEnabled + function isManagementTile(cmd) { return cmd in __managementCommands; } + readonly property alias brightnessPressedValue: brightnessItem.brightnessPressedValue function resetSwipeView() { @@ -126,6 +136,34 @@ Item { anchors.left: parent.left anchors.right: parent.right + // Management status rows (convergence mode only) + ColumnLayout { + Layout.fillWidth: true + Layout.leftMargin: Kirigami.Units.smallSpacing + Layout.rightMargin: Kirigami.Units.smallSpacing + Layout.bottomMargin: Kirigami.Units.smallSpacing + spacing: Kirigami.Units.smallSpacing + visible: root.isConvergence + + Repeater { + model: root.quickSettingsModel + delegate: QuickSettingsStatusRow { + required property var modelData + Layout.fillWidth: true + visible: root.isManagementTile(modelData.settingsCommand) + text: modelData.text + status: modelData.status + icon: modelData.icon + enabled: modelData.enabled + toggleFunction: modelData.toggle + onDetailClicked: { + let pluginId = root.__managementCommands[modelData.settingsCommand]; + if (pluginId) detailPopup.show(pluginId); + } + } + } + } + // Quick settings view ColumnLayout { Layout.fillWidth: true @@ -159,8 +197,10 @@ Item { delegate: MobileShell.BaseItem { required property var modelData - height: root.rowHeight - width: root.columnWidth + readonly property bool __hidden: root.isConvergence && root.isManagementTile(modelData.settingsCommand) + height: __hidden ? 0 : root.rowHeight + width: __hidden ? 0 : root.columnWidth + visible: !__hidden padding: Kirigami.Units.smallSpacing contentItem: QuickSettingsFullDelegate { diff --git a/components/mobileshell/qml/actiondrawer/private/QuickSettingsStatusRow.qml b/components/mobileshell/qml/actiondrawer/private/QuickSettingsStatusRow.qml new file mode 100644 index 00000000..9fac35eb --- /dev/null +++ b/components/mobileshell/qml/actiondrawer/private/QuickSettingsStatusRow.qml @@ -0,0 +1,217 @@ +// SPDX-FileCopyrightText: 2026 Marco Allegretti +// SPDX-License-Identifier: EUPL-1.2 + +import QtQuick 2.15 +import QtQuick.Layouts 1.1 + +import org.kde.kirigami as Kirigami +import org.kde.plasma.private.mobileshell as MobileShell +import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings + +/** + * Full-width management row (Wi-Fi, Bluetooth, Audio, Battery) shown in + * convergence mode. Two interaction zones: + * - Left toggle pill: icon + indicator dot, tap toggles the service. + * - Right detail area: name + status + chevron, tap opens detail popup. + */ +Item { + id: root + + required property string text + required property string status + required property string icon + required property bool enabled + required property var toggleFunction + + signal detailClicked() + + implicitHeight: Kirigami.Units.gridUnit * 3.6 + + Kirigami.Theme.inherit: false + Kirigami.Theme.colorSet: Kirigami.Theme.Button + + // ── Palette (shared with tile delegates) ──────────────────────────── + readonly property color enabledBg: Kirigami.ColorUtils.tintWithAlpha( + Kirigami.Theme.highlightColor, Kirigami.Theme.backgroundColor, 0.6) + readonly property color enabledBgPressed: Kirigami.ColorUtils.tintWithAlpha( + Kirigami.Theme.highlightColor, Kirigami.Theme.backgroundColor, 0.4) + readonly property color enabledBorder: Qt.darker(Kirigami.Theme.highlightColor, 1.25) + + readonly property color disabledBg: Kirigami.Theme.backgroundColor + readonly property color disabledBgPressed: Qt.darker(disabledBg, 1.1) + readonly property color disabledBorder: { + let bg = Kirigami.Theme.backgroundColor; + let fg = Kirigami.Theme.textColor; + if (Kirigami.ColorUtils.brightnessForColor(bg) === Kirigami.ColorUtils.Light) { + return Kirigami.ColorUtils.linearInterpolation(bg, fg, 0.2); + } else { + return Kirigami.ColorUtils.linearInterpolation(bg, fg, 0.1); + } + } + + MobileShell.HapticsEffect { id: haptics } + + // ── Outer card ────────────────────────────────────────────────────── + // Shadow + Rectangle { + anchors.top: parent.top + anchors.topMargin: 1 + anchors.left: parent.left + anchors.right: parent.right + height: parent.height + radius: Kirigami.Units.cornerRadius + color: Qt.rgba(0, 0, 0, 0.075) + } + + // Card background — always neutral base (the toggle pill carries the + // enabled highlight, not the whole row). + Rectangle { + id: cardBg + anchors.fill: parent + radius: Kirigami.Units.cornerRadius + border.pixelAligned: false + border.width: 1 + border.color: root.disabledBorder + color: root.disabledBg + } + + RowLayout { + anchors.fill: parent + anchors.margins: Kirigami.Units.smallSpacing + spacing: Kirigami.Units.smallSpacing + + // ── Toggle pill (left zone) ───────────────────────────────── + Item { + id: togglePill + Layout.alignment: Qt.AlignVCenter + Layout.preferredWidth: root.height - Kirigami.Units.smallSpacing * 2 + Layout.fillHeight: true + + Rectangle { + id: pillBg + anchors.fill: parent + radius: Kirigami.Units.cornerRadius + border.pixelAligned: false + border.width: 1 + border.color: root.enabled ? root.enabledBorder : root.disabledBorder + color: { + if (root.enabled) { + return toggleMouse.pressed ? root.enabledBgPressed : root.enabledBg; + } + return toggleMouse.pressed ? root.disabledBgPressed : root.disabledBg; + } + + Behavior on color { + ColorAnimation { duration: Kirigami.Units.shortDuration } + } + } + + // Scale on press + property real zoomScale: (ShellSettings.Settings.animationsEnabled && toggleMouse.pressed) ? 0.9 : 1 + Behavior on zoomScale { + NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.OutExpo } + } + transform: Scale { + origin.x: togglePill.width / 2 + origin.y: togglePill.height / 2 + xScale: togglePill.zoomScale + yScale: togglePill.zoomScale + } + + ColumnLayout { + anchors.centerIn: parent + spacing: Kirigami.Units.smallSpacing + + Kirigami.Icon { + Layout.alignment: Qt.AlignHCenter + implicitWidth: Kirigami.Units.iconSizes.smallMedium + implicitHeight: implicitWidth + source: root.icon + } + + // Indicator dot + Rectangle { + Layout.alignment: Qt.AlignHCenter + width: Kirigami.Units.smallSpacing * 1.5 + height: width + radius: width / 2 + color: root.enabled ? Kirigami.Theme.highlightColor : Kirigami.Theme.disabledTextColor + opacity: root.enabled ? 1.0 : 0.4 + } + } + + MouseArea { + id: toggleMouse + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onPressed: haptics.buttonVibrate() + onClicked: { + if (root.toggleFunction) root.toggleFunction(); + } + } + } + + // ── Detail area (right zone) ──────────────────────────────── + Item { + Layout.fillWidth: true + Layout.fillHeight: true + + // Hover/press highlight + Rectangle { + anchors.fill: parent + radius: Kirigami.Units.cornerRadius + color: detailMouse.pressed ? Qt.rgba(Kirigami.Theme.textColor.r, + Kirigami.Theme.textColor.g, + Kirigami.Theme.textColor.b, 0.06) + : detailMouse.containsMouse ? Qt.rgba(Kirigami.Theme.textColor.r, + Kirigami.Theme.textColor.g, + Kirigami.Theme.textColor.b, 0.03) + : "transparent" + } + + MouseArea { + id: detailMouse + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onPressed: haptics.buttonVibrate() + onClicked: root.detailClicked() + } + + RowLayout { + anchors.fill: parent + anchors.leftMargin: Kirigami.Units.smallSpacing * 2 + anchors.rightMargin: Kirigami.Units.smallSpacing * 2 + spacing: Kirigami.Units.smallSpacing + + ColumnLayout { + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + spacing: 2 + + MobileShell.MarqueeLabel { + Layout.fillWidth: true + inputText: root.text + font.weight: Font.Bold + font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.9 + } + + MobileShell.MarqueeLabel { + Layout.fillWidth: true + inputText: root.status ? root.status : (root.enabled ? i18n("On") : i18n("Off")) + opacity: 0.6 + font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.8 + } + } + + Kirigami.Icon { + Layout.alignment: Qt.AlignVCenter + implicitWidth: Kirigami.Units.iconSizes.small + implicitHeight: implicitWidth + source: "go-next-symbolic" + opacity: 0.5 + } + } + } + } +}