From 0faf9287ba4a9f0846b71656bd7996b22645f100 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 21 Apr 2025 17:51:28 +0200 Subject: [PATCH] osd --- .config/quickshell/modules/bar/Bar.qml | 84 ++++++++++--- .../quickshell/modules/common/Appearance.qml | 1 + .../modules/common/ConfigOptions.qml | 6 +- .../common/widgets/CircularProgress.qml | 2 +- .../common/widgets/StyledProgressBar.qml | 57 +++++++++ .../onScreenDisplay/OnScreenDisplay.qml | 118 ++++++++++++++++++ .../onScreenDisplay/OsdValueIndicator.qml | 86 +++++++++++++ .../modules/onScreenDisplay/OsdValues.qml | 24 ++++ .../modules/screenCorners/ScreenCorners.qml | 1 - .../modules/sidebarRight/SidebarRight.qml | 2 +- .config/quickshell/services/Audio.qml | 1 + .config/quickshell/services/Brightness.qml | 36 ++++-- .config/quickshell/shell.qml | 2 + 13 files changed, 392 insertions(+), 28 deletions(-) create mode 100644 .config/quickshell/modules/common/widgets/StyledProgressBar.qml create mode 100644 .config/quickshell/modules/onScreenDisplay/OnScreenDisplay.qml create mode 100644 .config/quickshell/modules/onScreenDisplay/OsdValueIndicator.qml create mode 100644 .config/quickshell/modules/onScreenDisplay/OsdValues.qml diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index cbaaad833..6a5971459 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -12,6 +12,7 @@ Scope { readonly property int barHeight: Appearance.sizes.barHeight readonly property int barCenterSideModuleWidth: Appearance.sizes.barCenterSideModuleWidth + readonly property int osdHideMouseMoveThreshold: 20 Process { id: openSidebarRight @@ -21,6 +22,10 @@ Scope { id: openSidebarLeft command: ["qs", "ipc", "call", "sidebarLeft", "open"] } + Process { + id: hideOsd + command: ["qs", "ipc", "call", "osd", "hide"] + } Variants { model: Quickshell.screens @@ -60,18 +65,6 @@ Scope { ActiveWindow { bar: barRoot } - - // Scroll to change brightness - WheelHandler { - onWheel: (event) => { - if (event.angleDelta.y < 0) - Brightness.value = -1; - else if (event.angleDelta.y > 0) - Brightness.value = 1; - } - acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad - } - } // Middle section @@ -179,9 +172,57 @@ Scope { } - MouseArea { + + // Interactions + MouseArea { // Left side: scroll to change brightness + id: barLeftSideMouseArea + property bool hovered: false + property real lastScrollX: 0 + property real lastScrollY: 0 + property bool trackingScroll: false + anchors.fill: leftSection + acceptedButtons: Qt.LeftButton + hoverEnabled: true + propagateComposedEvents: true + onEntered: (event) => { + barLeftSideMouseArea.hovered = true + } + onExited: (event) => { + barLeftSideMouseArea.hovered = false + barLeftSideMouseArea.trackingScroll = false + } + // Scroll to change brightness + WheelHandler { + onWheel: (event) => { + if (event.angleDelta.y < 0) + Brightness.increment = -1; + else if (event.angleDelta.y > 0) + Brightness.increment = 1; + // Store the mouse position and start tracking + barLeftSideMouseArea.lastScrollX = event.x; + barLeftSideMouseArea.lastScrollY = event.y; + barLeftSideMouseArea.trackingScroll = true; + } + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + } + onPositionChanged: (mouse) => { + if (barLeftSideMouseArea.trackingScroll) { + const dx = mouse.x - barLeftSideMouseArea.lastScrollX; + const dy = mouse.y - barLeftSideMouseArea.lastScrollY; + if (Math.sqrt(dx*dx + dy*dy) > osdHideMouseMoveThreshold) { + hideOsd.running = true; + barLeftSideMouseArea.trackingScroll = false; + } + } + } + } + + MouseArea { // Right side: scroll to change volume id: barRightSideMouseArea property bool hovered: false + property real lastScrollX: 0 + property real lastScrollY: 0 + property bool trackingScroll: false anchors.fill: rightSection acceptedButtons: Qt.LeftButton hoverEnabled: true @@ -191,6 +232,7 @@ Scope { } onExited: (event) => { barRightSideMouseArea.hovered = false + barRightSideMouseArea.trackingScroll = false } onPressed: (event) => { if (event.button === Qt.LeftButton) { @@ -200,15 +242,29 @@ Scope { // Scroll to change volume WheelHandler { onWheel: (event) => { - const currentVolume = Audio.sink?.audio.volume; + const currentVolume = Audio.value; const step = currentVolume < 0.1 ? 0.01 : 0.02 || 0.2; if (event.angleDelta.y < 0) Audio.sink.audio.volume -= step; else if (event.angleDelta.y > 0) Audio.sink.audio.volume += step; + // Store the mouse position and start tracking + barRightSideMouseArea.lastScrollX = event.x; + barRightSideMouseArea.lastScrollY = event.y; + barRightSideMouseArea.trackingScroll = true; } acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad } + onPositionChanged: (mouse) => { + if (barRightSideMouseArea.trackingScroll) { + const dx = mouse.x - barRightSideMouseArea.lastScrollX; + const dy = mouse.y - barRightSideMouseArea.lastScrollY; + if (Math.sqrt(dx*dx + dy*dy) > osdHideMouseMoveThreshold) { + hideOsd.running = true; + barRightSideMouseArea.trackingScroll = false; + } + } + } } } diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index 70eb0e726..0feb39f7e 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -134,6 +134,7 @@ Singleton { property color colTooltip: m3colors.m3inverseSurface property color colOnTooltip: m3colors.m3inverseOnSurface property color colScrim: transparentize(m3colors.m3scrim, 0.5) + property color colShadow: transparentize(m3colors.m3shadow, 0.75) } rounding: QtObject { diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index 57b821749..d30be6078 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -26,12 +26,16 @@ Singleton { } } + property QtObject osd: QtObject { + property int timeout: 1000 + } + property QtObject resources: QtObject { property int updateInterval: 3000 } property QtObject hacks: QtObject { - property int arbitraryRaceConditionDelay: 10 + property int arbitraryRaceConditionDelay: 10 // milliseconds } } diff --git a/.config/quickshell/modules/common/widgets/CircularProgress.qml b/.config/quickshell/modules/common/widgets/CircularProgress.qml index 2c6b1b9a0..8fc6a83b5 100644 --- a/.config/quickshell/modules/common/widgets/CircularProgress.qml +++ b/.config/quickshell/modules/common/widgets/CircularProgress.qml @@ -1,6 +1,6 @@ // From https://github.com/rafzby/circular-progressbar // License: LGPL-3.0 - A copy can be found in `licenses` folder of repo -// Modified +// Modified so it looks like in Material 3: https://m3.material.io/components/progress-indicators/specs import QtQuick 2.9 Item { diff --git a/.config/quickshell/modules/common/widgets/StyledProgressBar.qml b/.config/quickshell/modules/common/widgets/StyledProgressBar.qml new file mode 100644 index 000000000..078570ffe --- /dev/null +++ b/.config/quickshell/modules/common/widgets/StyledProgressBar.qml @@ -0,0 +1,57 @@ +import "root:/services" +import "root:/modules/common" +import "root:/modules/common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Widgets +import Qt5Compat.GraphicalEffects + +ProgressBar { + id: root + property real valueBarWidth: 120 + property real valueBarHeight: 4 + property real valueBarGap: 4 + + Behavior on value { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + + background: Rectangle { + anchors.fill: parent + color: "transparent" + radius: Appearance.rounding.full + implicitHeight: valueBarHeight + implicitWidth: valueBarWidth + } + + contentItem: Item { + implicitWidth: parent.width + implicitHeight: parent.height + + Rectangle { // Left progress fill + width: root.visualPosition * parent.width + height: parent.height + radius: Appearance.rounding.full + color: Appearance.m3colors.m3primary + } + Rectangle { // Right remaining part fill + anchors.right: parent.right + width: (1 - root.visualPosition) * parent.width - valueBarGap + height: parent.height + radius: Appearance.rounding.full + color: Appearance.m3colors.m3secondaryContainer + } + Rectangle { // Stop point + anchors.right: parent.right + width: valueBarGap + height: valueBarGap + radius: Appearance.rounding.full + color: Appearance.m3colors.m3primary + } + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplay.qml b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplay.qml new file mode 100644 index 000000000..f5fe97d35 --- /dev/null +++ b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplay.qml @@ -0,0 +1,118 @@ +import "root:/services/" +import "root:/modules/common" +import "root:/modules/common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Wayland + +Scope { + id: root + property bool showOsdValues: false + + function triggerOsd() { + showOsdValues = true + osdTimeout.restart() + } + + Timer { + id: osdTimeout + interval: ConfigOptions.osd.timeout + repeat: false + running: false + onTriggered: { + showOsdValues = false + } + } + + Connections { + target: Brightness + function onValueChanged() { + if (!Brightness.ready) return + root.triggerOsd() + } + } + + Connections { + target: Audio.sink.audio + function onVolumeChanged() { + if (!Audio.ready) return + root.triggerOsd() + } + } + + Variants { + model: Quickshell.screens + + PanelWindow { + property var modelData + + screen: modelData + exclusionMode: ExclusionMode.Normal + WlrLayershell.namespace: "quickshell:onScreenDisplay" + WlrLayershell.layer: WlrLayer.Overlay + color: "transparent" + + anchors { + top: true + left: true + right: true + } + mask: Region { + item: columnLayout + } + + width: columnLayout.implicitWidth + height: columnLayout.implicitHeight + visible: showOsdValues + + ColumnLayout { + id: columnLayout + anchors.horizontalCenter: parent.horizontalCenter + Item { + height: 1 // Prevent Wayland protocol error + } + Item { + implicitHeight: true ? osdValues.implicitHeight : 0 + implicitWidth: osdValues.implicitWidth + clip: true + + Behavior on implicitHeight { + NumberAnimation { + duration: Appearance.animation.menuDecel.duration + easing.type: Appearance.animation.menuDecel.type + } + } + + OsdValues { + id: osdValues + anchors.bottom: parent.bottom + // height: showOsdValues ? implicitHeight : 0 + // implicitHeight: 0 + } + } + } + + } + + } + + IpcHandler { + target: "osd" + + function trigger() { + root.triggerOsd() + } + + function hide() { + showOsdValues = false + } + + function toggle() { + showOsdValues = !showOsdValues + } + } + +} diff --git a/.config/quickshell/modules/onScreenDisplay/OsdValueIndicator.qml b/.config/quickshell/modules/onScreenDisplay/OsdValueIndicator.qml new file mode 100644 index 000000000..5437c09ee --- /dev/null +++ b/.config/quickshell/modules/onScreenDisplay/OsdValueIndicator.qml @@ -0,0 +1,86 @@ +import "root:/services" +import "root:/modules/common" +import "root:/modules/common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Widgets +import Qt5Compat.GraphicalEffects + +Item { + id: root + required property real value + required property string icon + required property string name + + property real valueIndicatorVerticalPadding: 5 + property real valueIndicatorLeftPadding: 10 + property real valueIndicatorRightPadding: 20 // An icon is circle ish, a column isn't, hence the extra padding + + Layout.margins: Appearance.sizes.elevationMargin + implicitWidth: valueIndicator.implicitWidth + implicitHeight: valueIndicator.implicitHeight + + WrapperRectangle { + id: valueIndicator + radius: Appearance.rounding.full + color: Appearance.colors.colLayer0 + implicitWidth: valueRow.implicitWidth + + RowLayout { // Icon on the left, stuff on the right + id: valueRow + spacing: 5 + Layout.margins: 10 + + MaterialSymbol { // Icon + Layout.alignment: Qt.AlignVCenter + Layout.leftMargin: valueIndicatorLeftPadding + Layout.topMargin: valueIndicatorVerticalPadding + Layout.bottomMargin: valueIndicatorVerticalPadding + text: root.icon + font.pixelSize: 30 + } + ColumnLayout { // Stuff + Layout.alignment: Qt.AlignVCenter + Layout.rightMargin: valueIndicatorRightPadding + spacing: 5 + + RowLayout { // Name fill left, value on the right end + Layout.leftMargin: valueBarHeight / 2 // Align text with progressbar radius curve's left end + Layout.rightMargin: valueBarHeight / 2 // Align text with progressbar radius curve's left end + + StyledText { + color: Appearance.colors.colOnLayer0 + font.pixelSize: Appearance.font.pixelSize.small + Layout.fillWidth: true + text: root.name + } + + StyledText { + color: Appearance.colors.colOnLayer0 + font.pixelSize: Appearance.font.pixelSize.small + Layout.fillWidth: false + text: Math.round(root.value * 100) + } + } + + StyledProgressBar { + id: valueProgressBar + value: root.value + } + } + } + } + + DropShadow { + id: valueShadow + anchors.fill: valueIndicator + source: valueIndicator + radius: Appearance.sizes.elevationMargin + samples: radius * 2 + 1 + color: Appearance.colors.colShadow + verticalOffset: 2 + horizontalOffset: 0 + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/onScreenDisplay/OsdValues.qml b/.config/quickshell/modules/onScreenDisplay/OsdValues.qml new file mode 100644 index 000000000..ccb23e386 --- /dev/null +++ b/.config/quickshell/modules/onScreenDisplay/OsdValues.qml @@ -0,0 +1,24 @@ +import "root:/services" +import "root:/modules/common" +import "root:/modules/common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Widgets +import Qt5Compat.GraphicalEffects + +RowLayout { + spacing: -5 + + OsdValueIndicator { + value: Brightness.value + icon: "light_mode" + name: "Brightness" + } + OsdValueIndicator { + value: Audio.sink.audio.volume + icon: "volume_up" + name: "Volume" + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/screenCorners/ScreenCorners.qml b/.config/quickshell/modules/screenCorners/ScreenCorners.qml index f7075305f..32708dc60 100644 --- a/.config/quickshell/modules/screenCorners/ScreenCorners.qml +++ b/.config/quickshell/modules/screenCorners/ScreenCorners.qml @@ -14,7 +14,6 @@ Scope { model: Quickshell.screens PanelWindow { - id: barRoot visible: (ConfigOptions.appearance.fakeScreenRounding === 1 || (ConfigOptions.appearance.fakeScreenRounding === 2 && !activeWindow?.fullscreen)) property var modelData diff --git a/.config/quickshell/modules/sidebarRight/SidebarRight.qml b/.config/quickshell/modules/sidebarRight/SidebarRight.qml index 572015fb0..b2e9404b1 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarRight.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarRight.qml @@ -162,7 +162,7 @@ Scope { verticalOffset: 2 radius: Appearance.sizes.elevationMargin samples: Appearance.sizes.elevationMargin * 2 + 1 // Ideally should be 2 * radius + 1, see qt docs - color: Appearance.transparentize(Appearance.m3colors.m3shadow, 0.55) + color: Appearance.colors.colShadow source: sidebarRightBackground } diff --git a/.config/quickshell/services/Audio.qml b/.config/quickshell/services/Audio.qml index 4bfe0e881..80c59f164 100644 --- a/.config/quickshell/services/Audio.qml +++ b/.config/quickshell/services/Audio.qml @@ -7,6 +7,7 @@ pragma ComponentBehavior: Bound Singleton { id: root + property bool ready: Pipewire.defaultAudioSink.ready property var sink: Pipewire.defaultAudioSink property var source: Pipewire.defaultAudioSource diff --git a/.config/quickshell/services/Brightness.qml b/.config/quickshell/services/Brightness.qml index a11c1fa4a..e8d697a1f 100644 --- a/.config/quickshell/services/Brightness.qml +++ b/.config/quickshell/services/Brightness.qml @@ -6,22 +6,22 @@ pragma ComponentBehavior: Bound Singleton { id: root - property string brightness - property int value: 0 + property bool ready: false + property real value + property int increment: 0 function refresh() { getBrightness.running = true; } - onValueChanged: () => { - if (value > 0) { + onIncrementChanged: () => { + if (increment > 0) { increaseBrightness.running = true; - root.value = 0; - } else if (value < 0) { + root.increment = 0; + } else if (increment < 0) { decreaseBrightness.running = true; - root.value = 0; + root.increment = 0; } - getBrightness.running = true; } Process { @@ -30,12 +30,15 @@ Singleton { command: ["sh", "-c", "brightnessctl -m i | cut -d, -f4"] running: true onExited: { - running = false; + if (!ready) ready = true } stdout: SplitParser { onRead: (data) => { - root.brightness = data; + root.value = parseFloat(data.replace("%", "")) / 100; + if (root.value < 0.01) { + preventPitchBlack.running = true; + } } } @@ -48,6 +51,7 @@ Singleton { running: false onExited: { running = false; + getBrightness.running = true; } } @@ -58,6 +62,18 @@ Singleton { running: false onExited: { running = false; + getBrightness.running = true; + } + } + + Process { + id: preventPitchBlack + + command: ["brightnessctl", "set", "1%+"] + running: false + onExited: { + running = false; + getBrightness.running = true; } } diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index cda323564..ab43b8243 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -1,6 +1,7 @@ //@ pragma UseQApplication import "./modules/bar/" +import "./modules/onScreenDisplay/" import "./modules/screenCorners/" import "./modules/sidebarRight/" import QtQuick @@ -14,5 +15,6 @@ ShellRoot { SidebarRight {} ScreenCorners {} ReloadPopup {} + OnScreenDisplay {} }