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.Layouts
import QtQuick.Controls
import Quickshell.Widgets
import Quickshell.Services.Pipewire
import qs.widgets
@ -13,34 +12,34 @@ Loader {
active: node != null
required property PwNode node
property string label: node.nickname
property string label: node.nickname ?? node.name
sourceComponent: WrapperRectangle {
id: comp
color: ShellSettings.colors.surface_container_translucent
radius: 12
property Component leftWidget
PwObjectTracker {
id: tracker
objects: [root.node]
}
sourceComponent: WrapperItem {
margin: 6
border {
width: 1
color: ShellSettings.colors.active_translucent
}
// property Component button
// property Component icon
PwObjectTracker {
id: tracker
objects: [root.node]
}
RowLayout {
spacing: 10
Loader {
id: leftWidget
sourceComponent: root.leftWidget
Layout.preferredWidth: this.height
Layout.fillHeight: true
}
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
Text {
text: root.label
text: root.label
color: ShellSettings.colors.active
elide: Text.ElideRight
Layout.fillWidth: true
@ -49,8 +48,6 @@ Loader {
StyledSlider {
value: root.node.audio.volume ?? 0
// text: root.text
// icon: root.icon
onValueChanged: {
// only allow changes when the node is ready other wise you will combust
@ -64,42 +61,6 @@ Loader {
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.Layouts
import QtQuick.Effects
import Quickshell.Widgets
import Quickshell.Services.Pipewire
import qs.widgets
@ -49,12 +50,29 @@ StyledMouseArea {
// Default Audio
VolumeCard {
id: defaultCard
node: menu.sink
Layout.fillWidth: true
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 {
visible: linkTracker.linkGroups.length !== 0
color: ShellSettings.colors.active_translucent
radius: height / 2
Layout.leftMargin: 3
@ -64,34 +82,55 @@ StyledMouseArea {
}
// Application Mixer
Loader {
id: sinkLoader
active: menu.sink
PwNodeLinkTracker {
id: linkTracker
node: menu.sink
}
StyledListView {
id: appList
visible: linkTracker.linkGroups.length !== 0
spacing: 6
model: linkTracker.linkGroups
clip: true
Layout.fillWidth: true
Layout.preferredHeight: 5 * menu.entryHeight
Layout.preferredHeight: {
const entryHeight = Math.min(5, linkTracker.linkGroups.length);
PwNodeLinkTracker {
id: linkTracker
node: menu.sink
return entryHeight * (menu.entryHeight + appList.spacing);
}
sourceComponent: ListView {
anchors.fill: parent
spacing: 6
model: linkTracker.linkGroups
delegate: VolumeCard {
id: appCard
node: modelData.source
label: node.properties["media.name"] ?? ""
width: ListView.view.width
height: menu.entryHeight
delegate: Loader {
id: nodeLoader
active: modelData.source != null
width: ListView.view.width
height: menu.entryHeight
required property PwLinkGroup modelData
required property PwLinkGroup modelData
leftWidget: StyledMouseArea {
onClicked: appCard.node.audio.muted = !appCard.node.audio.muted
sourceComponent: VolumeCard {
node: nodeLoader.modelData.source
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 {
horizontalCenter: parent.horizontalCenter
top: parent.top
topMargin: screen.height / 2.75
topMargin: screen.height / 3
}
ColumnLayout {

View file

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

View file

@ -21,7 +21,6 @@ Scope {
target: Pipewire.defaultAudioSink?.audio
function onVolumeChanged() {
console.log("Volume Changed, showing OSD.");
root.shouldShowOsd = true;
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 {
id: slider
implicitHeight: 8
implicitHeight: 7
property var accentColor: ShellSettings.colors.active
background: Rectangle {
id: sliderContainer
width: slider.availableWidth
height: slider.implicitHeight
color: ShellSettings.colors.inactive
radius: 4
color: "transparent"
border.color: ShellSettings.colors.active_translucent
border.width: 1
radius: 5
anchors.verticalCenter: parent.verticalCenter
layer.enabled: true
@ -39,7 +42,7 @@ Slider {
id: fill
width: slider.handle.width / 2 + slider.visualPosition * (sliderContainer.width - slider.handle.width)
height: sliderContainer.height
color: Qt.color(slider.accentColor ?? "purple").darker(1.2)
color: ShellSettings.colors.active_translucent
}
}