VolumeOSD: Improve design, and prevent touch events from being taken from outside the osd

Fixes: https://invent.kde.org/plasma/plasma-mobile/-/issues/274

Any feedback on these changes would be much appreciated.

![Screenshot_20241024_093458-1](/uploads/7b4f89ace1a53c559737d1c05d591329/Screenshot_20241024_093458-1.png)
![Screenshot_20241023_070919](/uploads/c7b9a8de7c9bba2de01d734408e02b2b/Screenshot_20241023_070919.png)

Change Log:
- NanoShell FullScreenOverlay was changed to a LayerShellQt Window to keep it on the top layer and to prevent the popup from obsorbing all touch inputs.
- New animations were added to the volume popup.
- User can now change the volume by touching and dragging on the popup
- The mute button on the popup was fixed
- Mute buttons were added next to the volume sliders in the AudioApplet page
- Volume icons now dynamically update to the volume level
- Visual design adjustments
This commit is contained in:
Micah Stanley 2024-10-25 15:52:49 +00:00 committed by Devin Lin
parent 8d1bb7642d
commit 681d1683f5
10 changed files with 534 additions and 150 deletions

View file

@ -94,6 +94,8 @@ find_package(KWin ${PROJECT_DEP_VERSION} REQUIRED COMPONENTS
kwin kwin
) )
find_package(LayerShellQt REQUIRED)
find_package(LibKWorkspace CONFIG REQUIRED) find_package(LibKWorkspace CONFIG REQUIRED)
find_package(Libudev REQUIRED) find_package(Libudev REQUIRED)

View file

@ -52,6 +52,7 @@ target_link_libraries(mobileshellplugin
Plasma::KWaylandClient Plasma::KWaylandClient
KF6::Service KF6::Service
KF6::Package KF6::Package
LayerShellQt::Interface
) )
ecm_finalize_qml_module(mobileshellplugin) ecm_finalize_qml_module(mobileshellplugin)

View file

