diff options
| author | nippy <you@example.com> | 2026-04-18 13:49:56 +0200 |
|---|---|---|
| committer | nippy <you@example.com> | 2026-04-18 13:49:56 +0200 |
| commit | 5d94c0a7d44a2255b81815a52a7056a94a39842d (patch) | |
| tree | 759bdea9645c6a62f9e1e4c001f7d81cccd120d2 /raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/DankDash/MediaDropdownOverlay.qml | |
| parent | e79cdf210b267f21a186255ce1a4d50029439d54 (diff) | |
| download | RaveOS-PKGBUILD-5d94c0a7d44a2255b81815a52a7056a94a39842d.tar.gz RaveOS-PKGBUILD-5d94c0a7d44a2255b81815a52a7056a94a39842d.zip | |
update Raveos themes
Diffstat (limited to 'raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/DankDash/MediaDropdownOverlay.qml')
| -rw-r--r-- | raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/DankDash/MediaDropdownOverlay.qml | 513 |
1 files changed, 513 insertions, 0 deletions
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/DankDash/MediaDropdownOverlay.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/DankDash/MediaDropdownOverlay.qml new file mode 100644 index 0000000..35dfe97 --- /dev/null +++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/DankDash/MediaDropdownOverlay.qml @@ -0,0 +1,513 @@ +import QtQuick +import QtQuick.Effects +import Quickshell.Services.Pipewire +import qs.Common +import qs.Services +import qs.Widgets + +Item { + id: root + + LayoutMirroring.enabled: I18n.isRtl + LayoutMirroring.childrenInherit: true + + property int dropdownType: 0 + property var activePlayer: null + property var allPlayers: [] + property point anchorPos: Qt.point(0, 0) + property bool isRightEdge: false + + property bool __isChromeBrowser: { + if (!activePlayer?.identity) + return false; + const id = activePlayer.identity.toLowerCase(); + return id.includes("chrome") || id.includes("chromium"); + } + property bool usePlayerVolume: activePlayer && activePlayer.volumeSupported && !__isChromeBrowser + property real currentVolume: usePlayerVolume ? activePlayer.volume : (AudioService.sink?.audio?.volume ?? 0) + property bool volumeAvailable: (activePlayer && activePlayer.volumeSupported && !__isChromeBrowser) || (AudioService.sink && AudioService.sink.audio) + property var availableDevices: { + const hidden = SessionData.hiddenOutputDeviceNames ?? []; + return Pipewire.nodes.values.filter(node => { + if (!node.audio || !node.isSink || node.isStream) + return false; + return !hidden.includes(node.name); + }); + } + + signal closeRequested + signal deviceSelected(var device) + signal playerSelected(var player) + signal volumeChanged(real volume) + signal panelEntered + signal panelExited + + property int __volumeHoverCount: 0 + + function volumeAreaEntered() { + __volumeHoverCount++; + panelEntered(); + } + + function volumeAreaExited() { + __volumeHoverCount--; + Qt.callLater(() => { + if (__volumeHoverCount <= 0) + panelExited(); + }); + } + + Rectangle { + id: volumePanel + visible: dropdownType === 1 && volumeAvailable + width: 60 + height: 180 + x: isRightEdge ? anchorPos.x : anchorPos.x - width + y: anchorPos.y - height / 2 + radius: Theme.cornerRadius * 2 + color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) + border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3) + border.width: 1 + + opacity: dropdownType === 1 ? 1 : 0 + scale: dropdownType === 1 ? 1 : 0.96 + transformOrigin: isRightEdge ? Item.Left : Item.Right + + Behavior on opacity { + NumberAnimation { + duration: Theme.expressiveDurations.expressiveDefaultSpatial + easing.type: Easing.BezierSpline + easing.bezierCurve: Theme.expressiveCurves.expressiveDefaultSpatial + } + } + + Behavior on scale { + NumberAnimation { + duration: Theme.expressiveDurations.expressiveDefaultSpatial + easing.type: Easing.BezierSpline + easing.bezierCurve: Theme.expressiveCurves.expressiveDefaultSpatial + } + } + + ElevationShadow { + id: volumeShadowLayer + anchors.fill: parent + z: -1 + level: Theme.elevationLevel2 + fallbackOffset: 4 + targetRadius: volumePanel.radius + targetColor: volumePanel.color + borderColor: volumePanel.border.color + borderWidth: volumePanel.border.width + shadowOpacity: Theme.elevationLevel2 && Theme.elevationLevel2.alpha !== undefined ? Theme.elevationLevel2.alpha : 0.25 + shadowEnabled: Theme.elevationEnabled + } + + MouseArea { + anchors.fill: parent + anchors.margins: -12 + hoverEnabled: true + onEntered: volumeAreaEntered() + onExited: volumeAreaExited() + } + + Item { + anchors.fill: parent + anchors.margins: Theme.spacingS + + Item { + id: volumeSlider + width: parent.width * 0.5 + height: parent.height - Theme.spacingXL * 2 + anchors.top: parent.top + anchors.topMargin: Theme.spacingS + anchors.horizontalCenter: parent.horizontalCenter + + Rectangle { + width: parent.width + height: parent.height + anchors.centerIn: parent + color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) + radius: Theme.cornerRadius + } + + Rectangle { + width: parent.width + height: volumeAvailable ? (Math.min(1.0, currentVolume) * parent.height) : 0 + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + color: Theme.primary + bottomLeftRadius: Theme.cornerRadius + bottomRightRadius: Theme.cornerRadius + } + + Rectangle { + width: parent.width + 8 + height: 8 + radius: Theme.cornerRadius + y: { + const ratio = volumeAvailable ? Math.min(1.0, currentVolume) : 0; + 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: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 1.0) + } + + MouseArea { + anchors.fill: parent + anchors.margins: -12 + enabled: volumeAvailable + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + preventStealing: true + + onEntered: volumeAreaEntered() + onExited: volumeAreaExited() + onPressed: mouse => updateVolume(mouse) + onPositionChanged: mouse => { + if (pressed) + updateVolume(mouse); + } + onClicked: mouse => updateVolume(mouse) + + function updateVolume(mouse) { + if (!volumeAvailable) + return; + const ratio = 1.0 - (mouse.y / height); + const volume = Math.max(0, Math.min(1, ratio)); + root.volumeChanged(volume); + } + } + } + + StyledText { + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottomMargin: Theme.spacingL + text: volumeAvailable ? Math.round(currentVolume * 100) + "%" : "0%" + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + font.weight: Font.Medium + } + } + } + + Rectangle { + id: audioDevicesPanel + visible: dropdownType === 2 + width: 280 + height: Math.max(200, Math.min(280, availableDevices.length * 50 + 100)) + x: isRightEdge ? anchorPos.x : anchorPos.x - width + y: anchorPos.y - height / 2 + radius: Theme.cornerRadius * 2 + color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.98) + border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.6) + border.width: 2 + + opacity: dropdownType === 2 ? 1 : 0 + scale: dropdownType === 2 ? 1 : 0.96 + transformOrigin: isRightEdge ? Item.Left : Item.Right + + Behavior on opacity { + NumberAnimation { + duration: Theme.expressiveDurations.expressiveDefaultSpatial + easing.type: Easing.BezierSpline + easing.bezierCurve: Theme.expressiveCurves.expressiveDefaultSpatial + } + } + + Behavior on scale { + NumberAnimation { + duration: Theme.expressiveDurations.expressiveDefaultSpatial + easing.type: Easing.BezierSpline + easing.bezierCurve: Theme.expressiveCurves.expressiveDefaultSpatial + } + } + + ElevationShadow { + id: audioDevicesShadowLayer + anchors.fill: parent + z: -1 + level: Theme.elevationLevel2 + fallbackOffset: 4 + targetRadius: audioDevicesPanel.radius + targetColor: audioDevicesPanel.color + borderColor: audioDevicesPanel.border.color + borderWidth: audioDevicesPanel.border.width + shadowOpacity: Theme.elevationLevel2 && Theme.elevationLevel2.alpha !== undefined ? Theme.elevationLevel2.alpha : 0.25 + shadowEnabled: Theme.elevationEnabled + } + + Column { + anchors.fill: parent + anchors.margins: Theme.spacingM + + StyledText { + text: I18n.tr("Audio Output Devices (") + availableDevices.length + ")" + font.pixelSize: Theme.fontSizeMedium + font.weight: Font.Medium + color: Theme.surfaceText + width: parent.width + horizontalAlignment: Text.AlignHCenter + bottomPadding: Theme.spacingM + } + + DankFlickable { + width: parent.width + height: parent.height - 40 + contentHeight: deviceColumn.height + clip: true + + Column { + id: deviceColumn + width: parent.width + spacing: Theme.spacingS + + Repeater { + model: availableDevices + delegate: Rectangle { + required property var modelData + required property int index + + width: parent.width + height: 48 + radius: Theme.cornerRadius + color: deviceMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) + border.color: modelData === AudioService.sink ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2) + border.width: modelData === AudioService.sink ? 2 : 1 + + Row { + anchors.left: parent.left + anchors.leftMargin: Theme.spacingM + anchors.verticalCenter: parent.verticalCenter + spacing: Theme.spacingM + width: parent.width - Theme.spacingM * 2 + + DankIcon { + name: getAudioDeviceIcon(modelData) + size: 20 + color: modelData === AudioService.sink ? Theme.primary : Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + + function getAudioDeviceIcon(device) { + if (!device?.name) + return "speaker"; + const name = device.name.toLowerCase(); + if (name.includes("bluez") || name.includes("bluetooth")) + return "headset"; + if (name.includes("hdmi")) + return "tv"; + if (name.includes("usb")) + return "headset"; + return "speaker"; + } + } + + Column { + anchors.verticalCenter: parent.verticalCenter + width: parent.width - 20 - Theme.spacingM * 2 + + StyledText { + text: AudioService.displayName(modelData) + font.pixelSize: Theme.fontSizeMedium + color: Theme.surfaceText + font.weight: modelData === AudioService.sink ? Font.Medium : Font.Normal + elide: Text.ElideRight + wrapMode: Text.NoWrap + width: parent.width + } + + StyledText { + text: modelData === AudioService.sink ? "Active" : "Available" + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceVariantText + elide: Text.ElideRight + width: parent.width + } + } + } + + MouseArea { + id: deviceMouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + if (modelData) { + Pipewire.preferredDefaultAudioSink = modelData; + root.deviceSelected(modelData); + } + } + } + } + } + } + } + } + } + + Rectangle { + id: playersPanel + visible: dropdownType === 3 + width: 240 + height: Math.max(180, Math.min(240, (allPlayers?.length || 0) * 50 + 80)) + x: isRightEdge ? anchorPos.x : anchorPos.x - width + y: anchorPos.y - height / 2 + radius: Theme.cornerRadius * 2 + color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.98) + border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.6) + border.width: 2 + + opacity: dropdownType === 3 ? 1 : 0 + scale: dropdownType === 3 ? 1 : 0.96 + transformOrigin: isRightEdge ? Item.Left : Item.Right + + Behavior on opacity { + NumberAnimation { + duration: Theme.expressiveDurations.expressiveDefaultSpatial + easing.type: Easing.BezierSpline + easing.bezierCurve: Theme.expressiveCurves.expressiveDefaultSpatial + } + } + + Behavior on scale { + NumberAnimation { + duration: Theme.expressiveDurations.expressiveDefaultSpatial + easing.type: Easing.BezierSpline + easing.bezierCurve: Theme.expressiveCurves.expressiveDefaultSpatial + } + } + + ElevationShadow { + id: playersShadowLayer + anchors.fill: parent + z: -1 + level: Theme.elevationLevel2 + fallbackOffset: 4 + targetRadius: playersPanel.radius + targetColor: playersPanel.color + borderColor: playersPanel.border.color + borderWidth: playersPanel.border.width + shadowOpacity: Theme.elevationLevel2 && Theme.elevationLevel2.alpha !== undefined ? Theme.elevationLevel2.alpha : 0.25 + shadowEnabled: Theme.elevationEnabled + } + + Column { + anchors.fill: parent + anchors.margins: Theme.spacingM + + StyledText { + text: I18n.tr("Media Players (") + (allPlayers?.length || 0) + ")" + font.pixelSize: Theme.fontSizeMedium + font.weight: Font.Medium + color: Theme.surfaceText + width: parent.width + horizontalAlignment: Text.AlignHCenter + bottomPadding: Theme.spacingM + } + + DankFlickable { + width: parent.width + height: parent.height - 40 + contentHeight: playerColumn.height + clip: true + + Column { + id: playerColumn + width: parent.width + spacing: Theme.spacingS + + Repeater { + model: allPlayers || [] + delegate: Rectangle { + required property var modelData + required property int index + + width: parent.width + height: 48 + radius: Theme.cornerRadius + color: playerMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) + border.color: modelData === activePlayer ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2) + border.width: modelData === activePlayer ? 2 : 1 + + Row { + anchors.left: parent.left + anchors.leftMargin: Theme.spacingM + anchors.verticalCenter: parent.verticalCenter + spacing: Theme.spacingM + width: parent.width - Theme.spacingM * 2 + + DankIcon { + name: "music_note" + size: 20 + color: modelData === activePlayer ? Theme.primary : Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + } + + Column { + anchors.verticalCenter: parent.verticalCenter + width: parent.width - 20 - Theme.spacingM * 2 + + StyledText { + text: { + if (!modelData) + return "Unknown Player"; + const identity = modelData.identity || "Unknown Player"; + const trackTitle = modelData.trackTitle || ""; + return trackTitle.length > 0 ? identity + " - " + trackTitle : identity; + } + font.pixelSize: Theme.fontSizeMedium + color: Theme.surfaceText + font.weight: modelData === activePlayer ? Font.Medium : Font.Normal + elide: Text.ElideRight + wrapMode: Text.NoWrap + width: parent.width + } + + StyledText { + text: { + if (!modelData) + return ""; + const artist = modelData.trackArtist || ""; + const isActive = modelData === activePlayer; + if (artist.length > 0) + return artist + (isActive ? " (Active)" : ""); + return isActive ? "Active" : "Available"; + } + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceVariantText + elide: Text.ElideRight + wrapMode: Text.NoWrap + width: parent.width + } + } + } + + MouseArea { + id: playerMouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + if (modelData?.identity) { + root.playerSelected(modelData); + } + } + } + } + } + } + } + } + } + + MouseArea { + anchors.fill: parent + z: -1 + enabled: dropdownType !== 0 + onClicked: closeRequested() + } +} |