/* * SPDX-FileCopyrightText: 2024 Micah Stanley * * 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 2.20 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" function showOverlay() { if (cards.state == "closed") { hideTimer.stop(); window.open(); } else if (!volumeSlider.isPressed) { 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 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 { PropertyAnimation { properties: "offset"; easing.type: cards.state == "open" ? Easing.OutQuint : Easing.InQuint; duration: Kirigami.Units.veryLongDuration * 1.25 } } 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) popupBackground: true 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 property int volumePercent: PreferredDevice.sink.volume / PulseAudio.NormalVolume * 100.0 PlasmaComponents.ToolButton { icon.name: !PreferredDevice.sink || PreferredDevice.sink.muted ? "audio-volume-muted" : MobileShell.AudioInfo.icon text: !PreferredDevice.sink || 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: { hideTimer.restart(); PreferredDevice.sink.muted = !PreferredDevice.sink.muted; } } PlasmaComponents.Slider { id: volumeSlider Layout.fillWidth: true Layout.fillHeight: true Layout.alignment: Qt.AlignVCenter Layout.rightMargin: Kirigami.Units.smallSpacing * 2 property real volume: PreferredDevice.sink.volume property bool muted: PreferredDevice.sink.muted property bool ignoreValueChange: false property bool isPressed: false from: PulseAudio.MinimalVolume to: PulseAudio.NormalVolume stepSize: to / (to / PulseAudio.NormalVolume * 100.0) opacity: muted ? 0.5 : 1.0 Component.onCompleted: { ignoreValueChange = false; } onVolumeChanged: { if (!window.visible) { return; } var oldIgnoreValueChange = ignoreValueChange; ignoreValueChange = true; value = muted ? 0 : PreferredDevice.sink.volume; ignoreValueChange = oldIgnoreValueChange; if (volumeSlider.isPressed) { return; } window.open(); hideTimer.restart(); } onMutedChanged: { var oldIgnoreValueChange = ignoreValueChange; ignoreValueChange = true; value = muted ? 0 : PreferredDevice.sink.volume; ignoreValueChange = oldIgnoreValueChange; if (!window.visible || volumeSlider.isPressed) { return; } window.open(); hideTimer.restart(); } onValueChanged: { if (!ignoreValueChange) { PreferredDevice.sink.muted = false; PreferredDevice.sink.volume = value; if (!volumeSlider.isPressed) { updateTimer.restart(); } } } onPressedChanged: { volumeSlider.isPressed = pressed; if (pressed) { window.open(); 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.). hideTimer.restart(); updateTimer.restart(); } } Timer { id: updateTimer interval: 200 onTriggered: volumeSlider.value = PreferredDevice.sink.volume } } 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() } } } } }