feat: proper volume control center, still not done though

This commit is contained in:
kossLAN 2025-11-03 14:39:53 -05:00
parent 102fa853a8
commit 99eb6956bd
Signed by: kossLAN
SSH key fingerprint: SHA256:bdV0x+wdQHGJ6LgmstH3KV8OpWY+OOFmJcPcB0wQPV8
10 changed files with 140 additions and 256 deletions

View file

@ -1,70 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell.Widgets
import Quickshell.Services.Pipewire
import "../../widgets/" as Widgets
import "../.."
ColumnLayout {
id: root
Loader {
id: sinkLoader
active: sink
property PwNode sink: Pipewire.defaultAudioSink
sourceComponent: WrapperItem {
PwNodeLinkTracker {
id: linkTracker
node: sinkLoader.sink
}
ColumnLayout {
Repeater {
model: linkTracker.linkGroups
delegate: Loader {
id: nodeLoader
active: modelData.source !== null
Layout.preferredWidth: 350
Layout.preferredHeight: 45
required property PwLinkGroup modelData
sourceComponent: VolumeCard {
id: nodeCard
node: nodeLoader.modelData.source
text: node.properties["media.name"] ?? ""
// if icon-name is undefined, just gonna fallback on the application name
icon: IconImage {
source: {
if (nodeCard.node.properties["application.icon-name"] !== undefined)
return `image://icon/${nodeCard.node.properties["application.icon-name"]}`;
let applicationName = nodeCard.node.properties["application.name"];
return `image://icon/${applicationName?.toLowerCase() ?? "image-missing"}`;
}
}
button: Widgets.FontIconButton {
hoverEnabled: false
iconName: nodeCard.node.audio.muted ? "volume_off" : "volume_up"
checked: !nodeCard.node.audio.muted
inactiveColor: ShellSettings.colors["surface_container_highest"]
onClicked: {
nodeCard.node.audio.muted = !nodeCard.node.audio.muted;
}
}
anchors.fill: parent
}
}
}
}
}
}
}

View file

@ -1,65 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell.Services.Pipewire
import Quickshell.Widgets
import qs
import qs.widgets
ColumnLayout {
id: root
// headphones
// don't load until the node is not null
Loader {
id: sinkLoader
active: sink !== null
Layout.preferredWidth: 350
Layout.preferredHeight: 45
property PwNode sink: Pipewire.defaultAudioSink
sourceComponent: VolumeCard {
id: sinkCard
node: sinkLoader.sink
button: StyledMouseArea {
property bool checked: !sinkCard.node.audio.muted
// IconImage {}
onClicked: {
sinkCard.node.audio.muted = !sinkCard.node.audio.muted;
}
}
anchors.fill: parent
}
}
// microphone, same as above
Loader {
id: sourceLoader
active: source !== null
Layout.preferredWidth: 350
Layout.preferredHeight: 45
property PwNode source: Pipewire.defaultAudioSource
sourceComponent: VolumeCard {
id: sourceCard
node: sourceLoader.source
button: StyledMouseArea {
property bool checked: !sourceCard.node.audio.muted
// IconImage {}
onClicked: {
sourceCard.node.audio.muted = !sourceCard.node.audio.muted;
}
}
anchors.fill: parent
}
}
}

View file

