diff --git a/Modules/OSD/BrightnessOSD.qml b/Modules/OSD/OSD.qml similarity index 62% rename from Modules/OSD/BrightnessOSD.qml rename to Modules/OSD/OSD.qml index c2d5e3fb..0063d5d4 100644 --- a/Modules/OSD/BrightnessOSD.qml +++ b/Modules/OSD/OSD.qml @@ -11,20 +11,78 @@ Loader { id: windowLoader active: false + // OSD Type enum + enum Type { + Volume, + Brightness + } + + property int osdType: OSD.Type.Volume readonly property real scaling: ScalingService.getScreenScale(Quickshell.screens[0]) + + // Volume properties + readonly property real currentVolume: AudioService.volume + readonly property bool isMuted: AudioService.muted + property bool firstVolumeReceived: false + property bool firstMuteReceived: false + + // Brightness properties readonly property real currentBrightness: { if (BrightnessService.monitors.length > 0) { return BrightnessService.monitors[0].brightness || 0 } return 0 } - - // Used to avoid showing OSD on Quickshell startup property bool firstBrightnessReceived: false + // Get appropriate icon based on OSD type function getIcon() { - var brightness = currentBrightness - return brightness <= 0.5 ? "brightness-low" : "brightness-high" + if (osdType === OSD.Type.Volume) { + if (AudioService.muted) { + return "volume-mute" + } + return (AudioService.volume <= Number.EPSILON) ? "volume-zero" : (AudioService.volume <= 0.5) ? "volume-low" : "volume-high" + } else { + // Brightness + var brightness = currentBrightness + return brightness <= 0.5 ? "brightness-low" : "brightness-high" + } + } + + // Get current value (0-1 range) + function getCurrentValue() { + if (osdType === OSD.Type.Volume) { + return isMuted ? 0 : currentVolume + } else { + return currentBrightness + } + } + + // Get display percentage + function getDisplayPercentage() { + if (osdType === OSD.Type.Volume) { + return isMuted ? "0%" : Math.round(currentVolume * 100) + "%" + } else { + return Math.round(currentBrightness * 100) + "%" + } + } + + // Get progress bar color + function getProgressColor() { + if (osdType === OSD.Type.Volume) { + return isMuted ? Color.mError : Color.mPrimary + } else { + return Color.mPrimary + } + } + + // Get icon color + function getIconColor() { + if (osdType === OSD.Type.Volume) { + return isMuted ? Color.mError : Color.mOnSurface + } else { + return Color.mOnSurface + } } sourceComponent: PanelWindow { @@ -97,7 +155,7 @@ Loader { NIcon { icon: windowLoader.getIcon() - color: Color.mOnSurface + color: windowLoader.getIconColor() font.pointSize: Style.fontSizeXL * windowLoader.scaling Layout.alignment: Qt.AlignVCenter } @@ -117,9 +175,9 @@ Loader { anchors.left: parent.left anchors.top: parent.top anchors.bottom: parent.bottom - width: parent.width * windowLoader.currentBrightness + width: parent.width * Math.min(1.0, windowLoader.getCurrentValue()) radius: parent.radius - color: Color.mPrimary + color: windowLoader.getProgressColor() Behavior on width { NumberAnimation { @@ -127,11 +185,17 @@ Loader { easing.type: Easing.OutCubic } } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } } } NText { - text: Math.round(windowLoader.currentBrightness * 100) + "%" + text: windowLoader.getDisplayPercentage() color: Color.mOnSurfaceVariant font.pointSize: Style.fontSizeS * windowLoader.scaling Layout.alignment: Qt.AlignVCenter @@ -158,6 +222,14 @@ Loader { windowLoader.active = false }) } + + function hideImmediately() { + hideTimer.stop() + osdItem.opacity = 0 + osdItem.scale = 0.7 + osdItem.visible = false + windowLoader.active = false + } } function showOSD() { @@ -165,12 +237,34 @@ Loader { } } - // Monitor brightness changes from all monitors + // Volume change monitoring + Connections { + target: AudioService + enabled: osdType === OSD.Type.Volume + + function onVolumeChanged() { + if (!firstVolumeReceived) { + firstVolumeReceived = true + } else { + showOSD() + } + } + + function onMutedChanged() { + if (!firstMuteReceived) { + firstMuteReceived = true + } else { + showOSD() + } + } + } + + // Brightness change monitoring Connections { target: BrightnessService + enabled: osdType === OSD.Type.Brightness function onMonitorsChanged() { - // Connect to brightness changes for each monitor for (var i = 0; i < BrightnessService.monitors.length; i++) { let monitor = BrightnessService.monitors[i] monitor.brightnessUpdated.connect(windowLoader.onBrightnessChanged) @@ -178,17 +272,17 @@ Loader { } } - // Connect to existing monitors on component completion Component.onCompleted: { - for (var i = 0; i < BrightnessService.monitors.length; i++) { - let monitor = BrightnessService.monitors[i] - monitor.brightnessUpdated.connect(windowLoader.onBrightnessChanged) + if (osdType === OSD.Type.Brightness) { + for (var i = 0; i < BrightnessService.monitors.length; i++) { + let monitor = BrightnessService.monitors[i] + monitor.brightnessUpdated.connect(windowLoader.onBrightnessChanged) + } } } function onBrightnessChanged(newBrightness) { if (!firstBrightnessReceived) { - // Ignore the first brightness change on startup firstBrightnessReceived = true } else { showOSD() diff --git a/Modules/OSD/VolumeOSD.qml b/Modules/OSD/VolumeOSD.qml deleted file mode 100644 index 1c28291c..00000000 --- a/Modules/OSD/VolumeOSD.qml +++ /dev/null @@ -1,217 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import Quickshell -import Quickshell.Wayland -import qs.Commons -import qs.Services -import qs.Widgets - -Loader { - id: windowLoader - active: false - - readonly property real currentVolume: AudioService.volume - readonly property bool isMuted: AudioService.muted - readonly property real scaling: ScalingService.getScreenScale(Quickshell.screens[0]) - - // Used to avoid showing OSD on Quickshell startup - property bool firstVolumeReceived: false - property bool firstMuteReceived: false - - function getIcon() { - if (AudioService.muted) { - return "volume-mute" - } - return (AudioService.volume <= Number.EPSILON) ? "volume-zero" : (AudioService.volume <= 0.5) ? "volume-low" : "volume-high" - } - - sourceComponent: PanelWindow { - id: panel - - screen: Quickshell.screens[0] // Use primary screen - - anchors { - top: true - } - - implicitWidth: 320 * windowLoader.scaling - implicitHeight: osdItem.height - - // Set margins based on bar position - margins.top: { - switch (Settings.data.bar.position) { - case "top": - return (Style.barHeight + Style.marginS) * windowLoader.scaling + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL * windowLoader.scaling : 0) - default: - return Style.marginL * windowLoader.scaling - } - } - - color: Color.transparent - - WlrLayershell.keyboardFocus: WlrKeyboardFocus.None - exclusionMode: PanelWindow.ExclusionMode.Ignore - - Rectangle { - id: osdItem - - width: parent.width - height: Math.round(contentLayout.implicitHeight + Style.marginL * 2 * windowLoader.scaling) - radius: Style.radiusL * windowLoader.scaling - color: Color.mSurface - border.color: Color.mOutline - border.width: Math.max(2, Style.borderM * windowLoader.scaling) - visible: false - opacity: 0 - scale: 0.7 - - anchors.horizontalCenter: parent.horizontalCenter - - Behavior on opacity { - NumberAnimation { - duration: Style.animationFast - easing.type: Easing.OutCubic - } - } - - Behavior on scale { - NumberAnimation { - duration: Style.animationFast - easing.type: Easing.OutCubic - } - } - - Timer { - id: hideTimer - interval: 2000 - onTriggered: osdItem.hide() - } - - RowLayout { - id: contentLayout - anchors.fill: parent - anchors.margins: Style.marginM * windowLoader.scaling - spacing: Style.marginM * windowLoader.scaling - - NIcon { - icon: windowLoader.getIcon() - color: windowLoader.isMuted ? Color.mError : Color.mOnSurface - font.pointSize: Style.fontSizeXL * windowLoader.scaling - Layout.alignment: Qt.AlignVCenter - } - - RowLayout { - Layout.fillWidth: true - Layout.alignment: Qt.AlignVCenter - spacing: Style.marginXS * windowLoader.scaling - - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: Math.round(6 * windowLoader.scaling) - radius: Math.round(3 * windowLoader.scaling) - color: Color.mSurfaceVariant - - Rectangle { - anchors.left: parent.left - anchors.top: parent.top - anchors.bottom: parent.bottom - width: parent.width * (windowLoader.isMuted ? 0 : Math.min(1.0, windowLoader.currentVolume)) - radius: parent.radius - color: windowLoader.isMuted ? Color.mError : Color.mPrimary - - Behavior on width { - NumberAnimation { - duration: Style.animationFast - easing.type: Easing.OutCubic - } - } - - Behavior on color { - ColorAnimation { - duration: Style.animationFast - } - } - } - } - - NText { - text: windowLoader.isMuted ? "0%" : Math.round(windowLoader.currentVolume * 100) + "%" - color: Color.mOnSurfaceVariant - font.pointSize: Style.fontSizeS * windowLoader.scaling - Layout.alignment: Qt.AlignVCenter - Layout.minimumWidth: Math.round(32 * windowLoader.scaling) - } - } - } - - function show() { - hideTimer.stop() - osdItem.visible = true - osdItem.opacity = 1 - osdItem.scale = 1.0 - hideTimer.start() - } - - function hide() { - hideTimer.stop() - osdItem.opacity = 0 - osdItem.scale = 0.7 - - Qt.callLater(function () { - osdItem.visible = false - windowLoader.active = false - }) - } - } - - function showOSD() { - osdItem.show() - } - } - - // Monitor volume changes - Connections { - target: AudioService - - function onVolumeChanged() { - if (!firstVolumeReceived) { - // Ignore the first volume change on startup - firstVolumeReceived = true - } else { - showOSD() - } - } - - function onMutedChanged() { - if (!firstMuteReceived) { - // Ignore the first mute state change on startup - firstMuteReceived = true - } else { - showOSD() - } - } - } - - // Signal to coordinate with other OSDs - signal osdShowing - - function showOSD() { - // Check if OSD is enabled in settings - if (!Settings.data.general.showOSD) { - return - } - - osdShowing() // Notify other OSDs to hide - windowLoader.active = true - if (windowLoader.item) { - windowLoader.item.showOSD() - } - } - - function hideOSD() { - if (windowLoader.item) { - windowLoader.item.osdItem.hideImmediately() - } - } -} diff --git a/Modules/Settings/Tabs/GeneralTab.qml b/Modules/Settings/Tabs/GeneralTab.qml index add48277..0e0d8932 100644 --- a/Modules/Settings/Tabs/GeneralTab.qml +++ b/Modules/Settings/Tabs/GeneralTab.qml @@ -76,13 +76,6 @@ ColumnLayout { onToggled: checked => Settings.data.general.dimDesktop = checked } - NToggle { - label: "Show volume and brightness OSD" - description: "Display on-screen notifications when adjusting volume or brightness." - checked: Settings.data.general.showOSD - onToggled: checked => Settings.data.general.showOSD = checked - } - ColumnLayout { spacing: Style.marginXXS * scaling Layout.fillWidth: true diff --git a/Modules/Settings/Tabs/NotificationsTab.qml b/Modules/Settings/Tabs/NotificationsTab.qml index 64093d49..be404a08 100644 --- a/Modules/Settings/Tabs/NotificationsTab.qml +++ b/Modules/Settings/Tabs/NotificationsTab.qml @@ -39,6 +39,13 @@ ColumnLayout { onToggled: checked => Settings.data.notifications.doNotDisturb = checked } + NToggle { + label: "Enable on screen display" + description: "Show volume and brightness changes in real-time." + checked: Settings.data.general.showOSD + onToggled: checked => Settings.data.general.showOSD = checked + } + NComboBox { label: "Location" description: "Where notifications appear on screen." diff --git a/shell.qml b/shell.qml index cdf774f2..b7418b24 100644 --- a/shell.qml +++ b/shell.qml @@ -61,15 +61,17 @@ ShellRoot { ToastOverlay {} // OSD overlays for volume and brightness - VolumeOSD { + OSD { id: volumeOSD objectName: "volumeOSD" + osdType: OSD.Type.Volume onOsdShowing: brightnessOSD.hideOSD() } - BrightnessOSD { + OSD { id: brightnessOSD objectName: "brightnessOSD" + osdType: OSD.Type.Brightness onOsdShowing: volumeOSD.hideOSD() }