shift-shell/components/mobileshell/qml/actiondrawer/private/QuickSettingsStatusRow.qml
Marco Allegretti dd3e366e17 Harden quick settings and volume OSD models
Clamp quick settings page math to valid bounds and guard volume OSD\ncontrols when PulseAudio objects are absent. Remove unused delegate\nrequired properties tied to enabled state.
2026-05-31 14:06:23 +02:00

248 lines
9.7 KiB
QML

// 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
/**
* Management/detail row 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 var toggleFunction
property bool compact: false
signal detailClicked()
implicitHeight: Kirigami.Units.gridUnit * (compact ? 3.1 : 3.6)
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Button
readonly property int rowRadius: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
readonly property color enabledBg: mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.25)
readonly property color enabledBgHover: mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.32)
readonly property color enabledBgPressed: mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.12)
readonly property color enabledBorder: Qt.darker(Kirigami.Theme.highlightColor, 1.25)
readonly property bool hasToggle: toggleFunction !== null && toggleFunction !== undefined
readonly property color disabledBg: Kirigami.Theme.alternateBackgroundColor
readonly property color disabledBgHover: mixColor(Kirigami.Theme.alternateBackgroundColor, Kirigami.Theme.textColor, 0.06)
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);
}
}
readonly property int shortAnimationDuration: MobileShell.Motion.duration(MobileShell.Motion.EffectsFast)
readonly property int pressAnimationDuration: MobileShell.Motion.duration(MobileShell.Motion.Press)
readonly property real pressedScale: MobileShell.Motion.pressScaleIn
function mixColor(base, overlay, ratio) {
return Qt.rgba(
base.r + (overlay.r - base.r) * ratio,
base.g + (overlay.g - base.g) * ratio,
base.b + (overlay.b - base.b) * ratio,
base.a + (overlay.a - base.a) * ratio)
}
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: root.rowRadius
color: Qt.rgba(0, 0, 0, root.enabled ? 0.12 : 0.08)
}
// Card background — always neutral base (the toggle pill carries the
// enabled highlight, not the whole row).
Rectangle {
id: cardBg
anchors.fill: parent
radius: root.rowRadius
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) {
if (toggleMouse.pressed) {
return root.enabledBgPressed;
}
return toggleMouse.containsMouse ? root.enabledBgHover : root.enabledBg;
}
if (toggleMouse.pressed) {
return root.disabledBgPressed;
}
return toggleMouse.containsMouse ? root.disabledBgHover : root.disabledBg;
}
Behavior on color {
MobileShell.MotionColorAnimation { type: MobileShell.Motion.EffectsFast }
}
}
// Scale on press
property real zoomScale: toggleMouse.pressed ? root.pressedScale : 1
Behavior on zoomScale {
MobileShell.MotionNumberAnimation { type: MobileShell.Motion.Press }
}
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
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Window
isMask: true
color: Kirigami.Theme.textColor
}
// Indicator bar
Rectangle {
Layout.alignment: Qt.AlignHCenter
visible: root.hasToggle
Layout.preferredHeight: visible ? Math.max(2, Math.round(Kirigami.Units.devicePixelRatio)) : 0
width: root.enabled ? Kirigami.Units.smallSpacing * 3 : Kirigami.Units.smallSpacing * 1.5
height: Layout.preferredHeight
radius: height / 2
color: root.enabled ? Kirigami.Theme.highlightColor : Kirigami.Theme.disabledTextColor
opacity: root.enabled ? 1.0 : 0.4
Behavior on width {
MobileShell.MotionNumberAnimation { type: MobileShell.Motion.EffectsFast }
}
}
}
MouseArea {
id: toggleMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: root.hasToggle ? Qt.PointingHandCursor : Qt.ArrowCursor
onPressed: {
if (root.hasToggle) {
haptics.buttonVibrate()
}
}
onClicked: {
if (root.hasToggle) root.toggleFunction();
}
}
}
// ── Detail area (right zone) ────────────────────────────────
Item {
Layout.fillWidth: true
Layout.fillHeight: true
MobileShell.MotionStateLayer {
anchors.fill: parent
radius: Kirigami.Units.cornerRadius
hovered: detailMouse.containsMouse
pressed: detailMouse.pressed
hoverOpacity: 0.03
pressedOpacity: 0.06
}
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"
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Window
isMask: true
color: Kirigami.Theme.textColor
opacity: 0.5
}
}
}
}
}