Add advanced VolumeOSD to top panel

This commit is contained in:
Devin Lin 2021-06-04 23:40:54 -04:00
parent b0b0e6f237
commit 40419e48bc
10 changed files with 864 additions and 8 deletions

View file

@ -12,6 +12,8 @@ import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 2.0 as PlasmaComponents
import org.kde.plasma.private.volume 0.1
import "../../volumeosd"
QtObject {
property bool isVisible: paSinkModel.preferredSink && paSinkModel.preferredSink.muted
property string icon: paSinkModel.preferredSink && !isDummyOutput(paSinkModel.preferredSink)
@ -23,6 +25,10 @@ QtObject {
property int volumeStep: Math.round(5 * PulseAudio.NormalVolume / 100.0)
readonly property string dummyOutputName: "auto_null"
function showVolumeOverlay() {
osd.showOverlay();
}
function iconName(volume, muted, prefix) {
if (!prefix) {
prefix = "audio-volume";
@ -57,10 +63,10 @@ QtObject {
}
function playFeedback(sinkIndex) {
if(!volumeFeedback){
if (!volumeFeedback){
return;
}
if(sinkIndex == undefined) {
if (sinkIndex == undefined) {
sinkIndex = paSinkModel.preferredSink.index;
}
feedback.play(sinkIndex)
@ -75,7 +81,8 @@ QtObject {
var percent = volumePercent(volume, maxVolumeValue);
paSinkModel.preferredSink.muted = percent == 0;
paSinkModel.preferredSink.volume = volume;
osd.show(percent);
osd.volume = percent;
osd.showOverlay();
playFeedback();
}
@ -89,7 +96,8 @@ QtObject {
var percent = volumePercent(volume, maxVolumeValue);
paSinkModel.preferredSink.muted = percent == 0;
paSinkModel.preferredSink.volume = volume;
osd.show(percent);
osd.volume = percent;
osd.showOverlay();
playFeedback();
}
@ -102,15 +110,37 @@ QtObject {
var toMute = !paSinkModel.preferredSink.muted;
paSinkModel.preferredSink.muted = toMute;
osd.show(toMute ? 0 : volumePercent(paSinkModel.preferredSink.volume, maxVolumeValue));
osd.volume = toMute ? 0 : volumePercent(paSinkModel.preferredSink.volume, maxVolumeValue);
osd.showOverlay();
if (!toMute) {
playFeedback();
}
}
property var updateVolume: Connections {
target: paSinkModel.preferredSink
function onVolumeChanged() {
var percent = volumePercent(paSinkModel.preferredSink.volume, maxVolumeValue);
osd.volume = percent;
}
}
property var updateVolumeOnSinkChange: Connections {
target: paSinkModel
function onPreferredSinkChanged() {
if (paSinkModel.preferredSink) {
var percent = volumePercent(paSinkModel.preferredSink.volume, maxVolumeValue);
osd.volume = percent;
}
}
}
property SinkModel paSinkModel: SinkModel {}
property VolumeOSD osd: VolumeOSD {}
property VolumeOsd osd: VolumeOsd {}
property VolumeFeedback feedback: VolumeFeedback {}

View file

@ -74,6 +74,7 @@ Item {
fullContainer.applet = applet;
fullContainer.contentItem = applet.fullRepresentationItem;
//applet.fullRepresentationItem.anchors.fill = fullContainer;
}
Component.onCompleted: {
@ -233,7 +234,7 @@ Item {
onCloseRequested: slidingPanel.close()
}
// notifications
// notifications and media player
ListView {
id: fullRepresentationView
implicitHeight: units.gridUnit * 20

View file

@ -106,6 +106,10 @@ Item {
root.closeRequested();
}
function openVolumeOsd() {
volumeProvider.showVolumeOverlay();
}
// initialize quick settings
Component.onCompleted: {
//NOTE: add all in javascript as the static decl of listelements can't have scripts
@ -150,7 +154,7 @@ Item {
"icon": "audio-speakers-symbolic",
"enabled": false,
"settingsCommand": "plasma-settings -m kcm_pulseaudio",
"toggleFunction": ""
"toggleFunction": "openVolumeOsd"
});
settingsModel.append({
"text": i18n("Flashlight"),

View file

@ -0,0 +1,203 @@
/*
* SPDX-FileCopyrightText: 2014-2015 Harald Sitter <sitter@kde.org>
* SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.15
import QtQuick.Controls 2.15 as Controls
import QtQuick.Layouts 1.1
import QtQuick.Window 2.2
import QtGraphicalEffects 1.12
import org.kde.plasma.core 2.1 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.extras 2.0 as PlasmaExtra
import org.kde.plasma.plasmoid 2.0
import org.kde.kquickcontrolsaddons 2.0 as KQCAddons
import org.kde.plasma.private.volume 0.1
// adapted version of https://invent.kde.org/plasma/plasma-pa/-/blob/master/applet/contents/ui/main.qml
// most audio functions are in VolumeProvider.qml (which will be a parent)
// capture presses on the audio applet so it doesn't close the overlay
ColumnLayout {
spacing: 0
// pulseaudio models
function isDummyOutput(output) {
return output && output.name === dummyOutputName;
}
SinkModel {
id: paSinkModel
}
PulseObjectFilterModel {
id: paSinkFilterModel
sortRole: "SortByDefault"
sortOrder: Qt.DescendingOrder
filterOutInactiveDevices: true
sourceModel: paSinkModel
}
SourceModel {
id: paSourceModel
}
PulseObjectFilterModel {
id: paSourceFilterModel
sortRole: "SortByDefault"
sortOrder: Qt.DescendingOrder
filterOutInactiveDevices: true
sourceModel: paSourceModel
}
CardModel {
id: paCardModel
}
// ui elements
PopupCard {
Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: PlasmaCore.Units.largeSpacing
contentItem: ColumnLayout {
anchors.rightMargin: PlasmaCore.Units.smallSpacing
anchors.leftMargin: PlasmaCore.Units.smallSpacing
PlasmaExtra.Heading {
level: 2
text: i18n("Outputs")
Layout.fillWidth: true
Layout.leftMargin: PlasmaCore.Units.smallSpacing
}
Repeater {
id: sinkView
Layout.fillWidth: true
model: paSinkFilterModel
delegate: DeviceListItem {
Layout.fillWidth: true
type: "sink"
onlyone: sinkView.count === 1
}
}
}
}
PopupCard {
Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: PlasmaCore.Units.largeSpacing
contentItem: ColumnLayout {
anchors.rightMargin: PlasmaCore.Units.smallSpacing
anchors.leftMargin: PlasmaCore.Units.smallSpacing
PlasmaExtra.Heading {
level: 2
text: i18n("Inputs")
Layout.fillWidth: true
Layout.leftMargin: PlasmaCore.Units.smallSpacing
}
Repeater {
id: sourceView
Layout.fillWidth: true
model: paSourceFilterModel
delegate: DeviceListItem {
Layout.fillWidth: true
type: "source"
onlyone: sinkView.count === 1
}
}
}
}
PopupCard {
visible: sourceInputView.model.count + sourceMediaInputView.model.count !== 0
Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: PlasmaCore.Units.largeSpacing
contentItem: ColumnLayout {
anchors.rightMargin: PlasmaCore.Units.smallSpacing
anchors.leftMargin: PlasmaCore.Units.smallSpacing
PlasmaExtra.Heading {
level: 2
text: i18n("Playback Streams")
Layout.fillWidth: true
Layout.leftMargin: PlasmaCore.Units.smallSpacing
}
Repeater {
id: sourceMediaInputView
Layout.fillWidth: true
model: PulseObjectFilterModel {
filters: [ { role: "Name", value: "sink-input-by-media-role:event" } ]
sourceModel: StreamRestoreModel {}
}
delegate: StreamListItem {
Layout.fillWidth: true
width: sourceOutputView.width
type: "sink-input"
devicesModel: sourceView.model
}
}
Repeater {
id: sourceInputView
Layout.fillWidth: true
model: PulseObjectFilterModel {
filters: [ { role: "VirtualStream", value: false } ]
sourceModel: SinkInputModel {}
}
delegate: StreamListItem {
Layout.fillWidth: true
width: sourceOutputView.width
type: "sink-input"
devicesModel: sourceView.model
}
}
}
}
PopupCard {
visible: sourceOutputView.model.count !== 0
Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: PlasmaCore.Units.largeSpacing
contentItem: ColumnLayout {
anchors.rightMargin: PlasmaCore.Units.smallSpacing
anchors.leftMargin: PlasmaCore.Units.smallSpacing
PlasmaExtra.Heading {
level: 2
text: i18n("Recording Streams")
Layout.fillWidth: true
Layout.leftMargin: PlasmaCore.Units.smallSpacing
}
Repeater {
id: sourceOutputView
Layout.fillWidth: true
model: PulseObjectFilterModel {
filters: [ { role: "VirtualStream", value: false } ]
sourceModel: SourceOutputModel {}
}
delegate: StreamListItem {
Layout.fillWidth: true
width: sourceOutputView.width
type: "source-output"
devicesModel: sourceView.model
}
}
}
}
}

View file

@ -0,0 +1,36 @@
/*
SPDX-FileCopyrightText: 2014-2015 Harald Sitter <sitter@kde.org>
SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
import QtQuick 2.0
import org.kde.plasma.private.volume 0.1
// adapted from https://invent.kde.org/plasma/plasma-pa/-/blob/master/applet/contents/ui/DeviceListItem.qml
ListItemBase {
readonly property var currentPort: Ports[ActivePortIndex]
readonly property var currentActivePortIndex: ActivePortIndex
readonly property var currentMuted: Muted
readonly property var activePortIndex: ActivePortIndex
property bool onlyone: false
label: {
if (currentPort && currentPort.description) {
if (onlyone || !Description) {
return currentPort.description;
} else {
return i18nc("label of device items", "%1 (%2)", currentPort.description, Description);
}
}
if (Description) {
return Description;
}
if (Name) {
return Name;
}
return i18n("Device name not found");
}
}

View file

@ -0,0 +1,271 @@
/*
* SPDX-FileCopyrightText: 2014-2015 Harald Sitter <sitter@kde.org>
* SPDX-FileCopyrightText: 2019 Sefa Eyeoglu <contact@scrumplex.net>
* SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.15
import QtQuick.Controls 2.15 as Controls
import QtQuick.Layouts 1.1
import QtQuick.Window 2.2
import QtGraphicalEffects 1.12
import org.kde.kquickcontrolsaddons 2.0
import org.kde.plasma.core 2.1 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.extras 2.0 as PlasmaExtra
import org.kde.plasma.private.volume 0.1
import org.kde.kirigami 2.12 as Kirigami
import "icon.js" as Icon
// adapted from https://invent.kde.org/plasma/plasma-pa/-/blob/master/applet/contents/ui/ListItemBase.qml
Kirigami.SwipeListItem {
id: baseItem
property string label
property alias listIcon: clientIcon.source
property alias iconUsesPlasmaTheme: clientIcon.usesPlasmaTheme
property string type // sink, source, source-output
topPadding: PlasmaCore.Units.smallSpacing
bottomPadding: PlasmaCore.Units.smallSpacing
leftPadding: PlasmaCore.Units.smallSpacing
rightPadding: PlasmaCore.Units.smallSpacing
alwaysVisibleActions: true
backgroundColor: "transparent" // we use panel background, no need for the same colour to be on top
activeBackgroundColor: selectButton.visible ? PlasmaCore.Theme.highlightColor : "transparent"
onClicked: {
if (selectButton.visible) {
model.PulseObject.default = true;
}
}
contentItem: RowLayout {
id: row
spacing: PlasmaCore.Units.smallSpacing
PlasmaComponents.RadioButton {
id: selectButton
Layout.alignment: Qt.AlignTop
Layout.topMargin: Math.round(row.height / 2 - implicitHeight - PlasmaCore.Units.smallSpacing / 2) // align with text
checked: model.PulseObject.hasOwnProperty("default") ? model.PulseObject.default : false
visible: (baseItem.type == "sink" && sinkView.model.count > 1) || (baseItem.type == "source" && sourceView.model.count > 1)
onClicked: model.PulseObject.default = true
}
// application icon
PlasmaCore.IconItem {
id: clientIcon
Layout.alignment: Qt.AlignVCenter
Layout.rightMargin: PlasmaCore.Units.smallSpacing
Layout.preferredWidth: PlasmaCore.Units.iconSizes.smallMedium
Layout.preferredHeight: PlasmaCore.Units.iconSizes.smallMedium
visible: type === "sink-input" || type === "source-output"
source: "unknown"
onSourceChanged: {
if (!valid && source != "unknown") {
source = "unknown";
}
}
}
ColumnLayout {
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
spacing: PlasmaCore.Units.smallSpacing
RowLayout {
Layout.fillWidth: true
spacing: PlasmaCore.Units.smallSpacing
Layout.alignment: Qt.AlignBottom
PlasmaComponents.Label {
id: mainLabel
text: baseItem.label
Layout.alignment: Qt.AlignBottom
Layout.fillWidth: true
elide: Text.ElideRight
}
PlasmaComponents.ToolButton {
Layout.alignment: Qt.AlignBottom
Layout.bottomMargin: -PlasmaCore.Units.smallSpacing
icon.name: "application-menu"
checkable: true
checked: contextMenu.visible && contextMenu.visualParent === this
visible: contextMenu.hasContent
onClicked: {
contextMenu.visualParent = this;
contextMenu.openRelative();
}
PlasmaComponents.ToolTip {
text: i18n("Show additional options for %1", baseItem.label)
}
ListItemMenu {
id: contextMenu
pulseObject: model.PulseObject
cardModel: paCardModel
itemType: {
switch (baseItem.type) {
case "sink":
return ListItemMenu.Sink;
case "sink-input":
return ListItemMenu.SinkInput;
case "source":
return ListItemMenu.Source;
case "source-output":
return ListItemMenu.SourceOutput;
}
}
sourceModel: {
if (baseItem.type.includes("sink")) {
return sinkView.model;
} else if (baseItem.type.includes("source")) {
return sourceView.model;
}
}
onVisibleChanged: window.suppressActiveClose = visible
}
}
}
RowLayout {
Layout.fillWidth: true
spacing: PlasmaCore.Units.smallSpacing
// this slider was effectively copied from the source (linked at the top of the file)
PlasmaComponents.Slider {
id: slider
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
// Helper properties to allow async slider updates.
// While we are sliding we must not react to value updates
// as otherwise we can easily end up in a loop where value
// changes trigger volume changes trigger value changes.
property int volume: Volume
property bool ignoreValueChange: true
readonly property bool forceRaiseMaxVolume: volume >= PulseAudio.NormalVolume * 1.01
from: PulseAudio.MinimalVolume
to: PulseAudio.NormalVolume
stepSize: to / (to / PulseAudio.NormalVolume * 100.0)
visible: HasVolume
enabled: VolumeWritable
opacity: Muted ? 0.5 : 1
Accessible.name: i18nc("Accessibility data on volume slider", "Adjust volume for %1", baseItem.label)
background: PlasmaCore.FrameSvgItem {
imagePath: "widgets/slider"
prefix: "groove"
width: parent.availableWidth
height: margins.top + margins.bottom
anchors.centerIn: parent
scale: parent.mirrored ? -1 : 1
PlasmaCore.FrameSvgItem {
imagePath: "widgets/slider"
prefix: "groove-highlight"
anchors.left: parent.left
y: (parent.height - height) / 2
width: Math.max(margins.left + margins.right, slider.handle.x * meter.volume)
height: Math.max(margins.top + margins.bottom, parent.height)
opacity: meter.available && (meter.volume > 0 || animation.running)
VolumeMonitor {
id: meter
target: parent.visible ? model.PulseObject : null
}
Behavior on width {
NumberAnimation {
id: animation
duration: PlasmaCore.Units.shortDuration
easing.type: Easing.OutQuad
}
}
}
}
Component.onCompleted: {
ignoreValueChange = false;
}
onVolumeChanged: {
var oldIgnoreValueChange = ignoreValueChange;
ignoreValueChange = true;
value = Volume;
ignoreValueChange = oldIgnoreValueChange;
}
onValueChanged: {
if (!ignoreValueChange) {
Volume = value;
Muted = value == 0;
if (!pressed) {
updateTimer.restart();
}
}
}
onPressedChanged: {
if (!pressed) {
// 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.).
updateTimer.restart();
if (baseItem.type == "sink") {
playFeedback(Index); // goes to providers/VolumeProvider.qml
}
}
}
Timer {
id: updateTimer
interval: 200
onTriggered: slider.value = Volume
}
}
PlasmaComponents.Label {
id: percentText
readonly property real value: model.PulseObject.volume > slider.to ? model.PulseObject.volume : slider.value
readonly property real displayValue: Math.round(value / PulseAudio.NormalVolume * 100.0)
Layout.alignment: Qt.AlignHCenter
Layout.minimumWidth: percentMetrics.advanceWidth
horizontalAlignment: Qt.AlignRight
text: i18nc("volume percentage", "%1%", displayValue)
color: {
if (displayValue <= 100) {
return PlasmaCore.Theme.textColor
} else if (displayValue > 100 && displayValue <= 125) {
return PlasmaCore.Theme.neutralTextColor
} else {
return PlasmaCore.Theme.negativeTextColor
}
}
}
TextMetrics {
id: percentMetrics
font: percentText.font
text: i18nc("only used for sizing, should be widest possible string", "100%")
}
}
}
}
function setVolumeByPercent(targetPercent) {
model.PulseObject.volume = Math.round(PulseAudio.NormalVolume * (targetPercent/100));
}
}

View file

@ -0,0 +1,25 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.15
import QtQuick.Controls 2.15 as Controls
import QtQuick.Layouts 1.1
import QtQuick.Window 2.2
import org.kde.plasma.core 2.1 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
// capture presses on the audio applet so it doesn't close the overlay
Controls.Control {
id: content
implicitWidth: Math.min(PlasmaCore.Units.gridUnit * 20, parent.width - PlasmaCore.Units.largeSpacing * 2)
padding: PlasmaCore.Units.smallSpacing * 2
background: PlasmaCore.FrameSvgItem {
imagePath: "widgets/background"
anchors.margins: -PlasmaCore.Units.smallSpacing * 2
anchors.fill: parent
}
}

View file

@ -0,0 +1,44 @@
/*
SPDX-FileCopyrightText: 2014-2015 Harald Sitter <sitter@kde.org>
SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
import QtQuick 2.0
import org.kde.plasma.private.volume 0.1
// adapted from https://invent.kde.org/plasma/plasma-pa/-/blob/master/applet/contents/ui/StreamListItem.qml
ListItemBase {
id: item
property QtObject devicesModel
readonly property bool isEventStream: Name == "sink-input-by-media-role:event"
label: {
if (isEventStream) {
return i18n("Notification Sounds");
}
if (Client && Client.name) {
return i18nc("label of stream items", "%1: %2", Client.name, Name);
}
if (Name) {
return Name;
}
return i18n("Stream name not found");
}
listIcon: {
if (IconName.length !== 0) {
return IconName
}
if (item.type === "source-output") {
return "audio-input-microphone"
}
return "audio-volume-high"
}
iconUsesPlasmaTheme: false
}

View file

@ -0,0 +1,217 @@
/*
* SPDX-FileCopyrightText: 2014 Martin Klapetek <mklapetek@kde.org>
* SPDX-FileCopyrightText: 2019 Kai Uwe Broulik <kde@broulik.de>
* SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.15
import QtQuick.Controls 2.15 as Controls
import QtQuick.Layouts 1.1
import QtQuick.Window 2.2
import QtGraphicalEffects 1.12
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.extras 2.0 as PlasmaExtra
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.extras 2.0 as PlasmaExtra
import org.kde.plasma.private.nanoshell 2.0 as NanoShell
import org.kde.kirigami 2.12 as Kirigami
// this is loaded and managed by indicators/providers/VolumeProvider.qml
NanoShell.FullScreenOverlay {
id: window
visible: false
color: showFullApplet ? Qt.rgba(0, 0, 0, 0.6) : "transparent"
property bool suppressActiveClose: false // used by context menus opened in the applet to not autoclose the osd
Behavior on color {
ColorAnimation {}
}
property int volume: 0
property bool showFullApplet: false
function showOverlay() {
if (!window.visible) {
window.showFullApplet = false;
window.showMaximized();
hideTimer.restart();
} else if (!window.showFullApplet) { // don't autohide applet when the full applet is showing
hideTimer.restart();
}
}
onActiveChanged: {
if (!active && !suppressActiveClose) {
hideTimer.stop();
hideTimer.triggered();
}
}
Timer {
id: hideTimer
interval: 3000
running: false
onTriggered: {
window.close();
window.showFullApplet = false;
}
}
Flickable {
id: flickable
anchors.fill: parent
contentHeight: cards.implicitHeight
boundsBehavior: window.showFullApplet ? Flickable.DragAndOvershootBounds : Flickable.StopAtBounds
pressDelay: 50
MouseArea {
// capture taps behind cards to close
anchors.left: parent.left
anchors.right: parent.right
width: parent.width
height: Math.max(cards.implicitHeight, window.height)
onReleased: {
hideTimer.stop();
hideTimer.triggered();
}
ColumnLayout {
id: cards
width: parent.width
anchors.left: parent.left
anchors.right: parent.right
spacing: 0
// osd card
PopupCard {
id: osd
Layout.topMargin: PlasmaCore.Units.largeSpacing
Layout.alignment: Qt.AlignHCenter
contentItem: RowLayout {
id: containerLayout
spacing: PlasmaCore.Units.smallSpacing
anchors.leftMargin: PlasmaCore.Units.smallSpacing * 2
anchors.rightMargin: PlasmaCore.Units.smallSpacing
PlasmaComponents.ToolButton {
icon.name: !paSinkModel.preferredSink || paSinkModel.preferredSink.muted ? "audio-volume-muted" : "audio-volume-high"
text: !paSinkModel.preferredSink || paSinkModel.preferredSink.muted ? i18n("Unmute") : i18n("Mute")
display: Controls.AbstractButton.IconOnly
Layout.alignment: Qt.AlignVCenter
Layout.preferredWidth: PlasmaCore.Units.iconSizes.medium
Layout.preferredHeight: PlasmaCore.Units.iconSizes.medium
Layout.rightMargin: PlasmaCore.Units.smallSpacing
onClicked: muteVolume()
}
PlasmaComponents.ProgressBar {
id: volumeSlider
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
Layout.rightMargin: PlasmaCore.Units.smallSpacing * 2
value: window.volume
from: 0
to: 100
Behavior on value { NumberAnimation { duration: PlasmaCore.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
}
PlasmaExtra.Heading {
id: percentageLabel
Layout.preferredWidth: widestLabelSize.width
Layout.alignment: Qt.AlignVCenter
Layout.rightMargin: PlasmaCore.Units.smallSpacing
level: 3
text: i18nc("Percentage value", "%1%", window.volume)
// 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 (volumeSlider.value <= 100) {
return PlasmaCore.Theme.textColor
} else if (volumeSlider.value > 100 && volumeSlider.value <= 125) {
return PlasmaCore.Theme.neutralTextColor
} else {
return PlasmaCore.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: PlasmaCore.Units.iconSizes.medium
Layout.preferredHeight: PlasmaCore.Units.iconSizes.medium
Layout.rightMargin: PlasmaCore.Units.smallSpacing
Behavior on opacity { NumberAnimation { duration: PlasmaCore.Units.shortDuration } }
onClicked: {
let coords = mapToItem(flickable, 0, 0);
NanoShell.StartupFeedback.open("audio-volume-high", i18n("Audio Settings"), coords.x, coords.y, PlasmaCore.Units.iconSizes.medium);
plasmoid.nativeInterface.executeCommand("plasma-settings -m 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: PlasmaCore.Units.iconSizes.medium
Layout.preferredHeight: PlasmaCore.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 {
id: applet
Layout.topMargin: PlasmaCore.Units.largeSpacing
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: cards.width
opacity: window.showFullApplet ? 1 : 0
visible: opacity !== 0
transform: Translate {
y: window.showFullApplet ? 0 : -PlasmaCore.Units.gridUnit
Behavior on y { NumberAnimation {} }
}
Behavior on opacity { NumberAnimation {} }
}
}
}
}
}

View file

@ -0,0 +1,25 @@
/*
SPDX-FileCopyrightText: 2014-2015 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
// from https://invent.kde.org/plasma/plasma-pa/-/blob/master/applet/contents/code/icon.js
function name(volume, muted, prefix) {
if (!prefix) {
prefix = "audio-volume";
}
var icon = null;
var percent = volume / 100;
if (percent <= 0.0 || muted) {
icon = prefix + "-muted";
} else if (percent <= 0.25) {
icon = prefix + "-low";
} else if (percent <= 0.75) {
icon = prefix + "-medium";
} else {
icon = prefix + "-high";
}
return icon;
}