extract panel contents into reusable content/ components

This commit is contained in:
Damocles 2026-04-17 21:44:44 +02:00
parent b27bb533da
commit e939a6b096
15 changed files with 1718 additions and 1547 deletions

View file

@ -2,6 +2,7 @@ import QtQuick
import Quickshell
import Quickshell.Services.Pipewire
import "." as M
import "content" as C
M.BarSection {
id: root
@ -113,279 +114,12 @@ M.BarSection {
panelTitle: "Sound"
contentWidth: 220
// Slider row
Item {
width: parent.width
height: 36
Text {
id: muteIcon
anchors.left: parent.left
anchors.leftMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: root._volumeIcon
color: root._volumeColor
font.pixelSize: M.Theme.fontSize + 2
font.family: M.Theme.iconFontFamily
TapHandler {
cursorShape: Qt.PointingHandCursor
onTapped: if (root.sink?.audio)
root.sink.audio.muted = !root.sink.audio.muted
}
}
Item {
id: slider
anchors.left: muteIcon.right
anchors.leftMargin: 8
anchors.right: volLabel.left
anchors.rightMargin: 8
anchors.verticalCenter: parent.verticalCenter
height: 6
Rectangle {
anchors.fill: parent
color: M.Theme.base02
radius: 3
}
Rectangle {
width: parent.width * Math.min(1, Math.max(0, root.volume))
height: parent.height
color: root._volumeColor
radius: 3
Behavior on width {
NumberAnimation {
duration: 80
}
}
}
MouseArea {
anchors.fill: parent
anchors.margins: -6
cursorShape: Qt.PointingHandCursor
onPressed: mouse => _setVol(mouse)
onPositionChanged: mouse => {
if (pressed)
_setVol(mouse);
}
function _setVol(mouse) {
if (!root.sink?.audio)
return;
root.sink.audio.volume = Math.max(0, Math.min(1, mouse.x / slider.width));
}
}
}
Text {
id: volLabel
anchors.right: parent.right
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: Math.round(root.volume * 100) + "%"
color: root.muted ? M.Theme.base04 : M.Theme.base05
font.pixelSize: M.Theme.fontSize
font.family: M.Theme.fontFamily
width: 30
}
}
// Device + stream list
Column {
id: deviceList
width: parent.width
// Output devices only shown when more than one exists
Column {
visible: root._sinkList.length > 1
width: parent.width
Rectangle {
width: parent.width - 16
height: 1
anchors.horizontalCenter: parent.horizontalCenter
color: M.Theme.base03
}
Text {
width: parent.width
height: 24
verticalAlignment: Text.AlignVCenter
leftPadding: 12
text: "Output Devices"
color: root.accentColor
font.pixelSize: M.Theme.fontSize - 1
font.family: M.Theme.fontFamily
}
Repeater {
model: root._sinkList
delegate: Item {
required property var modelData
width: deviceList.width
height: 28
readonly property bool _active: modelData === root.sink
Rectangle {
anchors.fill: parent
anchors.leftMargin: 4
anchors.rightMargin: 4
color: deviceHover.hovered ? M.Theme.base02 : "transparent"
radius: M.Theme.radius
}
Text {
anchors.left: parent.left
anchors.leftMargin: 12
anchors.right: parent.right
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: modelData.description || modelData.name || "Unknown"
color: parent._active ? root.accentColor : M.Theme.base05
font.pixelSize: M.Theme.fontSize
font.family: M.Theme.fontFamily
font.bold: parent._active
elide: Text.ElideRight
}
HoverHandler {
id: deviceHover
cursorShape: Qt.PointingHandCursor
}
TapHandler {
onTapped: Pipewire.preferredDefaultAudioSink = modelData
}
}
}
}
// Streams section
Rectangle {
visible: root._streamList.length > 0
width: parent.width - 16
height: visible ? 1 : 0
anchors.horizontalCenter: parent.horizontalCenter
color: M.Theme.base03
}
Text {
visible: root._streamList.length > 0
width: parent.width
height: visible ? 24 : 0
verticalAlignment: Text.AlignVCenter
leftPadding: 12
text: "Applications"
color: root.accentColor
font.pixelSize: M.Theme.fontSize - 1
font.family: M.Theme.fontFamily
}
Repeater {
model: root._streamList
delegate: Item {
id: streamEntry
required property var modelData
width: deviceList.width
height: 32
readonly property string _appName: modelData.properties["application.name"] || modelData.description || modelData.name || "Unknown"
readonly property real _vol: modelData.audio?.volume ?? 0
readonly property bool _muted: modelData.audio?.muted ?? false
Text {
id: streamIcon
anchors.left: parent.left
anchors.leftMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: streamEntry._muted ? "\uF026" : "\uF028"
color: streamEntry._muted ? M.Theme.base04 : M.Theme.base05
font.pixelSize: M.Theme.fontSize
font.family: M.Theme.iconFontFamily
TapHandler {
cursorShape: Qt.PointingHandCursor
onTapped: if (streamEntry.modelData.audio)
streamEntry.modelData.audio.muted = !streamEntry.modelData.audio.muted
}
}
Text {
id: streamName
anchors.left: streamIcon.right
anchors.leftMargin: 6
anchors.verticalCenter: parent.verticalCenter
text: streamEntry._appName
color: M.Theme.base05
font.pixelSize: M.Theme.fontSize - 1
font.family: M.Theme.fontFamily
elide: Text.ElideRight
width: 70
}
Item {
id: streamSlider
anchors.left: streamName.right
anchors.leftMargin: 6
anchors.right: streamVol.left
anchors.rightMargin: 6
anchors.verticalCenter: parent.verticalCenter
height: 4
Rectangle {
anchors.fill: parent
color: M.Theme.base02
radius: 2
}
Rectangle {
width: parent.width * Math.min(1, Math.max(0, streamEntry._vol))
height: parent.height
color: streamEntry._muted ? M.Theme.base04 : root.accentColor
radius: 2
}
MouseArea {
anchors.fill: parent
anchors.margins: -6
cursorShape: Qt.PointingHandCursor
onPressed: mouse => _set(mouse)
onPositionChanged: mouse => {
if (pressed)
_set(mouse);
}
function _set(mouse) {
if (!streamEntry.modelData.audio)
return;
streamEntry.modelData.audio.volume = Math.max(0, Math.min(1, mouse.x / streamSlider.width));
}
}
}
Text {
id: streamVol
anchors.right: parent.right
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: Math.round(streamEntry._vol * 100) + "%"
color: streamEntry._muted ? M.Theme.base04 : M.Theme.base05
font.pixelSize: M.Theme.fontSize - 1
font.family: M.Theme.fontFamily
width: 28
}
}
}
// Bottom padding
Item {
width: 1
height: 4
}
C.VolumeContent {
width: hoverPanel.contentWidth
sink: root.sink
sinkList: root._sinkList
streamList: root._streamList
accentColor: root.accentColor
}
}
}