mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-06-11 16:57:43 +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.
232 lines
8.3 KiB
QML
232 lines
8.3 KiB
QML
/*
|
|
* SPDX-FileCopyrightText: 2024 Micah Stanley <stanleymicah@proton.me>
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
import QtQuick
|
|
import QtQuick.Controls as Controls
|
|
import QtQuick.Layouts
|
|
import QtQuick.Window
|
|
|
|
import org.kde.kirigami as Kirigami
|
|
import org.kde.plasma.components 3.0 as PlasmaComponents
|
|
import org.kde.plasma.private.mobileshell as MobileShell
|
|
import org.kde.plasma.private.mobileshell.state as MobileShellState
|
|
|
|
import org.kde.plasma.private.volume
|
|
|
|
import org.kde.layershell 1.0 as LayerShell
|
|
|
|
|
|
Window {
|
|
id: window
|
|
|
|
width: osd.width + 6
|
|
height: cards.implicitHeight + 6 + cards.openOffset
|
|
|
|
onWidthChanged: if (visible) window.updateTouchRegion()
|
|
|
|
visible: false
|
|
|
|
LayerShell.Window.scope: "overlay"
|
|
LayerShell.Window.anchors: LayerShell.Window.AnchorTop
|
|
LayerShell.Window.layer: LayerShell.Window.LayerOverlay
|
|
LayerShell.Window.exclusionZone: -1
|
|
LayerShell.Window.keyboardInteractivity: LayerShell.Window.KeyboardInteractivityNone
|
|
|
|
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
|
Kirigami.Theme.inherit: false
|
|
|
|
color: "transparent"
|
|
readonly property int popupAnimationDuration: Math.round(MobileShell.Motion.duration(MobileShell.Motion.SpatialSlow) * 1.25)
|
|
|
|
function showOverlay() {
|
|
if (cards.state == "closed") {
|
|
hideTimer.stop();
|
|
window.open();
|
|
} else if (!volumeSlider.pressed) {
|
|
hideTimer.restart();
|
|
}
|
|
}
|
|
|
|
function open() {
|
|
// set window input transparency to allow touches to pass through while the opening animation is playing
|
|
ShellUtil.setInputTransparent(window, true);
|
|
|
|
window.visible = true;
|
|
cards.state = "open";
|
|
}
|
|
|
|
function close() {
|
|
cards.state = "closed";
|
|
// set window input transparency to allow touches to pass through while the closing animation is playing
|
|
ShellUtil.setInputTransparent(window, true);
|
|
}
|
|
|
|
function updateTouchRegion() {
|
|
ShellUtil.setInputRegion(window, Qt.rect(0, cards.openOffset, window.width, cards.implicitHeight + 6));
|
|
}
|
|
|
|
Timer {
|
|
id: hideTimer
|
|
interval: 2000
|
|
running: false
|
|
onTriggered: {
|
|
window.close();
|
|
}
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
window.close();
|
|
visible = false;
|
|
}
|
|
|
|
ColumnLayout {
|
|
id: cards
|
|
|
|
// Ensure children get visibility state of window so that they don't update while closed
|
|
visible: window.visible
|
|
|
|
width: parent.width
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
spacing: 0
|
|
|
|
readonly property real closedOffset: -(cards.implicitHeight + Kirigami.Units.smallSpacing)
|
|
readonly property real openOffset: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 3
|
|
property real offset: closedOffset
|
|
|
|
state: "closed"
|
|
|
|
states: [
|
|
State {
|
|
name: "open"
|
|
PropertyChanges {
|
|
target: cards; offset: openOffset
|
|
}
|
|
},
|
|
State {
|
|
name: "closed"
|
|
PropertyChanges {
|
|
target: cards; offset: closedOffset
|
|
}
|
|
}
|
|
]
|
|
|
|
transitions: Transition {
|
|
SequentialAnimation {
|
|
ParallelAnimation {
|
|
MobileShell.MotionNumberAnimation {
|
|
properties: "offset"
|
|
type: cards.state == "open" ? MobileShell.Motion.Emphasized : MobileShell.Motion.EmphasizedAccel
|
|
duration: window.popupAnimationDuration
|
|
}
|
|
}
|
|
ScriptAction {
|
|
script: {
|
|
if (cards.state == "open") {
|
|
hideTimer.restart();
|
|
// set window input transparency to accept touches
|
|
ShellUtil.setInputTransparent(window, false);
|
|
window.updateTouchRegion();
|
|
} else {
|
|
hideTimer.stop();
|
|
window.visible = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
PopupCard {
|
|
id: osd
|
|
Layout.alignment: Qt.AlignHCenter
|
|
implicitWidth: Math.min(Kirigami.Units.gridUnit * 15, Screen.width - Kirigami.Units.gridUnit * 2)
|
|
|
|
transform: [
|
|
Translate {
|
|
y: cards.offset + 1
|
|
}
|
|
]
|
|
|
|
contentItem: RowLayout {
|
|
id: containerLayout
|
|
spacing: Kirigami.Units.smallSpacing
|
|
|
|
anchors.leftMargin: Kirigami.Units.smallSpacing * 2
|
|
anchors.rightMargin: Kirigami.Units.smallSpacing
|
|
|
|
readonly property bool hasSink: PreferredDevice.sink !== null
|
|
property int volumePercent: hasSink ? PreferredDevice.sink.volume / PulseAudio.NormalVolume * 100.0 : 0
|
|
|
|
PlasmaComponents.ToolButton {
|
|
enabled: containerLayout.hasSink
|
|
icon.name: containerLayout.hasSink ? (PreferredDevice.sink.muted ? "audio-volume-muted" : MobileShell.AudioInfo.icon) : "audio-volume-muted"
|
|
text: containerLayout.hasSink && PreferredDevice.sink.muted ? i18n("Unmute") : i18n("Mute")
|
|
display: Controls.AbstractButton.IconOnly
|
|
Layout.alignment: Qt.AlignVCenter
|
|
Layout.preferredWidth: Kirigami.Units.iconSizes.medium
|
|
Layout.preferredHeight: Kirigami.Units.iconSizes.medium
|
|
Layout.rightMargin: Kirigami.Units.smallSpacing
|
|
|
|
onClicked: {
|
|
if (!containerLayout.hasSink) {
|
|
return;
|
|
}
|
|
hideTimer.restart();
|
|
PreferredDevice.sink.muted = !PreferredDevice.sink.muted;
|
|
}
|
|
}
|
|
|
|
VolumeSlider {
|
|
id: volumeSlider
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
Layout.alignment: Qt.AlignVCenter
|
|
Layout.rightMargin: Kirigami.Units.smallSpacing * 2
|
|
|
|
from: PulseAudio.MinimalVolume
|
|
to: PulseAudio.NormalVolume
|
|
stepSize: to / (to / PulseAudio.NormalVolume * 100.0)
|
|
|
|
enabled: containerLayout.hasSink
|
|
volumeObject: containerLayout.hasSink ? PreferredDevice.sink : null
|
|
muted: containerLayout.hasSink ? PreferredDevice.sink.muted : false
|
|
value: containerLayout.hasSink ? PreferredDevice.sink.volume : PulseAudio.MinimalVolume
|
|
|
|
onMoved: {
|
|
if (!containerLayout.hasSink) {
|
|
return;
|
|
}
|
|
PreferredDevice.sink.volume = value;
|
|
PreferredDevice.sink.muted = value === 0;
|
|
}
|
|
onPressedChanged: {
|
|
if (pressed) {
|
|
hideTimer.stop();
|
|
} else {
|
|
// Make sure to sync the volume once the button was
|
|
// released.
|
|
// Otherwise it might be that the slider is at v10
|
|
// whereas PA rejected the volume change and is
|
|
// still at v15 (e.g.).
|
|
value = Qt.binding(() => containerLayout.hasSink ? PreferredDevice.sink.volume : PulseAudio.MinimalVolume);
|
|
hideTimer.restart();
|
|
}
|
|
}
|
|
}
|
|
|
|
PlasmaComponents.ToolButton {
|
|
icon.name: window.showFullApplet ? "arrow-up" : "arrow-down"
|
|
text: i18n("configure audio streams")
|
|
display: Controls.AbstractButton.IconOnly
|
|
Layout.alignment: Qt.AlignVCenter
|
|
Layout.preferredWidth: Kirigami.Units.iconSizes.medium
|
|
Layout.preferredHeight: Kirigami.Units.iconSizes.medium
|
|
onClicked: MobileShellState.ShellDBusClient.showVolumeOSD()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|