@ -2,7 +2,6 @@ pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls
import Quickshell.Widgets import Quickshell.Widgets
import Quickshell.Services.Pipewire import Quickshell.Services.Pipewire
import qs.widgets import qs.widgets
@ -13,28 +12,28 @@ Loader {
active: node != null active: node != null
required property PwNode node required property PwNode node
property string label: node.nickname property string label: node.nickname ?? node.name
sourceComponent: WrapperRectangle { property Component leftWidget
id: comp
color: ShellSettings.colors.surface_container_translucent
radius: 12
margin: 6
border {
width: 1
color: ShellSettings.colors.active_translucent
}
// property Component button
// property Component icon
PwObjectTracker { PwObjectTracker {
id: tracker id: tracker
objects: [root.node] objects: [root.node]
} }
sourceComponent: WrapperItem {
margin: 6
RowLayout { RowLayout {
spacing: 10
Loader {
id: leftWidget
sourceComponent: root.leftWidget
Layout.preferredWidth: this.height
Layout.fillHeight: true
}
ColumnLayout { ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
@ -49,8 +48,6 @@ Loader {
StyledSlider { StyledSlider {
value: root.node.audio.volume ?? 0 value: root.node.audio.volume ?? 0
// text: root.text
// icon: root.icon
onValueChanged: { onValueChanged: {
// only allow changes when the node is ready other wise you will combust // only allow changes when the node is ready other wise you will combust
@ -64,42 +61,6 @@ Loader {
Layout.fillHeight: true Layout.fillHeight: true
} }
} }
// StyledMouseArea {
// id: rightArrow
// Layout.preferredWidth: rightArrow.height
// // Layout.fillWidth: true
// Layout.fillHeight: true
//
// IconImage {
// source: "root:resources/general/right-arrow.svg"
// anchors.fill: parent
// }
// }
// Loader {
// id: buttonLoader
// sourceComponent: root.button
//
// Layout.preferredWidth: this.height
// Layout.fillHeight: true
// }
} }
} }
// sourceComponent: VolumeCard {
// id: sinkCard
// node: sinkLoader.sink
// button: StyledMouseArea {
// property bool checked: !sinkCard.node.audio.muted
//
// // IconImage {}
//
// onClicked: {
// sinkCard.node.audio.muted = !sinkCard.node.audio.muted;
// }
// }
//
// anchors.fill: parent
// }
} }

View file

@ -1,36 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell.Widgets
import qs.widgets
DeviceMixer {}
// WrapperItem {
// id: root
//
// ColumnLayout {
// spacing: 10
//
// // TabBar {
// // id: tabBar
// // model: ["headphones", "tune"]
// // Layout.fillWidth: true
// // Layout.preferredHeight: 35
// // }
//
//
// // StackLayout {
// // id: page
// // currentIndex: tabBar.currentIndex
// // Layout.fillWidth: true
// // Layout.preferredHeight: currentItem ? currentItem.implicitHeight : 0
// //
// // readonly property Item currentItem: children[currentIndex]
// //
// // DeviceMixer {}
// // ApplicationMixer {}
// // }
// }
// }

View file

@ -2,6 +2,7 @@ pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Effects
import Quickshell.Widgets import Quickshell.Widgets
import Quickshell.Services.Pipewire import Quickshell.Services.Pipewire
import qs.widgets import qs.widgets
@ -49,12 +50,29 @@ StyledMouseArea {
// Default Audio // Default Audio
VolumeCard { VolumeCard {
id: defaultCard
node: menu.sink node: menu.sink
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: menu.entryHeight Layout.preferredHeight: menu.entryHeight
leftWidget: StyledMouseArea {
onClicked: defaultCard.node.audio.muted = !defaultCard.node.audio.muted
IconImage {
anchors.fill: parent
source: {
if (defaultCard.node.audio.muted) {
return "root:resources/volume/volume-mute.svg";
} else {
return "root:resources/volume/volume-full.svg";
}
}
}
}
} }
Rectangle { Rectangle {
visible: linkTracker.linkGroups.length !== 0
color: ShellSettings.colors.active_translucent color: ShellSettings.colors.active_translucent
radius: height / 2 radius: height / 2
Layout.leftMargin: 3 Layout.leftMargin: 3
@ -64,34 +82,55 @@ StyledMouseArea {
} }
// Application Mixer // Application Mixer
Loader {
id: sinkLoader
active: menu.sink
Layout.fillWidth: true
Layout.preferredHeight: 5 * menu.entryHeight
PwNodeLinkTracker { PwNodeLinkTracker {
id: linkTracker id: linkTracker
node: menu.sink node: menu.sink
} }
sourceComponent: ListView { StyledListView {
anchors.fill: parent id: appList
visible: linkTracker.linkGroups.length !== 0
spacing: 6 spacing: 6
model: linkTracker.linkGroups model: linkTracker.linkGroups
clip: true
delegate: Loader { Layout.fillWidth: true
id: nodeLoader Layout.preferredHeight: {
active: modelData.source != null const entryHeight = Math.min(5, linkTracker.linkGroups.length);
return entryHeight * (menu.entryHeight + appList.spacing);
}
delegate: VolumeCard {
id: appCard
node: modelData.source
label: node.properties["media.name"] ?? ""
width: ListView.view.width width: ListView.view.width
height: menu.entryHeight height: menu.entryHeight
required property PwLinkGroup modelData required property PwLinkGroup modelData
sourceComponent: VolumeCard { leftWidget: StyledMouseArea {
node: nodeLoader.modelData.source onClicked: appCard.node.audio.muted = !appCard.node.audio.muted
label: node.properties["media.name"] ?? ""
IconImage {
id: appIcon
visible: false
anchors.fill: parent
source: {
if (appCard.node.properties["application.icon-name"] !== undefined)
return `image://icon/${appCard.node.properties["application.icon-name"]}`;
let applicationName = appCard.node.properties["application.name"];
return `image://icon/${applicationName?.toLowerCase() ?? "image-missing"}`;
}
}
MultiEffect {
source: appIcon
anchors.fill: appIcon
saturation: appCard.node.audio.muted ? -1.0 : 0.0
} }
} }
} }

View file

@ -64,7 +64,7 @@ Singleton {
anchors { anchors {
horizontalCenter: parent.horizontalCenter horizontalCenter: parent.horizontalCenter
top: parent.top top: parent.top
topMargin: screen.height / 2.75 topMargin: screen.height / 3
} }
ColumnLayout { ColumnLayout {

View file

@ -6,7 +6,7 @@ import QtQuick
import "bar" import "bar"
import "notifications" as Notifications import "notifications" as Notifications
import "mpris" as Mpris import "mpris" as Mpris
import "volume-osd" as VolumeOSD import "volosd" as VolumeOSD
import "settings" as Settings import "settings" as Settings
import "launcher" as Launcher import "launcher" as Launcher
import "lockscreen" as LockScreen import "lockscreen" as LockScreen

View file

@ -21,7 +21,6 @@ Scope {
target: Pipewire.defaultAudioSink?.audio target: Pipewire.defaultAudioSink?.audio
function onVolumeChanged() { function onVolumeChanged() {
console.log("Volume Changed, showing OSD.");
root.shouldShowOsd = true; root.shouldShowOsd = true;
hideTimer.restart(); hideTimer.restart();
} }

View file

@ -0,0 +1,53 @@
import QtQuick
ListView {
id: root
add: Transition {
NumberAnimation {
property: "opacity"
from: 0
to: 1
duration: 100
}
}
displaced: Transition {
NumberAnimation {
property: "y"
duration: 200
easing.type: Easing.OutCubic
}
NumberAnimation {
property: "opacity"
to: 1
duration: 100
}
}
move: Transition {
NumberAnimation {
property: "y"
duration: 200
easing.type: Easing.OutCubic
}
NumberAnimation {
property: "opacity"
to: 1
duration: 100
}
}
remove: Transition {
NumberAnimation {
property: "y"
duration: 200
easing.type: Easing.OutCubic
}
NumberAnimation {
property: "opacity"
to: 0
duration: 100
}
}
}

View file

@ -7,15 +7,18 @@ import ".."
Slider { Slider {
id: slider id: slider
implicitHeight: 8 implicitHeight: 7
property var accentColor: ShellSettings.colors.active property var accentColor: ShellSettings.colors.active
background: Rectangle { background: Rectangle {
id: sliderContainer id: sliderContainer
width: slider.availableWidth width: slider.availableWidth
height: slider.implicitHeight height: slider.implicitHeight
color: ShellSettings.colors.inactive color: "transparent"
radius: 4 border.color: ShellSettings.colors.active_translucent
border.width: 1
radius: 5
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
layer.enabled: true layer.enabled: true
@ -39,7 +42,7 @@ Slider {
id: fill id: fill
width: slider.handle.width / 2 + slider.visualPosition * (sliderContainer.width - slider.handle.width) width: slider.handle.width / 2 + slider.visualPosition * (sliderContainer.width - slider.handle.width)
height: sliderContainer.height height: sliderContainer.height
color: Qt.color(slider.accentColor ?? "purple").darker(1.2) color: ShellSettings.colors.active_translucent
} }
} }