From aebd3235d755cb1ff95995b461e497fea2d52e8b Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Thu, 11 Sep 2025 14:43:11 +0200 Subject: ... --- .../gkleen@sif/shell/quickshell/PipewireWidget.qml | 412 +++++++++++++-------- 1 file changed, 265 insertions(+), 147 deletions(-) (limited to 'accounts/gkleen@sif/shell/quickshell/PipewireWidget.qml') diff --git a/accounts/gkleen@sif/shell/quickshell/PipewireWidget.qml b/accounts/gkleen@sif/shell/quickshell/PipewireWidget.qml index 007ce100..3e0b8fd9 100644 --- a/accounts/gkleen@sif/shell/quickshell/PipewireWidget.qml +++ b/accounts/gkleen@sif/shell/quickshell/PipewireWidget.qml @@ -114,232 +114,350 @@ Item { implicitWidth: tooltipContent.width implicitHeight: tooltipContent.height - color: "black" + color: "transparent" - WrapperMouseArea { - id: tooltipMouseArea + Rectangle { + width: tooltip.width + height: tooltipLayout.childrenRect.height + 16 + color: "black" + } - hoverEnabled: true - enabled: true + WrapperItem { + id: tooltipContent - anchors.fill: parent + bottomMargin: Math.max(0, 200 - tooltipLayout.implicitHeight) + + WrapperMouseArea { + id: tooltipMouseArea - WrapperItem { - id: tooltipContent + hoverEnabled: true + enabled: true - margin: 8 - bottomMargin: 8 + Math.max(0, 200 - tooltipLayout.implicitHeight) + WrapperItem { + margin: 8 + bottomMargin: 8 - GridLayout { - id: tooltipLayout + GridLayout { + id: tooltipLayout - columns: 4 + columns: 4 - Repeater { - model: Array.from(Pipewire.devices.values).filter(dev => dev.type == "Audio/Device") + Repeater { + model: Array.from(Pipewire.devices.values).filter(dev => dev.type == "Audio/Device") - Item { - id: descItem + Item { + id: descItem - required property var modelData - required property int index + required property var modelData + required property int index - Layout.column: 0 - Layout.row: index + Layout.column: 0 + Layout.row: index - implicitWidth: descText.contentWidth - implicitHeight: descText.contentHeight + implicitWidth: descText.contentWidth + implicitHeight: descText.contentHeight - Text { - id: descText + Text { + id: descText - color: "white" - font.pointSize: 10 - font.family: "Fira Sans" + color: "white" + font.pointSize: 10 + font.family: "Fira Sans" - text: descItem.modelData.description + text: descItem.modelData.description + } } } - } - Repeater { - id: defaultSinkRepeater + Repeater { + id: defaultSinkRepeater - model: { - Array.from(Pipewire.devices.values) - .filter(dev => dev.type == "Audio/Device") - .map(device => Array.from(Pipewire.nodes.values).find(node => node.type == PwNodeType.AudioSink && node.device?.id == device.id )); - } + model: { + Array.from(Pipewire.devices.values) + .filter(dev => dev.type == "Audio/Device") + .map(device => Array.from(Pipewire.nodes.values).find(node => node.type == PwNodeType.AudioSink && node.device?.id == device.id )); + } - Item { - id: defaultSinkItem + Item { + id: defaultSinkItem - required property var modelData - required property int index + required property var modelData + required property int index - PwObjectTracker { - objects: [defaultSinkItem.modelData] - } + visible: Boolean(modelData) - Layout.column: 1 - Layout.row: index + PwObjectTracker { + objects: [defaultSinkItem.modelData] + } - Layout.fillHeight: true + Layout.column: 1 + Layout.row: index - implicitWidth: 16 + 8 + Layout.fillHeight: true - WrapperMouseArea { - id: defaultSinkMouseArea + implicitWidth: 16 + 8 - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor + WrapperMouseArea { + id: defaultSinkMouseArea - onClicked: { - Pipewire.preferredDefaultAudioSink = defaultSinkItem.modelData - } + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor - Rectangle { - id: defaultSinkWidget + onClicked: { + Pipewire.preferredDefaultAudioSink = defaultSinkItem.modelData + } - anchors.fill: parent - color: { - if (defaultSinkMouseArea.containsMouse) - return "#33808080"; - return "transparent"; + onWheel: event => scrollVolume(event); + property real sensitivity: (1 / 40) / 120 + function scrollVolume(event) { + defaultSinkItem.modelData.audio.volume += event.angleDelta.y * sensitivity; } - MaterialDesignIcon { - width: 16 - height: 16 + Rectangle { + id: defaultSinkWidget + + anchors.fill: parent + color: { + if (defaultSinkMouseArea.containsMouse) + return "#33808080"; + return "transparent"; + } + + MaterialDesignIcon { + width: 16 + height: 16 + anchors.centerIn: parent + + icon: { + if (defaultSinkItem.modelData?.id == Pipewire.defaultAudioSink?.id) + return "speaker"; + return "speaker-off"; + } + color: icon == "speaker" ? "white" : "#555" + } + } + } + + PopupWindow { + id: volumeTooltip + + property bool nextVisible: defaultSinkMouseArea.containsMouse || volumeTooltipMouseArea.containsMouse + + anchor { + item: defaultSinkMouseArea + edges: Edges.Bottom | Edges.Left + } + visible: false + + onNextVisibleChanged: volumeHangTimer.restart() + + onVisibleChanged: tooltip.openPopup = volumeTooltip.visible + + Timer { + id: volumeHangTimer + interval: 100 + onTriggered: volumeTooltip.visible = volumeTooltip.nextVisible + } + + implicitWidth: volumeTooltipText.contentWidth + 16 + implicitHeight: volumeTooltipText.contentHeight + 16 + color: "black" + + WrapperMouseArea { + id: volumeTooltipMouseArea + + hoverEnabled: true + enabled: true + + onWheel: event => defaultSinkMouseArea.scrollVolume(event); + anchors.centerIn: parent - icon: { - if (defaultSinkItem.modelData?.id == Pipewire.defaultAudioSink?.id) - return "speaker"; - return "speaker-off"; + Text { + id: volumeTooltipText + + font.pointSize: 10 + font.family: "Fira Sans" + color: "white" + + text: `${Math.round(defaultSinkItem.modelData?.audio?.volume * 100)}%` } - color: icon == "speaker" ? "white" : "#555" } } } } - } - Repeater { - id: defaultSourceRepeater + Repeater { + id: defaultSourceRepeater - model: { - Array.from(Pipewire.devices.values) - .filter(dev => dev.type == "Audio/Device") - .map(device => Array.from(Pipewire.nodes.values).find(node => node.type == PwNodeType.AudioSource && node.device?.id == device.id )); - } + model: { + Array.from(Pipewire.devices.values) + .filter(dev => dev.type == "Audio/Device") + .map(device => Array.from(Pipewire.nodes.values).find(node => node.type == PwNodeType.AudioSource && node.device?.id == device.id )); + } - Item { - id: defaultSourceItem + Item { + id: defaultSourceItem - required property var modelData - required property int index + required property var modelData + required property int index - PwObjectTracker { - objects: [defaultSourceItem.modelData] - } + visible: Boolean(modelData) + + PwObjectTracker { + objects: [defaultSourceItem.modelData] + } + + Layout.column: 2 + Layout.row: index - Layout.column: 2 - Layout.row: index + Layout.fillHeight: true + + implicitWidth: 16 + 8 + + WrapperMouseArea { + id: defaultSourceMouseArea + + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor - Layout.fillHeight: true + onClicked: { + Pipewire.preferredDefaultAudioSource = defaultSourceItem.modelData + } - implicitWidth: 16 + 8 + onWheel: event => scrollVolume(event); + property real sensitivity: (1 / 40) / 120 + function scrollVolume(event) { + defaultSourceItem.modelData.audio.volume += event.angleDelta.y * sensitivity; + } - WrapperMouseArea { - id: defaultSourceMouseArea + Rectangle { + id: defaultSourceWidget - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor + anchors.fill: parent + color: { + if (defaultSourceMouseArea.containsMouse) + return "#33808080"; + return "transparent"; + } - onClicked: { - Pipewire.preferredDefaultAudioSource = defaultSourceItem.modelData + MaterialDesignIcon { + width: 16 + height: 16 + anchors.centerIn: parent + + icon: { + if (defaultSourceItem.modelData?.id == Pipewire.defaultAudioSource?.id) + return "microphone"; + return "microphone-off"; + } + color: icon == "microphone" ? "white" : "#555" + } + } } - Rectangle { - id: defaultSourceWidget + PopupWindow { + id: volumeTooltip - anchors.fill: parent - color: { - if (defaultSourceMouseArea.containsMouse) - return "#33808080"; - return "transparent"; + property bool nextVisible: defaultSourceMouseArea.containsMouse || volumeTooltipMouseArea.containsMouse + + anchor { + item: defaultSourceMouseArea + edges: Edges.Bottom | Edges.Left } + visible: false + + onNextVisibleChanged: volumeHangTimer.restart() + + onVisibleChanged: tooltip.openPopup = volumeTooltip.visible + + Timer { + id: volumeHangTimer + interval: 100 + onTriggered: volumeTooltip.visible = volumeTooltip.nextVisible + } + + implicitWidth: volumeTooltipText.contentWidth + 16 + implicitHeight: volumeTooltipText.contentHeight + 16 + color: "black" + + WrapperMouseArea { + id: volumeTooltipMouseArea + + hoverEnabled: true + enabled: true + + onWheel: event => defaultSourceMouseArea.scrollVolume(event); - MaterialDesignIcon { - width: 16 - height: 16 anchors.centerIn: parent - icon: { - if (defaultSourceItem.modelData?.id == Pipewire.defaultAudioSource?.id) - return "microphone"; - return "microphone-off"; + Text { + id: volumeTooltipText + + font.pointSize: 10 + font.family: "Fira Sans" + color: "white" + + text: `${Math.round(defaultSourceItem.modelData?.audio?.volume * 100)}%` } - color: icon == "microphone" ? "white" : "#555" } } } } - } - Repeater { - id: profileRepeater + Repeater { + id: profileRepeater - model: Array.from(Pipewire.devices.values).filter(dev => dev.type == "Audio/Device") + model: Array.from(Pipewire.devices.values).filter(dev => dev.type == "Audio/Device") - Item { - id: profileItem + Item { + id: profileItem - required property var modelData - required property int index + required property var modelData + required property int index - PwObjectTracker { - objects: [profileItem.modelData] - } + PwObjectTracker { + objects: [profileItem.modelData] + } - Layout.column: 3 - Layout.row: index + Layout.column: 3 + Layout.row: index - Layout.fillWidth: true + Layout.fillWidth: true - implicitWidth: Math.max(profileBox.implicitWidth, 300) - implicitHeight: profileBox.height + implicitWidth: Math.max(profileBox.implicitWidth, 300) + implicitHeight: profileBox.height - ComboBox { - id: profileBox + ComboBox { + id: profileBox - model: profileItem.modelData.profiles + model: profileItem.modelData.profiles - textRole: "description" - valueRole: "index" - onActivated: profileItem.modelData.setProfile(currentValue) + textRole: "description" + valueRole: "index" + onActivated: profileItem.modelData.setProfile(currentValue) - anchors.fill: parent + anchors.fill: parent - implicitContentWidthPolicy: ComboBox.WidestText + implicitContentWidthPolicy: ComboBox.WidestText - Connections { - target: profileItem.modelData - function onCurrentProfileChanged() { + Connections { + target: profileItem.modelData + function onCurrentProfileChanged() { + profileBox.currentIndex = Array.from(profileItem.modelData.profiles).findIndex(profile => profile.index == profileItem.modelData.currentProfile); + } + } + Component.onCompleted: { profileBox.currentIndex = Array.from(profileItem.modelData.profiles).findIndex(profile => profile.index == profileItem.modelData.currentProfile); } - } - Component.onCompleted: { - profileBox.currentIndex = Array.from(profileItem.modelData.profiles).findIndex(profile => profile.index == profileItem.modelData.currentProfile); - } - Connections { - target: profileBox.popup - function onVisibleChanged() { - tooltip.openPopup = profileBox.popup.visible + Connections { + target: profileBox.popup + function onVisibleChanged() { + tooltip.openPopup = profileBox.popup.visible + } } } } -- cgit v1.2.3