Compare commits

...

3 commits

Author SHA1 Message Date
83d7dc47c4
update volume icons 2025-11-03 18:00:43 -05:00
468291c9ea
bump dots again 2025-11-03 14:55:33 -05:00
99eb6956bd
feat: proper volume control center, still not done though 2025-11-03 14:39:53 -05:00
17 changed files with 177 additions and 321 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,28 +12,28 @@ Loader {
active: node != null
required property PwNode node
property string label: node.nickname
property string label: node.nickname === "" ? node.description : node.nickname
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
@ -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,11 +2,11 @@ pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import QtQuick.Effects
import Quickshell.Widgets
import Quickshell.Services.Pipewire
import qs.widgets
import qs.bar
import qs
StyledMouseArea {
id: root
@ -14,14 +14,19 @@ StyledMouseArea {
required property var bar
property bool showMenu: false
property PwNode sink: Pipewire.defaultAudioSink
IconImage {
id: icon
source: "root:resources/volume/volume-full.svg"
anchors {
fill: parent
margins: 2
anchors.fill: parent
source: if (root.sink.audio.muted) {
return "image://icon/audio-volume-muted";
} else if (root.sink.audio.volume > 0.66) {
return "image://icon/audio-volume-high";
} else if (root.sink.audio.volume > 0.33) {
return "image://icon/audio-volume-medium";
} else {
return "image://icon/audio-volume-low";
}
}
@ -32,66 +37,95 @@ StyledMouseArea {
show: root.showMenu
onClosed: root.showMenu = false
implicitWidth: 300
implicitWidth: 275
implicitHeight: container.implicitHeight + (2 * 8)
property PwNode sink: Pipewire.defaultAudioSink
property real entryHeight: 45
property real entryHeight: 40
ColumnLayout {
id: container
spacing: 4
spacing: 2
anchors {
fill: parent
margins: 8
margins: 4
}
// Default Audio
VolumeCard {
node: menu.sink
id: defaultCard
node: root.sink
Layout.fillWidth: true
Layout.preferredHeight: menu.entryHeight
}
Rectangle {
color: ShellSettings.colors.active_translucent
radius: height / 2
Layout.leftMargin: 3
Layout.rightMargin: 3
Layout.fillWidth: true
Layout.preferredHeight: 2
leftWidget: StyledMouseArea {
onClicked: defaultCard.node.audio.muted = !defaultCard.node.audio.muted
IconImage {
anchors.fill: parent
source: if (root.sink.audio.muted) {
return "image://icon/audio-volume-muted";
} else if (root.sink.audio.volume > 0.66) {
return "image://icon/audio-volume-high";
} else if (root.sink.audio.volume > 0.33) {
return "image://icon/audio-volume-medium";
} else {
return "image://icon/audio-volume-low";
}
}
}
}
// Application Mixer
Loader {
id: sinkLoader
active: menu.sink
PwNodeLinkTracker {
id: linkTracker
node: root.sink
}
StyledListView {
id: appList
visible: linkTracker.linkGroups.length !== 0
spacing: 2
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 {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="white" width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 16c2.206 0 4-1.794 4-4V6c0-2.217-1.785-4.021-3.979-4.021a.933.933 0 0 0-.209.025A4.006 4.006 0 0 0 8 6v6c0 2.206 1.794 4 4 4z"/><path d="M11 19.931V22h2v-2.069c3.939-.495 7-3.858 7-7.931h-2c0 3.309-2.691 6-6 6s-6-2.691-6-6H4c0 4.072 3.061 7.436 7 7.931z"/></svg>

Before

Width:  |  Height:  |  Size: 497 B

View file

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="white" width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m21.707 20.293-3.4-3.4A7.93 7.93 0 0 0 20 12h-2a5.945 5.945 0 0 1-1.119 3.467l-1.449-1.45A3.926 3.926 0 0 0 16 12V6c0-2.217-1.785-4.021-3.979-4.021-.07 0-.14.009-.209.025A4.006 4.006 0 0 0 8 6v.586L3.707 2.293 2.293 3.707l18 18 1.414-1.414zM6 12H4c0 4.072 3.06 7.436 7 7.931V22h2v-2.069a7.935 7.935 0 0 0 2.241-.63l-1.549-1.548A5.983 5.983 0 0 1 12 18c-3.309 0-6-2.691-6-6z"/><path d="M8.007 12.067a3.996 3.996 0 0 0 3.926 3.926l-3.926-3.926z"/></svg>

Before

Width:  |  Height:  |  Size: 682 B

View file

@ -1,9 +0,0 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg fill="#ffffff" width="128px" height="128px" viewBox="0 -32 576 576" xmlns="http://www.w3.org/2000/svg" stroke="#ffffff">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
<g id="SVGRepo_iconCarrier">
<path d="M215.03 71.05L126.06 160H24c-13.26 0-24 10.74-24 24v144c0 13.25 10.74 24 24 24h102.06l88.97 88.95c15.03 15.03 40.97 4.47 40.97-16.97V88.02c0-21.46-25.96-31.98-40.97-16.97zm233.32-51.08c-11.17-7.33-26.18-4.24-33.51 6.95-7.34 11.17-4.22 26.18 6.95 33.51 66.27 43.49 105.82 116.6 105.82 195.58 0 78.98-39.55 152.09-105.82 195.58-11.17 7.32-14.29 22.34-6.95 33.5 7.04 10.71 21.93 14.56 33.51 6.95C528.27 439.58 576 351.33 576 256S528.27 72.43 448.35 19.97zM480 256c0-63.53-32.06-121.94-85.77-156.24-11.19-7.14-26.03-3.82-33.12 7.46s-3.78 26.21 7.41 33.36C408.27 165.97 432 209.11 432 256s-23.73 90.03-63.48 115.42c-11.19 7.14-14.5 22.07-7.41 33.36 6.51 10.36 21.12 15.14 33.12 7.46C447.94 377.94 480 319.54 480 256zm-141.77-76.87c-11.58-6.33-26.19-2.16-32.61 9.45-6.39 11.61-2.16 26.2 9.45 32.61C327.98 228.28 336 241.63 336 256c0 14.38-8.02 27.72-20.92 34.81-11.61 6.41-15.84 21-9.45 32.61 6.43 11.66 21.05 15.8 32.61 9.45 28.23-15.55 45.77-45 45.77-76.88s-17.54-61.32-45.78-76.86z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -1,9 +0,0 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg fill="#ffffff" width="800px" height="800px" viewBox="-64 0 512 512" xmlns="http://www.w3.org/2000/svg">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
<g id="SVGRepo_iconCarrier">
<path d="M215.03 72.04L126.06 161H24c-13.26 0-24 10.74-24 24v144c0 13.25 10.74 24 24 24h102.06l88.97 88.95c15.03 15.03 40.97 4.47 40.97-16.97V89.02c0-21.47-25.96-31.98-40.97-16.98zm123.2 108.08c-11.58-6.33-26.19-2.16-32.61 9.45-6.39 11.61-2.16 26.2 9.45 32.61C327.98 229.28 336 242.62 336 257c0 14.38-8.02 27.72-20.92 34.81-11.61 6.41-15.84 21-9.45 32.61 6.43 11.66 21.05 15.8 32.61 9.45 28.23-15.55 45.77-45 45.77-76.88s-17.54-61.32-45.78-76.87z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 914 B

View file

@ -1,9 +0,0 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg fill="#ffffff" width="800px" height="800px" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
<g id="SVGRepo_iconCarrier">
<path d="M215.03 71.05L126.06 160H24c-13.26 0-24 10.74-24 24v144c0 13.25 10.74 24 24 24h102.06l88.97 88.95c15.03 15.03 40.97 4.47 40.97-16.97V88.02c0-21.46-25.96-31.98-40.97-16.97zM461.64 256l45.64-45.64c6.3-6.3 6.3-16.52 0-22.82l-22.82-22.82c-6.3-6.3-16.52-6.3-22.82 0L416 210.36l-45.64-45.64c-6.3-6.3-16.52-6.3-22.82 0l-22.82 22.82c-6.3 6.3-6.3 16.52 0 22.82L370.36 256l-45.63 45.63c-6.3 6.3-6.3 16.52 0 22.82l22.82 22.82c6.3 6.3 16.52 6.3 22.82 0L416 301.64l45.64 45.64c6.3 6.3 16.52 6.3 22.82 0l22.82-22.82c6.3-6.3 6.3-16.52 0-22.82L461.64 256z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1,014 B

View file

@ -1,9 +0,0 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg fill="#ffffff" width="800px" height="800px" viewBox="-128 0 512 512" xmlns="http://www.w3.org/2000/svg">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
<g id="SVGRepo_iconCarrier">
<path d="M215 71l-89 89H24a24 24 0 0 0-24 24v144a24 24 0 0 0 24 24h102.06L215 441c15 15 41 4.47 41-17V88c0-21.47-26-32-41-17z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 594 B

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,11 @@ Scope {
target: Pipewire.defaultAudioSink?.audio
function onVolumeChanged() {
console.log("Volume Changed, showing OSD.");
root.shouldShowOsd = true;
hideTimer.restart();
}
function onMutedChanged() {
root.shouldShowOsd = true;
hideTimer.restart();
}
@ -53,6 +57,8 @@ Scope {
// radius: 8
RowLayout {
spacing: 10
anchors {
fill: parent
leftMargin: 10
@ -61,15 +67,25 @@ Scope {
IconImage {
implicitSize: 30
source: "root:resources/volume/volume-full.svg"
source: if (Pipewire.defaultAudioSink?.audio.muted) {
return "image://icon/audio-volume-muted";
} else if (Pipewire.defaultAudioSink?.audio.volume > 0.66) {
return "image://icon/audio-volume-high";
} else if (Pipewire.defaultAudioSink?.audio.volume > 0.33) {
return "image://icon/audio-volume-medium";
} else {
return "image://icon/audio-volume-low";
}
}
Rectangle {
id: sliderBackground
Layout.fillWidth: true
implicitHeight: 10
radius: 20
color: ShellSettings.colors.inactive
radius: height / 2
color: "transparent"
border.color: ShellSettings.colors.active_translucent
border.width: 1
layer.enabled: true
layer.effect: OpacityMask {
@ -83,13 +99,13 @@ Scope {
Rectangle {
color: ShellSettings.colors.active
implicitWidth: parent.width * (Pipewire.defaultAudioSink?.audio.volume ?? 0)
anchors {
left: parent.left
top: parent.top
bottom: parent.bottom
}
implicitWidth: parent.width * (Pipewire.defaultAudioSink?.audio.volume ?? 0)
}
}
}

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
}
}