115 lines
3.6 KiB
QML
115 lines
3.6 KiB
QML
// Volume row: mute button, slider, and a chevron that opens the device picker.
|
|
//
|
|
// PwObjectTracker is mandatory — without it node.audio.volume reads 0 and
|
|
// writes are dropped on the floor with no error.
|
|
|
|
import QtQuick
|
|
import Quickshell
|
|
import Quickshell.Services.Pipewire
|
|
import qs.widgets
|
|
import qs.config
|
|
|
|
Item {
|
|
id: root
|
|
|
|
property var node: null
|
|
// Output vs input, purely for icon selection.
|
|
property bool output: true
|
|
property bool expanded: false
|
|
|
|
signal toggleExpanded
|
|
|
|
implicitHeight: 32
|
|
visible: root.node !== null
|
|
|
|
readonly property real volume: root.node && root.node.audio ? root.node.audio.volume : 0
|
|
readonly property bool muted: root.node && root.node.audio ? root.node.audio.muted : false
|
|
|
|
// Over-amplification extends the output to 150%. The input is never
|
|
// extended: a microphone above 100% is gain on noise, not loudness.
|
|
readonly property real maximum: root.output && Settings.overAmplification ? 1.5 : 1
|
|
|
|
PwObjectTracker {
|
|
objects: root.node ? [root.node] : []
|
|
}
|
|
|
|
function iconName(): string {
|
|
if (root.output) {
|
|
if (root.muted || root.volume <= 0.001)
|
|
return "audio-volume-muted-symbolic";
|
|
if (root.volume < 0.34)
|
|
return "audio-volume-low-symbolic";
|
|
if (root.volume < 0.67)
|
|
return "audio-volume-medium-symbolic";
|
|
return "audio-volume-high-symbolic";
|
|
}
|
|
return root.muted ? "microphone-sensitivity-muted-symbolic" : "audio-input-microphone-symbolic";
|
|
}
|
|
|
|
IconButton {
|
|
id: muteButton
|
|
anchors.left: parent.left
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
size: 30
|
|
iconSize: 17
|
|
icon: root.iconName()
|
|
iconFallback: root.output ? "audio-volume-high-symbolic" : "audio-input-microphone-symbolic"
|
|
onClicked: {
|
|
if (root.node && root.node.audio)
|
|
root.node.audio.muted = !root.node.audio.muted;
|
|
}
|
|
}
|
|
|
|
ValueSlider {
|
|
id: slider
|
|
anchors.left: muteButton.right
|
|
anchors.leftMargin: 6
|
|
anchors.right: chevron.left
|
|
anchors.rightMargin: 4
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
value: root.muted ? 0 : root.volume / root.maximum
|
|
onMoved: v => {
|
|
if (!root.node || !root.node.audio)
|
|
return;
|
|
// Nudging the slider is also how you unmute, same as GNOME.
|
|
root.node.audio.muted = false;
|
|
root.node.audio.volume = v * root.maximum;
|
|
}
|
|
}
|
|
|
|
// Everything above 100%, marked over the track so the loud end of the
|
|
// slider is visibly the loud end rather than more of the same.
|
|
Rectangle {
|
|
anchors.right: slider.right
|
|
anchors.verticalCenter: slider.verticalCenter
|
|
width: root.maximum > 1 ? slider.width * (1 - 1 / root.maximum) : 0
|
|
height: 10
|
|
radius: 2
|
|
visible: root.maximum > 1
|
|
color: Theme.alpha(Theme.warn, 0.30)
|
|
border.width: 0
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.verticalCenter: slider.verticalCenter
|
|
x: slider.x + slider.width / root.maximum - 1
|
|
width: 2
|
|
height: 16
|
|
radius: 1
|
|
visible: root.maximum > 1
|
|
color: Theme.alpha(Theme.warn, 0.7)
|
|
border.width: 0
|
|
}
|
|
|
|
IconButton {
|
|
id: chevron
|
|
anchors.right: parent.right
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
size: 28
|
|
iconSize: 14
|
|
icon: root.expanded ? "pan-up-symbolic" : "pan-down-symbolic"
|
|
iconFallback: "go-next-symbolic"
|
|
onClicked: root.toggleExpanded()
|
|
}
|
|
}
|