mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-06-11 08:57:21 +00:00
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.
248 lines
9.7 KiB
QML
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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|