@ -19,8 +19,14 @@ import org.kde.plasma.private.volume
// capture presses on the audio applet so it doesn't close the overlay // capture presses on the audio applet so it doesn't close the overlay
ColumnLayout { ColumnLayout {
id: audioApplet
spacing: 0 spacing: 0
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
property real scale: 1.0
PulseObjectFilterModel { PulseObjectFilterModel {
id: paSinkFilterModel id: paSinkFilterModel
sortRoleName: "SortByDefault" sortRoleName: "SortByDefault"
@ -50,6 +56,14 @@ ColumnLayout {
PopupCard { PopupCard {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: Kirigami.Units.gridUnit Layout.bottomMargin: Kirigami.Units.gridUnit
transform: Scale {
origin.x: Math.round(implicitWidth / 2)
origin.y: Math.round(height / 2)
xScale: audioApplet.scale
yScale: audioApplet.scale
}
contentItem: ColumnLayout { contentItem: ColumnLayout {
anchors.rightMargin: Kirigami.Units.smallSpacing anchors.rightMargin: Kirigami.Units.smallSpacing
anchors.leftMargin: Kirigami.Units.smallSpacing anchors.leftMargin: Kirigami.Units.smallSpacing
@ -80,6 +94,14 @@ ColumnLayout {
PopupCard { PopupCard {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: Kirigami.Units.gridUnit Layout.bottomMargin: Kirigami.Units.gridUnit
transform: Scale {
origin.x: Math.round(implicitWidth / 2)
origin.y: Math.round(height / 2)
xScale: audioApplet.scale
yScale: audioApplet.scale
}
contentItem: ColumnLayout { contentItem: ColumnLayout {
anchors.rightMargin: Kirigami.Units.smallSpacing anchors.rightMargin: Kirigami.Units.smallSpacing
anchors.leftMargin: Kirigami.Units.smallSpacing anchors.leftMargin: Kirigami.Units.smallSpacing
@ -111,6 +133,14 @@ ColumnLayout {
visible: sourceInputView.model.count + sourceMediaInputView.model.count !== 0 visible: sourceInputView.model.count + sourceMediaInputView.model.count !== 0
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: Kirigami.Units.gridUnit Layout.bottomMargin: Kirigami.Units.gridUnit
transform: Scale {
origin.x: Math.round(implicitWidth / 2)
origin.y: Math.round(height / 2)
xScale: audioApplet.scale
yScale: audioApplet.scale
}
contentItem: ColumnLayout { contentItem: ColumnLayout {
anchors.rightMargin: Kirigami.Units.smallSpacing anchors.rightMargin: Kirigami.Units.smallSpacing
anchors.leftMargin: Kirigami.Units.smallSpacing anchors.leftMargin: Kirigami.Units.smallSpacing
@ -164,6 +194,14 @@ ColumnLayout {
visible: sourceOutputView.model.count !== 0 visible: sourceOutputView.model.count !== 0
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: Kirigami.Units.gridUnit Layout.bottomMargin: Kirigami.Units.gridUnit
transform: Scale {
origin.x: Math.round(implicitWidth / 2)
origin.y: Math.round(height / 2)
xScale: audioApplet.scale
yScale: audioApplet.scale
}
contentItem: ColumnLayout { contentItem: ColumnLayout {
anchors.rightMargin: Kirigami.Units.smallSpacing anchors.rightMargin: Kirigami.Units.smallSpacing
anchors.leftMargin: Kirigami.Units.smallSpacing anchors.leftMargin: Kirigami.Units.smallSpacing

View file

@ -128,6 +128,18 @@ Controls.ItemDelegate {
Layout.fillWidth: true Layout.fillWidth: true
spacing: Kirigami.Units.smallSpacing spacing: Kirigami.Units.smallSpacing
PlasmaComponents.ToolButton {
icon.name: Icon.name(Volume / PulseAudio.NormalVolume * 100.0, Muted)
text: Muted ? i18n("Unmute") : i18n("Mute")
display: Controls.AbstractButton.IconOnly
Layout.alignment: Qt.AlignVCenter
Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium
onClicked: {
Muted = !Muted
}
}
// this slider was effectively copied from the source (linked at the top of the file) // this slider was effectively copied from the source (linked at the top of the file)
PlasmaComponents.Slider { PlasmaComponents.Slider {
id: slider id: slider

View file

@ -1,5 +1,6 @@
/* /*
* SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com> * SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
* SPDX-FileCopyrightText: 2024 Micah Stanley <stanleymicah@proton.me>
* *
* SPDX-License-Identifier: GPL-2.0-or-later * SPDX-License-Identifier: GPL-2.0-or-later
*/ */
@ -9,6 +10,9 @@ import QtQuick.Controls as Controls
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Window import QtQuick.Window
import QtQuick.Effects
import Qt5Compat.GraphicalEffects
import org.kde.kirigami 2.20 as Kirigami import org.kde.kirigami 2.20 as Kirigami
import org.kde.ksvg 1.0 as KSvg import org.kde.ksvg 1.0 as KSvg
import org.kde.plasma.components 3.0 as PlasmaComponents import org.kde.plasma.components 3.0 as PlasmaComponents
@ -16,11 +20,40 @@ import org.kde.plasma.components 3.0 as PlasmaComponents
// capture presses on the audio applet so it doesn't close the overlay // capture presses on the audio applet so it doesn't close the overlay
Controls.Control { Controls.Control {
id: content id: content
implicitWidth: Math.min(Kirigami.Units.gridUnit * 20, parent.width - Kirigami.Units.gridUnit * 2) implicitWidth: Math.min(Kirigami.Units.gridUnit * 20, Screen.width - Kirigami.Units.gridUnit * 2)
padding: Kirigami.Units.smallSpacing * 2 padding: Kirigami.Units.smallSpacing * 2
background: KSvg.FrameSvgItem {
imagePath: "widgets/background" Kirigami.Theme.colorSet: Kirigami.Theme.View
anchors.margins: -Kirigami.Units.smallSpacing * 2 Kirigami.Theme.inherit: false
MultiEffect {
anchors.fill: parent anchors.fill: parent
source: simpleShadow
blurMax: 16
shadowEnabled: true
shadowVerticalOffset: 1
shadowOpacity: 0.85
shadowColor: Qt.lighter(Kirigami.Theme.backgroundColor, 0.2)
}
Rectangle {
id: simpleShadow
anchors.fill: parent
anchors.leftMargin: -1
anchors.rightMargin: -1
anchors.bottomMargin: -1
color: {
let darkerBackgroundColor = Qt.darker(Kirigami.Theme.backgroundColor, 1.3);
return Qt.rgba(darkerBackgroundColor.r, darkerBackgroundColor.g, darkerBackgroundColor.b, 0.5)
}
radius: Kirigami.Units.cornerRadius
}
Rectangle {
anchors.fill: parent
color: Qt.lighter(Kirigami.Theme.backgroundColor, 1.5)
opacity: 0.85
radius: Kirigami.Units.cornerRadius
} }
} }

View file

@ -0,0 +1,270 @@
/*
* 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 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
visible: false
readonly property real offsetMargins: Math.max(cards.offset, 0)
LayerShell.Window.scope: "overlay"
LayerShell.Window.anchors: LayerShell.Window.AnchorTop
LayerShell.Window.layer: LayerShell.Window.LayerOverlay
LayerShell.Window.exclusionZone: -1
LayerShell.Window.margins.top: offsetMargins
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() {
cards.state = "open";
// set window input transparency to accept touches
ShellUtil.setInputTransparent(window, false);
window.visible = true;
}
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);
}
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
property real scale: 0.95
state: "closed"
states: [
State {
name: "open"
PropertyChanges {
target: cards; offset: openOffset
}
PropertyChanges {
target: cards; scale: 1.0
}
},
State {
name: "closed"
PropertyChanges {
target: cards; offset: closedOffset
}
PropertyChanges {
target: cards; scale: 0.95
}
}
]
transitions: Transition {
SequentialAnimation {
ParallelAnimation {
PropertyAnimation {
properties: "offset"; easing.type: Easing.OutExpo; duration: Kirigami.Units.veryLongDuration
}
PropertyAnimation {
properties: "scale"; easing.type: Easing.OutExpo; duration: Kirigami.Units.veryLongDuration
}
}
ScriptAction {
script: {
if (cards.state == "open") {
hideTimer.restart();
} 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 - window.offsetMargins + 1
},
Scale {
origin.x: Math.round(width / 2)
origin.y: Math.round(height / 2)
xScale: cards.scale
yScale: cards.scale
}
]
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()
}
}
}
}
}

View file

@ -13,63 +13,130 @@ import QtQuick.Window
import org.kde.kirigami 2.20 as Kirigami import org.kde.kirigami 2.20 as Kirigami
import org.kde.plasma.components 3.0 as PlasmaComponents import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.private.nanoshell 2.0 as NanoShell
import org.kde.plasma.private.mobileshell as MobileShell import org.kde.plasma.private.mobileshell as MobileShell
import org.kde.plasma.private.mobileshell.state as MobileShellState import org.kde.plasma.private.mobileshell.state as MobileShellState
import org.kde.plasma.private.volume import org.kde.plasma.private.volume
NanoShell.FullScreenOverlay { import org.kde.layershell 1.0 as LayerShell
Window {
id: window id: window
// used by context menus opened in the applet to not autoclose the osd width: Screen.width
property bool suppressActiveClose: false height: Screen.height
// whether the applet is showing all devices
property bool showFullApplet: false
visible: false visible: false
color: showFullApplet ? Qt.rgba(0, 0, 0, 0.6) : "transparent" LayerShell.Window.scope: "overlay"
Behavior on color { LayerShell.Window.anchors: LayerShell.Window.AnchorTop
ColorAnimation {} LayerShell.Window.layer: LayerShell.Window.LayerTop
} LayerShell.Window.exclusionZone: -1
readonly property color backgroundColor: Qt.darker(Qt.rgba(Kirigami.Theme.backgroundColor.r, Kirigami.Theme.backgroundColor.g, Kirigami.Theme.backgroundColor.b, 0.95), 1.05)
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
color: backgroundColor
function showOverlay() { function showOverlay() {
if (!window.visible) { if (!window.visible) {
window.showFullApplet = false; window.open();
window.showFullScreen();
hideTimer.restart();
} else if (!window.showFullApplet) { // don't autohide applet when the full applet is showing
hideTimer.restart();
} }
} }
onActiveChanged: { function open() {
if (!active && !suppressActiveClose) { flickable.state = "open";
hideTimer.stop(); // set window input transparency to accept touches
hideTimer.triggered(); ShellUtil.setInputTransparent(window, false);
} window.visible = true;
} }
Timer { function close() {
id: hideTimer flickable.state = "closed";
interval: 3000 // set window input transparency to allow touches to pass through while the closing animation is playing
running: false ShellUtil.setInputTransparent(window, true);
onTriggered: { }
Component.onCompleted: {
window.close(); window.close();
window.showFullApplet = false; visible = false;
}
} }
Flickable { Flickable {
id: flickable id: flickable
anchors.fill: parent anchors.fill: parent
contentHeight: cards.implicitHeight contentHeight: cards.implicitHeight
boundsBehavior: window.showFullApplet ? Flickable.DragAndOvershootBounds : Flickable.StopAtBounds boundsBehavior: Flickable.DragAndOvershootBounds
pressDelay: 50 pressDelay: 50
property real offset: -Kirigami.Units.gridUnit
property real scale: 0.95
state: "closed"
states: [
State {
name: "open"
PropertyChanges {
target: flickable; offset: 0
}
PropertyChanges {
target: flickable; scale: 1.0
}
PropertyChanges {
target: flickable; opacity: 1.0
}
PropertyChanges {
target: window; color: backgroundColor
}
},
State {
name: "closed"
PropertyChanges {
target: flickable; offset: -Kirigami.Units.gridUnit * 3
}
PropertyChanges {
target: flickable; scale: 0.95
}
PropertyChanges {
target: flickable; opacity: 0.0
}
PropertyChanges {
target: window; color: "transparent"
}
}
]
transitions: Transition {
SequentialAnimation {
ParallelAnimation {
PropertyAnimation {
properties: "offset"; easing.type: Easing.OutExpo; duration: Kirigami.Units.veryLongDuration
}
PropertyAnimation {
properties: "scale"; easing.type: Easing.OutExpo; duration: Kirigami.Units.veryLongDuration
}
PropertyAnimation {
properties: "opacity"; easing.type: Easing.OutExpo; duration: Kirigami.Units.veryLongDuration
}
PropertyAnimation {
properties: "color"; easing.type: Easing.OutExpo; duration: Kirigami.Units.veryLongDuration
}
}
ScriptAction {
script: {
if (flickable.state == "closed") {
window.visible = false;
}
}
}
}
}
MouseArea { MouseArea {
// capture taps behind cards to close // capture taps behind cards to close
anchors.left: parent.left anchors.left: parent.left
@ -77,8 +144,7 @@ NanoShell.FullScreenOverlay {
width: parent.width width: parent.width
height: Math.max(cards.implicitHeight, window.height) height: Math.max(cards.implicitHeight, window.height)
onReleased: { onReleased: {
hideTimer.stop(); window.close();
hideTimer.triggered();
} }
ColumnLayout { ColumnLayout {
@ -88,126 +154,63 @@ NanoShell.FullScreenOverlay {
anchors.right: parent.right anchors.right: parent.right
spacing: 0 spacing: 0
// osd card transform: Translate {
PopupCard { y: flickable.offset
id: osd
Layout.topMargin: Kirigami.Units.gridUnit
Layout.alignment: Qt.AlignHCenter
contentItem: RowLayout {
id: containerLayout
spacing: Kirigami.Units.smallSpacing
anchors.leftMargin: Kirigami.Units.smallSpacing * 2
anchors.rightMargin: Kirigami.Units.smallSpacing
PlasmaComponents.ToolButton {
icon.name: !PreferredDevice.sink || PreferredDevice.sink.muted ? "audio-volume-muted" : "audio-volume-high"
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: muteVolume()
} }
PlasmaComponents.ProgressBar {
id: volumeSlider
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
Layout.rightMargin: Kirigami.Units.smallSpacing * 2
value: MobileShell.AudioInfo.volumeValue
from: 0
to: MobileShell.AudioInfo.maxVolumePercent
Behavior on value { NumberAnimation { duration: Kirigami.Units.shortDuration } }
}
// Get the width of a three-digit number so we can size the label
// to the maximum width to avoid the progress bar resizing itself
TextMetrics {
id: widestLabelSize
text: i18n("100%")
font: percentageLabel.font
}
Kirigami.Heading {
id: percentageLabel
Layout.preferredWidth: widestLabelSize.width
Layout.alignment: Qt.AlignVCenter
Layout.rightMargin: Kirigami.Units.smallSpacing
level: 3
text: i18nc("Percentage value", "%1%", MobileShell.AudioInfo.volumeValue)
// Display a subtle visual indication that the volume might be
// dangerously high
// ------------------------------------------------
// Keep this in sync with the copies in plasma-pa:ListItemBase.qml
// and plasma-pa:VolumeSlider.qml
color: {
if (MobileShell.AudioInfo.volumeValue <= 100) {
return Kirigami.Theme.textColor
} else if (MobileShell.AudioInfo.volumeValue > 100 && MobileShell.AudioInfo.volumeValue <= 125) {
return Kirigami.Theme.neutralTextColor
} else {
return Kirigami.Theme.negativeTextColor
}
}
}
PlasmaComponents.ToolButton {
icon.name: "configure"
text: i18n("Open audio settings")
visible: opacity !== 0
opacity: showFullApplet ? 1 : 0
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
Behavior on opacity { NumberAnimation { duration: Kirigami.Units.shortDuration } }
onClicked: {
let coords = mapToItem(flickable, 0, 0);
MobileShell.ShellUtil.executeCommand("plasma-open-settings kcm_pulseaudio");
}
}
PlasmaComponents.ToolButton {
icon.name: window.showFullApplet ? "arrow-up" : "arrow-down"
text: i18n("Toggle showing audio streams")
display: Controls.AbstractButton.IconOnly
Layout.alignment: Qt.AlignVCenter
Layout.preferredWidth: Kirigami.Units.iconSizes.medium
Layout.preferredHeight: Kirigami.Units.iconSizes.medium
onClicked: {
window.showFullApplet = !window.showFullApplet
// don't autohide applet when full applet is shown
if (window.showFullApplet) {
hideTimer.stop();
} else {
hideTimer.restart();
}
}
}
}
}
// other applet cards
AudioApplet { AudioApplet {
id: applet id: applet
Layout.topMargin: Kirigami.Units.gridUnit Layout.topMargin: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 3
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: cards.width Layout.preferredWidth: cards.width
opacity: window.showFullApplet ? 1 : 0 scale: flickable.scale
visible: opacity !== 0
transform: Translate {
y: window.showFullApplet ? 0 : -Kirigami.Units.gridUnit
Behavior on y { NumberAnimation {} }
} }
Behavior on opacity { NumberAnimation {} } PopupCard {
id: settings
Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: Kirigami.Units.gridUnit
transform: Scale {
origin.x: Math.round(implicitWidth / 2)
origin.y: Math.round(height / 2)
xScale: flickable.scale
yScale: flickable.scale
}
contentItem: RowLayout {
PlasmaComponents.ToolButton {
property int addedPadding: Kirigami.Units.smallSpacing * 2
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
Layout.preferredWidth: parent.width - addedPadding * 2
Layout.preferredHeight: Kirigami.Units.iconSizes.medium
Layout.margins: addedPadding
contentItem: Item {
anchors.fill: parent
RowLayout {
spacing: Kirigami.Units.largeSpacing
anchors.centerIn: parent
Kirigami.Icon {
Layout.alignment: Qt.AlignVCenter
Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium
source: "settings-configure"
}
PlasmaComponents.Label {
text: i18n("Open audio settings")
anchors.verticalCenter: parent.verticalCenter
}
}
}
onClicked: {
MobileShell.ShellUtil.executeCommand("plasma-open-settings kcm_pulseaudio");
window.close();
}
}
}
} }
} }
} }

View file

@ -20,7 +20,9 @@ QtObject {
id: component id: component
function showVolumeOverlay() { function showVolumeOverlay() {
osd.showOverlay(); if (!osd.visible) {
vcp.showOverlay();
}
} }
Component.onCompleted: { Component.onCompleted: {
@ -31,9 +33,11 @@ QtObject {
target: MobileShellState.ShellDBusClient target: MobileShellState.ShellDBusClient
function onShowVolumeOSDRequested() { function onShowVolumeOSDRequested() {
component.showVolumeOverlay(); osd.showOverlay();
vcp.close();
} }
} }
property var osd: VolumeOSD {} property var osd: VolumeOSD {}
property var vcp: VolumeChangedPopup {}
} }

View file

@ -14,6 +14,8 @@
#include <KNotification> #include <KNotification>
#include <KNotificationJobUiDelegate> #include <KNotificationJobUiDelegate>
#include <QQuickWindow>
#include <QDBusPendingReply> #include <QDBusPendingReply>
#include <QDateTime> #include <QDateTime>
#include <QDebug> #include <QDebug>
@ -87,3 +89,16 @@ void ShellUtil::launchApp(const QString &storageId)
job->setUiDelegate(new KNotificationJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled)); job->setUiDelegate(new KNotificationJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled));
job->start(); job->start();
} }
void ShellUtil::setInputTransparent(QQuickWindow *window, bool transparent) {
if (window) {
Qt::WindowFlags flags = window->flags();
if (transparent) {
flags |= Qt::WindowTransparentForInput;
} else {
flags &= ~Qt::WindowTransparentForInput;
}
window->setFlags(flags);
}
}

View file

@ -9,6 +9,7 @@
#include <QObject> #include <QObject>
#include <QQuickItem> #include <QQuickItem>
#include <QQuickWindow>
#include <qqmlregistration.h> #include <qqmlregistration.h>
#include <KConfigWatcher> #include <KConfigWatcher>
@ -65,6 +66,11 @@ public:
*/ */
Q_INVOKABLE bool isSystem24HourFormat(); Q_INVOKABLE bool isSystem24HourFormat();
/**
* Set window input to be transparent.
*/
Q_INVOKABLE void setInputTransparent(QQuickWindow *window, bool transparent);
Q_SIGNALS: Q_SIGNALS:
void isSystem24HourFormatChanged(); void isSystem24HourFormatChanged();