diff options
Diffstat (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD')
9 files changed, 1316 insertions, 0 deletions
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD/AudioOutputOSD.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD/AudioOutputOSD.qml new file mode 100644 index 0000000..1bea1e5 --- /dev/null +++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD/AudioOutputOSD.qml @@ -0,0 +1,67 @@ +import QtQuick +import qs.Common +import qs.Services +import qs.Widgets + +DankOSD { + id: root + + property string deviceName: "" + property string deviceIcon: "speaker" + + osdWidth: Math.min(Math.max(120, Theme.iconSize + textMetrics.width + Theme.spacingS * 4), Screen.width - Theme.spacingM * 2) + osdHeight: 40 + Theme.spacingS * 2 + autoHideInterval: 2500 + enableMouseInteraction: false + + TextMetrics { + id: textMetrics + font.pixelSize: Theme.fontSizeMedium + font.weight: Font.Medium + font.family: Theme.fontFamily + text: root.deviceName + } + + Connections { + target: AudioService + + function onAudioOutputCycled(name, icon) { + if (!SettingsData.osdAudioOutputEnabled) + return; + root.deviceName = name; + root.deviceIcon = icon; + root.show(); + } + } + + content: Item { + property int gap: Theme.spacingS + + anchors.centerIn: parent + width: parent.width - Theme.spacingS * 2 + height: 40 + + DankIcon { + id: iconItem + width: Theme.iconSize + height: Theme.iconSize + x: parent.gap + anchors.verticalCenter: parent.verticalCenter + name: root.deviceIcon + size: Theme.iconSize + color: Theme.primary + } + + StyledText { + id: textItem + x: parent.gap * 2 + Theme.iconSize + width: parent.width - Theme.iconSize - parent.gap * 3 + anchors.verticalCenter: parent.verticalCenter + text: root.deviceName + font.pixelSize: Theme.fontSizeMedium + font.weight: Font.Medium + color: Theme.surfaceText + elide: Text.ElideRight + } + } +} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD/BrightnessOSD.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD/BrightnessOSD.qml new file mode 100644 index 0000000..7f83578 --- /dev/null +++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD/BrightnessOSD.qml @@ -0,0 +1,274 @@ +import QtQuick +import qs.Common +import qs.Services +import qs.Widgets + +DankOSD { + id: root + + readonly property bool useVertical: isVerticalLayout + property int _displayBrightness: 0 + + function _syncBrightness() { + _displayBrightness = DisplayService.brightnessLevel; + } + + osdWidth: useVertical ? (40 + Theme.spacingS * 2) : Math.min(260, Screen.width - Theme.spacingM * 2) + osdHeight: useVertical ? Math.min(260, Screen.height - Theme.spacingM * 2) : (40 + Theme.spacingS * 2) + autoHideInterval: 3000 + enableMouseInteraction: true + + Connections { + target: DisplayService + function onBrightnessChanged(showOsd) { + root._syncBrightness(); + if (showOsd && SettingsData.osdBrightnessEnabled) + root.show(); + } + } + + content: Loader { + anchors.fill: parent + sourceComponent: useVertical ? verticalContent : horizontalContent + } + + Component { + id: horizontalContent + + Item { + property int gap: Theme.spacingS + + anchors.centerIn: parent + width: parent.width - Theme.spacingS * 2 + height: 40 + + Rectangle { + width: Theme.iconSize + height: Theme.iconSize + radius: Theme.iconSize / 2 + color: "transparent" + x: parent.gap + anchors.verticalCenter: parent.verticalCenter + + DankIcon { + anchors.centerIn: parent + name: { + const deviceInfo = DisplayService.getCurrentDeviceInfo(); + if (!deviceInfo || deviceInfo.class === "backlight" || deviceInfo.class === "ddc") + return "brightness_medium"; + if (deviceInfo.name.includes("kbd")) + return "keyboard"; + return "lightbulb"; + } + size: Theme.iconSize + color: Theme.primary + } + } + + DankSlider { + id: brightnessSlider + + width: parent.width - Theme.iconSize - parent.gap * 3 + height: 40 + x: parent.gap * 2 + Theme.iconSize + anchors.verticalCenter: parent.verticalCenter + minimum: { + const deviceInfo = DisplayService.getCurrentDeviceInfo(); + if (!deviceInfo) + return 1; + if (SessionData.getBrightnessExponential(deviceInfo.id)) + return 1; + return (deviceInfo.class === "backlight" || deviceInfo.class === "ddc") ? 1 : 0; + } + maximum: { + const deviceInfo = DisplayService.getCurrentDeviceInfo(); + if (!deviceInfo) + return 100; + if (SessionData.getBrightnessExponential(deviceInfo.id)) + return 100; + return deviceInfo.displayMax || 100; + } + enabled: DisplayService.brightnessAvailable + showValue: true + unit: { + const deviceInfo = DisplayService.getCurrentDeviceInfo(); + if (!deviceInfo) + return "%"; + if (SessionData.getBrightnessExponential(deviceInfo.id)) + return "%"; + return deviceInfo.class === "ddc" ? "" : "%"; + } + thumbOutlineColor: Theme.surfaceContainer + alwaysShowValue: SettingsData.osdAlwaysShowValue + + onSliderValueChanged: newValue => { + if (!DisplayService.brightnessAvailable) + return; + DisplayService.setBrightness(newValue, DisplayService.lastIpcDevice, true); + resetHideTimer(); + } + + onContainsMouseChanged: setChildHovered(containsMouse) + + Binding on value { + value: root._displayBrightness + when: !brightnessSlider.isDragging + } + } + } + } + + Component { + id: verticalContent + + Item { + anchors.fill: parent + property int gap: Theme.spacingS + + Rectangle { + width: Theme.iconSize + height: Theme.iconSize + radius: Theme.iconSize / 2 + color: "transparent" + anchors.horizontalCenter: parent.horizontalCenter + y: gap + + DankIcon { + anchors.centerIn: parent + name: { + const deviceInfo = DisplayService.getCurrentDeviceInfo(); + if (!deviceInfo || deviceInfo.class === "backlight" || deviceInfo.class === "ddc") + return "brightness_medium"; + if (deviceInfo.name.includes("kbd")) + return "keyboard"; + return "lightbulb"; + } + size: Theme.iconSize + color: Theme.primary + } + } + + Item { + id: vertSlider + width: 12 + height: parent.height - Theme.iconSize - gap * 3 - 24 + anchors.horizontalCenter: parent.horizontalCenter + y: gap * 2 + Theme.iconSize + + property bool dragging: false + property int value: 50 + + Binding on value { + value: root._displayBrightness + when: !vertSlider.dragging + } + + readonly property int minimum: { + const deviceInfo = DisplayService.getCurrentDeviceInfo(); + if (!deviceInfo) + return 1; + if (SessionData.getBrightnessExponential(deviceInfo.id)) + return 1; + return (deviceInfo.class === "backlight" || deviceInfo.class === "ddc") ? 1 : 0; + } + + readonly property int maximum: { + const deviceInfo = DisplayService.getCurrentDeviceInfo(); + if (!deviceInfo) + return 100; + if (SessionData.getBrightnessExponential(deviceInfo.id)) + return 100; + return deviceInfo.displayMax || 100; + } + + Rectangle { + id: vertTrack + width: parent.width + height: parent.height + anchors.centerIn: parent + color: Theme.outline + radius: Theme.cornerRadius + } + + Rectangle { + id: vertFill + width: parent.width + height: { + const ratio = (vertSlider.value - vertSlider.minimum) / (vertSlider.maximum - vertSlider.minimum); + return ratio * parent.height; + } + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + color: Theme.primary + radius: Theme.cornerRadius + } + + Rectangle { + id: vertHandle + width: 24 + height: 8 + radius: Theme.cornerRadius + y: { + const ratio = (vertSlider.value - vertSlider.minimum) / (vertSlider.maximum - vertSlider.minimum); + const travel = parent.height - height; + return Math.max(0, Math.min(travel, travel * (1 - ratio))); + } + anchors.horizontalCenter: parent.horizontalCenter + color: Theme.primary + border.width: 3 + border.color: Theme.surfaceContainer + } + + MouseArea { + id: vertSliderArea + anchors.fill: parent + anchors.margins: -12 + enabled: DisplayService.brightnessAvailable + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + + onContainsMouseChanged: setChildHovered(containsMouse) + + onPressed: mouse => { + vertSlider.dragging = true; + updateBrightness(mouse); + } + + onReleased: vertSlider.dragging = false + + onPositionChanged: mouse => { + if (pressed) + updateBrightness(mouse); + } + + onClicked: mouse => updateBrightness(mouse) + + function updateBrightness(mouse) { + if (!DisplayService.brightnessAvailable) + return; + const ratio = 1.0 - (mouse.y / height); + const newValue = Math.round(vertSlider.minimum + ratio * (vertSlider.maximum - vertSlider.minimum)); + vertSlider.value = newValue; + DisplayService.setBrightness(newValue, DisplayService.lastIpcDevice, true); + resetHideTimer(); + } + } + } + + StyledText { + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottomMargin: gap + text: { + const deviceInfo = DisplayService.getCurrentDeviceInfo(); + const isExponential = deviceInfo ? SessionData.getBrightnessExponential(deviceInfo.id) : false; + const unit = (deviceInfo && deviceInfo.class === "ddc" && !isExponential) ? "" : "%"; + return vertSlider.value + unit; + } + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + visible: SettingsData.osdAlwaysShowValue + } + } + } +} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD/CapsLockOSD.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD/CapsLockOSD.qml new file mode 100644 index 0000000..b37a86b --- /dev/null +++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD/CapsLockOSD.qml @@ -0,0 +1,37 @@ +import QtQuick +import qs.Common +import qs.Services +import qs.Widgets + +DankOSD { + id: root + + osdWidth: Theme.iconSize + Theme.spacingS * 2 + osdHeight: Theme.iconSize + Theme.spacingS * 2 + autoHideInterval: 2000 + enableMouseInteraction: false + + property bool lastCapsLockState: false + + Connections { + target: DMSService + + function onCapsLockStateChanged() { + if (lastCapsLockState !== DMSService.capsLockState && SettingsData.osdCapsLockEnabled) { + root.show() + } + lastCapsLockState = DMSService.capsLockState + } + } + + Component.onCompleted: { + lastCapsLockState = DMSService.capsLockState + } + + content: DankIcon { + anchors.centerIn: parent + name: DMSService.capsLockState ? "shift_lock" : "shift_lock_off" + size: Theme.iconSize + color: Theme.primary + } +} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD/IdleInhibitorOSD.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD/IdleInhibitorOSD.qml new file mode 100644 index 0000000..e08c216 --- /dev/null +++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD/IdleInhibitorOSD.qml @@ -0,0 +1,29 @@ +import QtQuick +import qs.Common +import qs.Services +import qs.Widgets + +DankOSD { + id: root + + osdWidth: Theme.iconSize + Theme.spacingS * 2 + osdHeight: Theme.iconSize + Theme.spacingS * 2 + autoHideInterval: 2000 + enableMouseInteraction: false + + Connections { + target: SessionService + function onInhibitorChanged() { + if (SettingsData.osdIdleInhibitorEnabled) { + root.show() + } + } + } + + content: DankIcon { + anchors.centerIn: parent + name: SessionService.idleInhibited ? "motion_sensor_active" : "motion_sensor_idle" + size: Theme.iconSize + color: SessionService.idleInhibited ? Theme.primary : Theme.outline + } +} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD/MediaPlaybackOSD.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD/MediaPlaybackOSD.qml new file mode 100644 index 0000000..270a9d0 --- /dev/null +++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD/MediaPlaybackOSD.qml @@ -0,0 +1,319 @@ +import QtQuick +import QtQuick.Effects +import qs.Common +import qs.Services +import qs.Widgets +import Quickshell.Services.Mpris + +DankOSD { + id: root + + readonly property bool useVertical: isVerticalLayout + readonly property var player: MprisController.activePlayer + + osdWidth: useVertical ? (40 + Theme.spacingS * 2) : Math.min(280, Screen.width - Theme.spacingM * 2) + osdHeight: useVertical ? (Theme.iconSize * 2) : (40 + Theme.spacingS * 2) + autoHideInterval: 3000 + enableMouseInteraction: true + + property string _displayIcon: "music_note" + + function updatePlaybackIcon() { + if (!player) { + _displayIcon = "music_note"; + iconDebounce.stop(); + return; + } + let icon = "music_note"; + switch (player.playbackState) { + case MprisPlaybackState.Playing: + icon = "pause"; + break; + case MprisPlaybackState.Paused: + case MprisPlaybackState.Stopped: + icon = "play_arrow"; + break; + } + if (icon === _displayIcon) + return; + iconDebounce.pendingIcon = icon; + iconDebounce.restart(); + } + + function togglePlaying() { + if (player?.canTogglePlaying) { + player.togglePlaying(); + } + } + + property bool _pendingShow: false + + Timer { + id: iconDebounce + interval: 150 + property string pendingIcon: "music_note" + onTriggered: root._displayIcon = pendingIcon + } + + Image { + id: artPreloader + source: TrackArtService._bgArtSource + visible: false + asynchronous: true + cache: true + } + + onPlayerChanged: { + if (!player) { + _pendingShow = false; + hide(); + } + } + + Connections { + target: TrackArtService + function onLoadingChanged() { + if (TrackArtService.loading || !root._pendingShow) + return; + if (!TrackArtService._bgArtSource || artPreloader.status === Image.Ready) { + root._pendingShow = false; + root.show(); + } + } + } + + Connections { + target: artPreloader + function onStatusChanged() { + if (!root._pendingShow || TrackArtService.loading) + return; + switch (artPreloader.status) { + case Image.Ready: + case Image.Error: + root._pendingShow = false; + root.show(); + break; + } + } + } + + Connections { + target: player + + function handleUpdate() { + if (!root.player?.trackTitle) + return; + if (!SettingsData.osdMediaPlaybackEnabled) + return; + + root.updatePlaybackIcon(); + TrackArtService.loadArtwork(player.trackArtUrl); + + if (!player.trackArtUrl || player.trackArtUrl === "") { + root.show(); + return; + } + if (TrackArtService.loading) { + root._pendingShow = true; + return; + } + if (!TrackArtService._bgArtSource || artPreloader.status === Image.Ready) { + root.show(); + return; + } + root._pendingShow = true; + } + + function onTrackArtUrlChanged() { + TrackArtService.loadArtwork(player.trackArtUrl); + } + function onIsPlayingChanged() { + handleUpdate(); + } + function onTrackChanged() { + if (!useVertical) + handleUpdate(); + } + } + + content: Loader { + anchors.fill: parent + sourceComponent: useVertical ? verticalContent : horizontalContent + } + + Component { + id: horizontalContent + + Item { + property int gap: Theme.spacingS + + anchors.centerIn: parent + width: parent.width - Theme.spacingS * 2 + height: 40 + + MouseArea { + anchors.fill: parent + onClicked: root.hide() + } + + Item { + id: bgContainer + anchors.fill: parent + visible: TrackArtService._bgArtSource !== "" + + Image { + id: bgImage + anchors.centerIn: parent + width: Math.max(parent.width, parent.height) + height: width + source: TrackArtService._bgArtSource + fillMode: Image.PreserveAspectCrop + asynchronous: true + cache: true + visible: false + } + + Item { + id: blurredBg + anchors.fill: parent + visible: false + + MultiEffect { + anchors.centerIn: parent + width: bgImage.width + height: bgImage.height + source: bgImage + blurEnabled: true + blurMax: 64 + blur: 0.3 + saturation: -0.2 + brightness: -0.25 + } + } + + Rectangle { + id: bgMask + anchors.fill: parent + radius: Theme.cornerRadius + visible: false + layer.enabled: true + } + + MultiEffect { + anchors.fill: parent + source: blurredBg + maskEnabled: true + maskSource: bgMask + maskThresholdMin: 0.5 + maskSpreadAtMin: 1.0 + opacity: 0.7 + } + + Rectangle { + anchors.fill: parent + radius: Theme.cornerRadius + color: Theme.surface + opacity: 0.3 + } + } + + Rectangle { + width: Theme.iconSize + height: Theme.iconSize + radius: Theme.iconSize / 2 + color: "transparent" + x: parent.gap + anchors.verticalCenter: parent.verticalCenter + + DankIcon { + anchors.centerIn: parent + name: root._displayIcon + size: Theme.iconSize + color: playPauseButton.containsMouse ? Theme.primary : Theme.surfaceText + } + + MouseArea { + id: playPauseButton + + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + togglePlaying(); + root.hide(); + } + } + } + + Column { + x: parent.gap * 2 + Theme.iconSize + width: parent.width - Theme.iconSize - parent.gap * 3 + anchors.verticalCenter: parent.verticalCenter + spacing: 3 + + StyledText { + id: topText + width: parent.width + text: player ? `${player.trackTitle || I18n.tr("Unknown Title")}` : "" + font.pixelSize: Theme.fontSizeMedium + font.weight: Font.Medium + color: Theme.surfaceText + wrapMode: Text.NoWrap + elide: Text.ElideRight + } + + StyledText { + id: bottomText + width: parent.width + text: player ? ((player.trackArtist || I18n.tr("Unknown Artist")) + (player.trackAlbum ? ` • ${player.trackAlbum}` : "")) : "" + font.pixelSize: Theme.fontSizeSmall + font.weight: Font.Light + color: Theme.surfaceText + wrapMode: Text.NoWrap + elide: Text.ElideRight + } + } + } + } + + Component { + id: verticalContent + + Item { + property int gap: Theme.spacingS + + MouseArea { + anchors.fill: parent + onClicked: root.hide() + } + + Rectangle { + width: Theme.iconSize + height: Theme.iconSize + radius: Theme.iconSize / 2 + color: "transparent" + anchors.centerIn: parent + y: gap + + DankIcon { + anchors.centerIn: parent + name: root._displayIcon + size: Theme.iconSize + color: playPauseButtonVert.containsMouse ? Theme.primary : Theme.surfaceText + } + + MouseArea { + id: playPauseButtonVert + + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + togglePlaying(); + root.hide(); + } + } + } + } + } +} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD/MediaVolumeOSD.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD/MediaVolumeOSD.qml new file mode 100644 index 0000000..147d81e --- /dev/null +++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD/MediaVolumeOSD.qml @@ -0,0 +1,264 @@ +import QtQuick +import qs.Common +import qs.Services +import qs.Widgets + +DankOSD { + id: root + + readonly property bool useVertical: isVerticalLayout + readonly property var player: MprisController.activePlayer + readonly property bool volumeSupported: player?.volumeSupported ?? false + property bool _suppressNewPlayer: false + property int _displayVolume: 0 + + function _syncVolume() { + if (!player) + return; + _displayVolume = Math.min(100, Math.round(player.volume * 100)); + } + + onPlayerChanged: { + _suppressNewPlayer = true; + _suppressTimer.restart(); + _syncVolume(); + } + + Timer { + id: _suppressTimer + interval: 2000 + onTriggered: _suppressNewPlayer = false + } + + osdWidth: useVertical ? (40 + Theme.spacingS * 2) : Math.min(260, Screen.width - Theme.spacingM * 2) + osdHeight: useVertical ? Math.min(260, Screen.height - Theme.spacingM * 2) : (40 + Theme.spacingS * 2) + autoHideInterval: 3000 + enableMouseInteraction: true + + function getVolumeIcon(volume) { + if (!player) + return "music_note"; + if (volume === 0) + return "music_off"; + return "music_note"; + } + + function toggleMute() { + if (!player) + return; + player.volume = player.volume > 0 ? 0 : 1; + } + + function setVolume(volumePercent) { + if (!player) + return; + player.volume = volumePercent / 100; + resetHideTimer(); + } + + Connections { + target: player + + function onVolumeChanged() { + root._syncVolume(); + if (SettingsData.osdMediaVolumeEnabled && volumeSupported && !_suppressNewPlayer) + root.show(); + } + } + + content: Loader { + anchors.fill: parent + sourceComponent: useVertical ? verticalContent : horizontalContent + } + + Component { + id: horizontalContent + + Item { + property int gap: Theme.spacingS + + anchors.centerIn: parent + width: parent.width - Theme.spacingS * 2 + height: 40 + + Rectangle { + width: Theme.iconSize + height: Theme.iconSize + radius: Theme.iconSize / 2 + color: "transparent" + x: parent.gap + anchors.verticalCenter: parent.verticalCenter + + DankIcon { + anchors.centerIn: parent + name: getVolumeIcon(player?.volume ?? 0) + size: Theme.iconSize + color: muteButton.containsMouse ? Theme.primary : Theme.surfaceText + } + + MouseArea { + id: muteButton + + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: toggleMute() + onContainsMouseChanged: setChildHovered(containsMouse || volumeSlider.containsMouse) + } + } + + DankSlider { + id: volumeSlider + + width: parent.width - Theme.iconSize - parent.gap * 3 + height: 40 + x: parent.gap * 2 + Theme.iconSize + anchors.verticalCenter: parent.verticalCenter + minimum: 0 + maximum: 100 + enabled: volumeSupported + showValue: true + unit: "%" + thumbOutlineColor: Theme.surfaceContainer + valueOverride: root._displayVolume + alwaysShowValue: SettingsData.osdAlwaysShowValue + + Component.onCompleted: { + root._syncVolume(); + value = root._displayVolume; + } + + onSliderValueChanged: newValue => setVolume(newValue) + + onContainsMouseChanged: setChildHovered(containsMouse || muteButton.containsMouse) + + Binding on value { + value: root._displayVolume + when: !volumeSlider.pressed + } + } + } + } + + Component { + id: verticalContent + + Item { + anchors.fill: parent + property int gap: Theme.spacingS + + Rectangle { + width: Theme.iconSize + height: Theme.iconSize + radius: Theme.iconSize / 2 + color: "transparent" + anchors.horizontalCenter: parent.horizontalCenter + y: gap + + DankIcon { + anchors.centerIn: parent + name: getVolumeIcon(player?.volume ?? 0) + size: Theme.iconSize + color: muteButtonVert.containsMouse ? Theme.primary : Theme.surfaceText + } + + MouseArea { + id: muteButtonVert + + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: toggleMute() + onContainsMouseChanged: setChildHovered(containsMouse || vertSliderArea.containsMouse) + } + } + + Item { + id: vertSlider + width: 12 + height: parent.height - Theme.iconSize - gap * 3 - 24 + anchors.horizontalCenter: parent.horizontalCenter + y: gap * 2 + Theme.iconSize + + property bool dragging: false + property int value: root._displayVolume + + Rectangle { + id: vertTrack + width: parent.width + height: parent.height + anchors.centerIn: parent + color: Theme.outline + radius: Theme.cornerRadius + } + + Rectangle { + id: vertFill + width: parent.width + height: (vertSlider.value / 100) * parent.height + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + color: Theme.primary + radius: Theme.cornerRadius + } + + Rectangle { + id: vertHandle + width: 24 + height: 8 + radius: Theme.cornerRadius + y: { + const ratio = vertSlider.value / 100; + const travel = parent.height - height; + return Math.max(0, Math.min(travel, travel * (1 - ratio))); + } + anchors.horizontalCenter: parent.horizontalCenter + color: Theme.primary + border.width: 3 + border.color: Theme.surfaceContainer + } + + MouseArea { + id: vertSliderArea + anchors.fill: parent + anchors.margins: -12 + enabled: volumeSupported + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + + onContainsMouseChanged: setChildHovered(containsMouse || muteButtonVert.containsMouse) + + onPressed: mouse => { + vertSlider.dragging = true; + updateVolume(mouse); + } + + onReleased: vertSlider.dragging = false + + onPositionChanged: mouse => { + if (pressed) + updateVolume(mouse); + } + + onClicked: mouse => updateVolume(mouse) + + function updateVolume(mouse) { + const ratio = 1.0 - (mouse.y / height); + const volume = Math.max(0, Math.min(100, Math.round(ratio * 100))); + setVolume(volume); + } + } + } + + StyledText { + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottomMargin: gap + text: vertSlider.value + "%" + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + visible: SettingsData.osdAlwaysShowValue + } + } + } +} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD/MicMuteOSD.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD/MicMuteOSD.qml new file mode 100644 index 0000000..71162ac --- /dev/null +++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD/MicMuteOSD.qml @@ -0,0 +1,29 @@ +import QtQuick +import qs.Common +import qs.Services +import qs.Widgets + +DankOSD { + id: root + + osdWidth: Theme.iconSize + Theme.spacingS * 2 + osdHeight: Theme.iconSize + Theme.spacingS * 2 + autoHideInterval: 2000 + enableMouseInteraction: false + + Connections { + target: AudioService + function onMicMuteChanged() { + if (SettingsData.osdMicMuteEnabled) { + root.show() + } + } + } + + content: DankIcon { + anchors.centerIn: parent + name: AudioService.source && AudioService.source.audio && AudioService.source.audio.muted ? "mic_off" : "mic" + size: Theme.iconSize + color: AudioService.source && AudioService.source.audio && AudioService.source.audio.muted ? Theme.error : Theme.primary + } +} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD/PowerProfileOSD.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD/PowerProfileOSD.qml new file mode 100644 index 0000000..11b008f --- /dev/null +++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD/PowerProfileOSD.qml @@ -0,0 +1,43 @@ +import QtQuick +import Quickshell.Services.UPower +import qs.Common +import qs.Services +import qs.Widgets + +DankOSD { + id: root + + property int currentProfile: 0 + property string profileIcon: "settings" + + osdWidth: Theme.iconSize + Theme.spacingS * 2 + osdHeight: Theme.iconSize + Theme.spacingS * 2 + autoHideInterval: 2000 + enableMouseInteraction: false + + Connections { + target: PowerProfileWatcher + + function onProfileChanged(profile) { + if (SettingsData.osdPowerProfileEnabled) { + root.currentProfile = profile; + root.profileIcon = Theme.getPowerProfileIcon(profile); + root.show(); + } + } + } + + Component.onCompleted: { + if (SettingsData.osdPowerProfileEnabled && typeof PowerProfileWatcher !== "undefined") { + root.currentProfile = PowerProfileWatcher.currentProfile; + root.profileIcon = Theme.getPowerProfileIcon(PowerProfileWatcher.currentProfile); + } + } + + content: DankIcon { + anchors.centerIn: parent + name: root.profileIcon + size: Theme.iconSize + color: Theme.primary + } +} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD/VolumeOSD.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD/VolumeOSD.qml new file mode 100644 index 0000000..89c07b7 --- /dev/null +++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD/VolumeOSD.qml @@ -0,0 +1,254 @@ +import QtQuick +import qs.Common +import qs.Services +import qs.Widgets + +DankOSD { + id: root + + readonly property bool useVertical: isVerticalLayout + property int _displayVolume: 0 + + function _syncVolume() { + if (!AudioService.sink?.audio) + return; + _displayVolume = Math.min(AudioService.sinkMaxVolume, Math.round(AudioService.sink.audio.volume * 100)); + } + + osdWidth: useVertical ? (40 + Theme.spacingS * 2) : Math.min(260, Screen.width - Theme.spacingM * 2) + osdHeight: useVertical ? Math.min(260, Screen.height - Theme.spacingM * 2) : (40 + Theme.spacingS * 2) + autoHideInterval: 3000 + enableMouseInteraction: true + + Connections { + target: AudioService.sink?.audio ?? null + + function onVolumeChanged() { + root._syncVolume(); + if (SettingsData.osdVolumeEnabled) + root.show(); + } + + function onMutedChanged() { + if (SettingsData.osdVolumeEnabled) + root.show(); + } + } + + Connections { + target: AudioService + + function onSinkChanged() { + root._syncVolume(); + if (root.shouldBeVisible && SettingsData.osdVolumeEnabled) + root.show(); + } + } + + content: Loader { + anchors.fill: parent + sourceComponent: useVertical ? verticalContent : horizontalContent + } + + Component { + id: horizontalContent + + Item { + property int gap: Theme.spacingS + + anchors.centerIn: parent + width: parent.width - Theme.spacingS * 2 + height: 40 + + Rectangle { + width: Theme.iconSize + height: Theme.iconSize + radius: Theme.iconSize / 2 + color: "transparent" + x: parent.gap + anchors.verticalCenter: parent.verticalCenter + + DankIcon { + anchors.centerIn: parent + name: AudioService.sink?.audio?.muted ? "volume_off" : "volume_up" + size: Theme.iconSize + color: muteButton.containsMouse ? Theme.primary : Theme.surfaceText + } + + MouseArea { + id: muteButton + + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: AudioService.toggleMute() + onContainsMouseChanged: setChildHovered(containsMouse || volumeSlider.containsMouse) + } + } + + DankSlider { + id: volumeSlider + + width: parent.width - Theme.iconSize - parent.gap * 3 + height: 40 + x: parent.gap * 2 + Theme.iconSize + anchors.verticalCenter: parent.verticalCenter + minimum: 0 + maximum: AudioService.sinkMaxVolume + enabled: AudioService.sink?.audio ?? false + showValue: true + unit: "%" + thumbOutlineColor: Theme.surfaceContainer + valueOverride: root._displayVolume + alwaysShowValue: SettingsData.osdAlwaysShowValue + + Component.onCompleted: { + root._syncVolume(); + value = root._displayVolume; + } + + onSliderValueChanged: newValue => { + if (!AudioService.sink?.audio) + return; + SessionData.suppressOSDTemporarily(); + AudioService.sink.audio.volume = newValue / 100; + resetHideTimer(); + } + + onContainsMouseChanged: setChildHovered(containsMouse || muteButton.containsMouse) + + Binding on value { + value: root._displayVolume + when: !volumeSlider.pressed + } + } + } + } + + Component { + id: verticalContent + + Item { + anchors.fill: parent + property int gap: Theme.spacingS + + Rectangle { + width: Theme.iconSize + height: Theme.iconSize + radius: Theme.iconSize / 2 + color: "transparent" + anchors.horizontalCenter: parent.horizontalCenter + y: gap + + DankIcon { + anchors.centerIn: parent + name: AudioService.sink?.audio?.muted ? "volume_off" : "volume_up" + size: Theme.iconSize + color: muteButtonVert.containsMouse ? Theme.primary : Theme.surfaceText + } + + MouseArea { + id: muteButtonVert + + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: AudioService.toggleMute() + onContainsMouseChanged: setChildHovered(containsMouse || vertSliderArea.containsMouse) + } + } + + Item { + id: vertSlider + width: 12 + height: parent.height - Theme.iconSize - gap * 3 - 24 + anchors.horizontalCenter: parent.horizontalCenter + y: gap * 2 + Theme.iconSize + + property bool dragging: false + property int value: root._displayVolume + + Rectangle { + id: vertTrack + width: parent.width + height: parent.height + anchors.centerIn: parent + color: Theme.outline + radius: Theme.cornerRadius + } + + Rectangle { + id: vertFill + width: parent.width + height: (vertSlider.value / AudioService.sinkMaxVolume) * parent.height + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + color: Theme.primary + radius: Theme.cornerRadius + } + + Rectangle { + id: vertHandle + width: 24 + height: 8 + radius: Theme.cornerRadius + y: { + const ratio = vertSlider.value / AudioService.sinkMaxVolume; + const travel = parent.height - height; + return Math.max(0, Math.min(travel, travel * (1 - ratio))); + } + anchors.horizontalCenter: parent.horizontalCenter + color: Theme.primary + border.width: 3 + border.color: Theme.surfaceContainer + } + + MouseArea { + id: vertSliderArea + anchors.fill: parent + anchors.margins: -12 + enabled: AudioService.sink?.audio ?? false + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + + onContainsMouseChanged: setChildHovered(containsMouse || muteButtonVert.containsMouse) + + onPressed: mouse => { + vertSlider.dragging = true; + updateVolume(mouse); + } + + onReleased: vertSlider.dragging = false + + onPositionChanged: mouse => { + if (pressed) + updateVolume(mouse); + } + + onClicked: mouse => updateVolume(mouse) + + function updateVolume(mouse) { + if (!AudioService.sink?.audio) + return; + const maxVol = AudioService.sinkMaxVolume; + const ratio = 1.0 - (mouse.y / height); + const volume = Math.max(0, Math.min(maxVol, Math.round(ratio * maxVol))); + SessionData.suppressOSDTemporarily(); + AudioService.sink.audio.volume = volume / 100; + resetHideTimer(); + } + } + } + + StyledText { + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottomMargin: gap + text: vertSlider.value + "%" + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + visible: SettingsData.osdAlwaysShowValue + } + } + } +} |