From 19ba8329386d87f3b1f26d38a4adf13252c469f2 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 20 Apr 2025 17:53:17 +0200 Subject: [PATCH] volume mixer --- .../modules/common/widgets/StyledToolTip.qml | 2 +- .../sidebarRight/CenterWidgetGroup.qml | 5 +- .../sidebarRight/volumeMixer/VolumeMixer.qml | 42 +++++ .../volumeMixer/VolumeMixerEntry.qml | 145 ++++++++++++++++++ 4 files changed, 191 insertions(+), 3 deletions(-) create mode 100644 .config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml create mode 100644 .config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml diff --git a/.config/quickshell/modules/common/widgets/StyledToolTip.qml b/.config/quickshell/modules/common/widgets/StyledToolTip.qml index 388a4bad7..13959fd8f 100644 --- a/.config/quickshell/modules/common/widgets/StyledToolTip.qml +++ b/.config/quickshell/modules/common/widgets/StyledToolTip.qml @@ -9,7 +9,7 @@ ToolTip { property bool extraVisibleCondition: true padding: 7 - visible: (extraVisibleCondition && parent.hovered) + visible: (extraVisibleCondition && (parent.hovered === undefined || parent?.hovered)) background: Rectangle { color: Appearance.colors.colTooltip diff --git a/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml b/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml index 4230a6064..2322a505d 100644 --- a/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml +++ b/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml @@ -2,8 +2,9 @@ import "root:/modules/common" import "root:/modules/common/widgets" import "root:/services" import "./calendar" -import "./todo" import "./notifications" +import "./todo" +import "./volumeMixer" import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Controls @@ -125,7 +126,7 @@ Rectangle { } NotificationList {} - Item{} + VolumeMixer {} } } } \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml new file mode 100644 index 000000000..998cdd480 --- /dev/null +++ b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml @@ -0,0 +1,42 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/services" +import Qt5Compat.GraphicalEffects +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell.Widgets +import Quickshell.Services.Pipewire + + +Item { + id: root + Flickable { + id: flickable + anchors.fill: parent + contentHeight: volumeMixerColumnLayout.height + ColumnLayout { + id: volumeMixerColumnLayout + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: 10 + spacing: 10 + + // get a list of nodes that output to the default sink + PwNodeLinkTracker { + id: linkTracker + node: Pipewire.defaultAudioSink + } + + Repeater { + model: linkTracker.linkGroups + + VolumeMixerEntry { + required property PwLinkGroup modelData + node: modelData.source // target = default sink, source = what we need + } + } + } + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml new file mode 100644 index 000000000..a3898df95 --- /dev/null +++ b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml @@ -0,0 +1,145 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/services" +import Qt5Compat.GraphicalEffects +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell.Widgets +import Quickshell.Services.Pipewire + + +RowLayout { + id: root + required property PwNode node; + PwObjectTracker { objects: [ node ] } + + spacing: 10 + + Image { + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + visible: source != "" + sourceSize.width: 50 + sourceSize.height: 50 + source: { + const icon = node.properties["application.icon-name"] ?? "audio-volume-high-symbolic"; + return `image://icon/${icon}`; + } + } + + ColumnLayout { + Layout.fillWidth: true + RowLayout { + StyledText { + Layout.fillWidth: true + font.pixelSize: Appearance.font.pixelSize.normal + elide: Text.ElideRight + text: { + // application.name -> description -> name + const app = node.properties["application.name"] ?? (node.description != "" ? node.description : node.name); + const media = node.properties["media.name"]; + return media != undefined ? `${app} • ${media}` : app; + } + } + } + + RowLayout { + Slider { + id: slider + property real backgroundDotSize: 4 + property real backgroundDotMargins: 4 + property real handleMargins: slider.pressed ? 3 : 6 + property real handleWidth: slider.pressed ? 3 : 5 + property real handleHeight: 44 + property real handleLimit: slider.backgroundDotMargins + property real limitedHandleWidth: (slider.availableWidth - handleWidth - slider.handleLimit * 2) + Layout.fillWidth: true + value: node.audio.volume + onValueChanged: node.audio.volume = value + from: 0 + to: 1 + + Behavior on value { // This makes the volume shift smoothly + SmoothedAnimation { + velocity: Appearance.animation.elementDecel.velocity + } + } + + Behavior on handleMargins { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + + MouseArea { + anchors.fill: parent + onPressed: (mouse) => mouse.accepted = false + cursorShape: slider.pressed ? Qt.ClosedHandCursor : Qt.PointingHandCursor + } + + background: Item { + anchors.verticalCenter: parent.verticalCenter + implicitHeight: 16 + + // Fill left + Rectangle { + anchors.left: parent.left + width: slider.handleLimit + slider.visualPosition * slider.limitedHandleWidth - (slider.handleMargins + slider.handleWidth / 2) + height: parent.height + color: Appearance.m3colors.m3primary + topLeftRadius: Appearance.rounding.full + bottomLeftRadius: Appearance.rounding.full + topRightRadius: Appearance.rounding.unsharpen + bottomRightRadius: Appearance.rounding.unsharpen + } + + // Fill right + Rectangle { + anchors.right: parent.right + width: slider.handleLimit + (1 - slider.visualPosition) * slider.limitedHandleWidth - (slider.handleMargins + slider.handleWidth / 2) + height: parent.height + color: Appearance.m3colors.m3secondaryContainer + topLeftRadius: Appearance.rounding.unsharpen + bottomLeftRadius: Appearance.rounding.unsharpen + topRightRadius: Appearance.rounding.full + bottomRightRadius: Appearance.rounding.full + } + + // Dot at the end + Rectangle { + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + anchors.rightMargin: slider.backgroundDotMargins + width: slider.backgroundDotSize + height: slider.backgroundDotSize + radius: Appearance.rounding.full + color: Appearance.m3colors.m3onSecondaryContainer + } + } + + handle: Rectangle { + id: handle + x: slider.leftPadding + slider.handleLimit + slider.visualPosition * slider.limitedHandleWidth + y: slider.topPadding + slider.availableHeight / 2 - height / 2 + implicitWidth: slider.handleWidth + implicitHeight: slider.handleHeight + radius: Appearance.rounding.full + color: Appearance.m3colors.m3onSecondaryContainer + + Behavior on implicitWidth { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + + StyledToolTip { + extraVisibleCondition: slider.pressed + content: `${Math.round(slider.value * 100)}%` + } + } + } + } + } +} \ No newline at end of file