repositories / dotfiles
dotfiles
bugabingas dorkfiles
owned by admin
quickshell/nuguland/AudioPanel.qml
Rawpragma ComponentBehavior: Bound
import QtQuick
import "Util.js" as Util
Flickable {
id: root
required property Theme theme
required property Audio audioService
readonly property var panelState: audioService ? Util.parseJson(audioService.stateJson(), {}) : ({})
readonly property var sinks: panelState.sinks || []
readonly property var sources: panelState.sources || []
readonly property var streams: panelState.streams || []
property string expandedSink: ""
property string expandedSource: ""
function setVolume(id, ratio) {
if (audioService)
audioService.setVolume(id, Math.max(0, Math.min(1.5, ratio)));
}
function toggleMute(id) {
if (audioService)
audioService.toggleMute(id);
}
function selectSink(id) {
if (audioService)
audioService.setDefaultSink(id);
}
function selectSource(id) {
if (audioService)
audioService.setDefaultSource(id);
}
function roleColor(role) {
if (role === "warning")
return theme.warning;
if (role === "error")
return theme.error;
if (role === "ui.minor")
return theme.ui.minor;
return theme.ui.accent;
}
width: parent ? parent.width : 0
contentWidth: width
contentHeight: audioCol.height
clip: true
interactive: contentHeight > height
boundsBehavior: Flickable.StopAtBounds
flickableDirection: Flickable.VerticalFlick
component DeviceSection: Column {
id: section
required property bool input
required property var rows
readonly property string expanded: input ? root.expandedSource : root.expandedSink
function expand(id) {
const next = expanded === id ? "" : id;
if (input)
root.expandedSource = next;
else
root.expandedSink = next;
}
spacing: 6
StableListModel {
id: devices
rows: section.rows
}
NuguText {
theme: root.theme
variant: "title"
text: section.input ? "input" : "output"
}
NuguText {
theme: root.theme
variant: "meta"
visible: devices.count === 0
text: section.input ? "no input devices" : "no output devices"
}
Repeater {
model: devices
delegate: Column {
id: deviceRow
required property var rowData
readonly property bool revealed: section.expanded === rowData.id
width: section.width
spacing: 4
PopupCard {
theme: root.theme
padding: 0
active: deviceRow.rowData.isDefault
revealed: deviceRow.revealed
interactive: true
Accessible.name: `${deviceRow.rowData.name} options`
Accessible.description: deviceRow.rowData.isDefault ? "default device" : ""
tooltip: `${deviceRow.rowData.name}${deviceRow.rowData.isDefault ? "\ndefault device" : ""}\n${deviceRow.revealed ? "hide" : "show"} device options`
onControlFocusedChanged: if (controlFocused)
Util.revealIn(this, root)
onClicked: section.expand(deviceRow.rowData.id)
width: parent.width
height: 48
Text {
id: deviceGlyph
anchors.left: parent.left
anchors.leftMargin: 12
anchors.verticalCenter: parent.verticalCenter
font.family: "Symbols Nerd Font Mono"
font.pixelSize: 18
text: deviceRow.rowData.glyph
color: root.roleColor(deviceRow.rowData.role)
opacity: deviceRow.rowData.muted ? 0.5 : 1
}
NuguText {
theme: root.theme
text: `${deviceRow.rowData.name}${deviceRow.rowData.isDefault ? " · default" : ""}`
anchors.left: deviceGlyph.right
anchors.leftMargin: 10
anchors.right: deviceTrack.left
anchors.rightMargin: 10
anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight
opacity: deviceRow.rowData.muted ? 0.5 : 1
}
NuguText {
id: trailing
anchors.right: parent.right
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
theme: root.theme
variant: "meta"
width: 48
horizontalAlignment: Text.AlignRight
text: deviceRow.rowData.muted ? "muted" : `${deviceRow.rowData.percent}%`
color: root.roleColor(deviceRow.rowData.muted ? "ui.minor" : deviceRow.rowData.role)
}
PopupSlider {
id: deviceTrack
anchors.right: trailing.left
anchors.rightMargin: 10
anchors.verticalCenter: parent.verticalCenter
theme: root.theme
width: 120
Accessible.name: `${deviceRow.rowData.name} ${section.input ? "input" : "output"} volume`
onActiveFocusChanged: if (activeFocus)
Util.revealIn(this, root)
value: deviceRow.rowData.muted ? 0 : deviceRow.rowData.percent / 150
onMoved: value => root.setVolume(deviceRow.rowData.id, value * 1.5)
}
}
Row {
visible: deviceRow.revealed
width: parent.width
height: visible ? 34 : 0
spacing: 8
PopupButton {
theme: root.theme
text: deviceRow.rowData.isDefault ? "default" : "set default"
enabled: !deviceRow.rowData.isDefault
checked: deviceRow.rowData.isDefault
onControlFocusedChanged: if (controlFocused)
Util.revealIn(this, root)
onClicked: {
section.expand(deviceRow.rowData.id);
if (section.input)
root.selectSource(deviceRow.rowData.id);
else
root.selectSink(deviceRow.rowData.id);
}
}
PopupButton {
theme: root.theme
text: deviceRow.rowData.muted ? "unmute" : "mute"
checked: deviceRow.rowData.muted
accessibleName: `${text} ${deviceRow.rowData.name}`
onControlFocusedChanged: if (controlFocused)
Util.revealIn(this, root)
onClicked: root.toggleMute(deviceRow.rowData.id)
}
}
}
}
}
Column {
id: audioCol
width: root.width
spacing: 12
DeviceSection {
objectName: "outputSection"
width: parent.width
input: false
rows: root.sinks
}
DeviceSection {
objectName: "inputSection"
width: parent.width
input: true
rows: root.sources
}
}
}