summaryrefslogtreecommitdiff
path: root/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD
diff options
context:
space:
mode:
authorAlexanderCurl <alexc@alexc.hu>2026-04-18 17:46:06 +0100
committerAlexanderCurl <alexc@alexc.hu>2026-04-18 17:46:06 +0100
commit70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69 (patch)
treeab1123d4067c1b086dd6faa7ee4ea643236b565a /raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD
parent5d94c0a7d44a2255b81815a52a7056a94a39842d (diff)
downloadRaveOS-PKGBUILD-70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69.tar.gz
RaveOS-PKGBUILD-70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69.zip
Replaced file structures for themes in the correct format
Diffstat (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD')
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD/AudioOutputOSD.qml67
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD/BrightnessOSD.qml274
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD/CapsLockOSD.qml37
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD/IdleInhibitorOSD.qml29
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD/MediaPlaybackOSD.qml319
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD/MediaVolumeOSD.qml264
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD/MicMuteOSD.qml29
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD/PowerProfileOSD.qml43
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/OSD/VolumeOSD.qml254
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
+ }
+ }
+ }
+}