mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-07-26 06:14:45 +00:00
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.   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:
parent
8d1bb7642d
commit
681d1683f5
10 changed files with 534 additions and 150 deletions
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
270
components/mobileshell/qml/volumeosd/VolumeChangedPopup.qml
Normal file
270
components/mobileshell/qml/volumeosd/VolumeChangedPopup.qml
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue