From 70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69 Mon Sep 17 00:00:00 2001 From: AlexanderCurl Date: Sat, 18 Apr 2026 17:46:06 +0100 Subject: Replaced file structures for themes in the correct format --- .../quickshell/Modules/DankDash/MediaPlayerTab.qml | 785 +++++++++++++++++++++ 1 file changed, 785 insertions(+) create mode 100644 raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/DankDash/MediaPlayerTab.qml (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/DankDash/MediaPlayerTab.qml') diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/DankDash/MediaPlayerTab.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/DankDash/MediaPlayerTab.qml new file mode 100644 index 0000000..c6d7bfc --- /dev/null +++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/DankDash/MediaPlayerTab.qml @@ -0,0 +1,785 @@ +import QtQuick +import QtQuick.Effects +import QtQuick.Layouts +import Quickshell.Services.Mpris +import Quickshell.Io +import qs.Common +import qs.Services +import qs.Widgets + +Item { + id: root + + LayoutMirroring.enabled: I18n.isRtl + LayoutMirroring.childrenInherit: true + + property MprisPlayer activePlayer: MprisController.activePlayer + property var allPlayers: MprisController.availablePlayers + property var targetScreen: null + property real popoutX: 0 + property real popoutY: 0 + property real popoutWidth: 0 + property real popoutHeight: 0 + property real contentOffsetY: 0 + property string section: "" + property int barPosition: SettingsData.Position.Top + + signal showVolumeDropdown(point pos, var screen, bool rightEdge, var player, var players) + signal showAudioDevicesDropdown(point pos, var screen, bool rightEdge) + signal showPlayersDropdown(point pos, var screen, bool rightEdge, var player, var players) + signal hideDropdowns + signal volumeButtonExited + + property bool volumeExpanded: false + property bool devicesExpanded: false + property bool playersExpanded: false + + function resetDropdownStates() { + volumeExpanded = false; + devicesExpanded = false; + playersExpanded = false; + } + + DankTooltipV2 { + id: sharedTooltip + } + + readonly property bool isRightEdge: { + if (barPosition === SettingsData.Position.Right) + return true; + if (barPosition === SettingsData.Position.Left) + return false; + return section === "right"; + } + readonly property bool __isChromeBrowser: { + if (!activePlayer?.identity) + return false; + const id = activePlayer.identity.toLowerCase(); + return id.includes("chrome") || id.includes("chromium"); + } + readonly property bool volumeAvailable: (activePlayer && activePlayer.volumeSupported && !__isChromeBrowser) || (AudioService.sink && AudioService.sink.audio) + readonly property bool usePlayerVolume: activePlayer && activePlayer.volumeSupported && !__isChromeBrowser + readonly property real currentVolume: usePlayerVolume ? activePlayer.volume : (AudioService.sink?.audio?.volume ?? 0) + + property bool isSwitching: false + + // Derived "no players" state: always correct, no timers. + readonly property int _playerCount: allPlayers ? allPlayers.length : 0 + readonly property bool _noneAvailable: _playerCount === 0 + readonly property bool _trulyIdle: activePlayer && activePlayer.playbackState === MprisPlaybackState.Stopped && !activePlayer.trackTitle && !activePlayer.trackArtist + readonly property bool showNoPlayerNow: (!_switchHold) && (_noneAvailable || _trulyIdle) + + property bool _switchHold: false + Timer { + id: _switchHoldTimer + interval: 650 + repeat: false + onTriggered: _switchHold = false + } + + onActivePlayerChanged: { + if (!activePlayer) { + isSwitching = false; + _switchHold = false; + return; + } + isSwitching = true; + _switchHold = true; + _switchHoldTimer.restart(); + TrackArtService.loadArtwork(activePlayer.trackArtUrl); + } + + function maybeFinishSwitch() { + if (activePlayer && activePlayer.trackTitle !== "") { + isSwitching = false; + _switchHold = false; + } + } + + readonly property real ratio: { + if (!activePlayer || !activePlayer.length || activePlayer.length <= 0) { + return 0; + } + const pos = (activePlayer.position || 0) % Math.max(1, activePlayer.length); + const calculatedRatio = pos / activePlayer.length; + return Math.max(0, Math.min(1, calculatedRatio)); + } + + implicitWidth: SettingsData.showWeekNumber ? 736 : 700 + implicitHeight: playerContent.height + playerContent.anchors.topMargin * 2 + + Connections { + target: activePlayer + function onTrackTitleChanged() { + _switchHoldTimer.restart(); + maybeFinishSwitch(); + } + function onTrackArtUrlChanged() { + TrackArtService.loadArtwork(activePlayer.trackArtUrl); + } + } + + Connections { + target: MprisController + function onAvailablePlayersChanged() { + const count = (MprisController.availablePlayers?.length || 0); + if (count === 0) { + isSwitching = false; + _switchHold = false; + } else { + _switchHold = true; + _switchHoldTimer.restart(); + } + } + } + + function getAudioDeviceIcon(device) { + if (!device || !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"; + if (name.includes("analog") || name.includes("built-in")) + return "speaker"; + + return "speaker"; + } + + function getVolumeIcon() { + if (!volumeAvailable) + return "volume_off"; + + const volume = currentVolume; + + if (usePlayerVolume) { + if (volume === 0.0) + return "music_off"; + return "music_note"; + } + + if (volume === 0.0) + return "volume_off"; + if (volume <= 0.33) + return "volume_down"; + if (volume <= 0.66) + return "volume_up"; + return "volume_up"; + } + + function adjustVolume(step) { + if (!volumeAvailable) + return; + const maxVol = usePlayerVolume ? 100 : AudioService.sinkMaxVolume; + const current = Math.round(currentVolume * 100); + const newVolume = Math.min(maxVol, Math.max(0, current + step)); + + SessionData.suppressOSDTemporarily(); + if (usePlayerVolume) { + activePlayer.volume = newVolume / 100; + } else if (AudioService.sink?.audio) { + AudioService.sink.audio.volume = newVolume / 100; + } + } + + property bool isSeeking: false + + Timer { + interval: 1000 + running: activePlayer?.playbackState === MprisPlaybackState.Playing && !isSeeking + repeat: true + onTriggered: activePlayer?.positionChanged() + } + + Item { + id: bgContainer + anchors.fill: parent + visible: TrackArtService._bgArtSource !== "" + + Image { + id: bgImage + anchors.centerIn: parent + width: Math.max(parent.width, parent.height) * 1.1 + height: width + source: TrackArtService._bgArtSource + fillMode: Image.PreserveAspectCrop + asynchronous: true + cache: true + visible: false + onStatusChanged: { + if (status === Image.Ready) + maybeFinishSwitch(); + } + } + + 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.8 + 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 + } + } + + Column { + anchors.centerIn: parent + spacing: Theme.spacingM + visible: showNoPlayerNow + + DankIcon { + name: "music_note" + size: Theme.iconSize * 3 + color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5) + anchors.horizontalCenter: parent.horizontalCenter + } + + StyledText { + text: I18n.tr("No Active Players") + font.pixelSize: Theme.fontSizeLarge + color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) + anchors.horizontalCenter: parent.horizontalCenter + } + } + + Item { + anchors.fill: parent + clip: false + visible: !_noneAvailable && (!showNoPlayerNow) + ColumnLayout { + id: playerContent + width: 484 + height: 370 + spacing: Theme.spacingXS + anchors.top: parent.top + anchors.topMargin: 20 + anchors.horizontalCenter: parent.horizontalCenter + + Item { + width: parent.width + height: 200 + clip: false + + DankAlbumArt { + width: Math.min(parent.width * 0.8, parent.height * 0.9) + height: width + anchors.centerIn: parent + activePlayer: root.activePlayer + } + } + + // Song Info and Controls Section + Item { + width: parent.width + Layout.fillHeight: true + + Column { + id: songInfo + width: parent.width + spacing: Theme.spacingXS + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + + StyledText { + text: activePlayer?.trackTitle || I18n.tr("Unknown Track") + font.pixelSize: Theme.fontSizeLarge + font.weight: Font.Bold + color: Theme.surfaceText + width: parent.width + horizontalAlignment: Text.AlignHCenter + elide: Text.ElideRight + wrapMode: Text.WordWrap + maximumLineCount: 2 + } + + StyledText { + text: activePlayer?.trackTitle || I18n.tr("Unknown Artist") + font.pixelSize: Theme.fontSizeMedium + color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.8) + width: parent.width + horizontalAlignment: Text.AlignHCenter + elide: Text.ElideRight + wrapMode: Text.WordWrap + maximumLineCount: 1 + } + + StyledText { + text: activePlayer?.trackAlbum || "" + font.pixelSize: Theme.fontSizeSmall + color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6) + width: parent.width + horizontalAlignment: Text.AlignHCenter + elide: Text.ElideRight + wrapMode: Text.WordWrap + maximumLineCount: 1 + visible: text.length > 0 + } + } + + Item { + id: seekbarContainer + width: parent.width + anchors.top: songInfo.bottom + anchors.bottom: playbackControls.top + anchors.horizontalCenter: parent.horizontalCenter + + Column { + width: parent.width + spacing: 2 + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: parent.height * 0.2 + + DankSeekbar { + width: parent.width * 0.8 + height: 20 + anchors.horizontalCenter: parent.horizontalCenter + activePlayer: root.activePlayer + isSeeking: root.isSeeking + onIsSeekingChanged: root.isSeeking = isSeeking + } + + Item { + width: parent.width * 0.8 + height: 16 + anchors.horizontalCenter: parent.horizontalCenter + + StyledText { + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + text: { + if (!activePlayer) + return "0:00"; + const rawPos = Math.max(0, activePlayer.position || 0); + const pos = activePlayer.length ? rawPos % Math.max(1, activePlayer.length) : rawPos; + const minutes = Math.floor(pos / 60); + const seconds = Math.floor(pos % 60); + const timeStr = minutes + ":" + (seconds < 10 ? "0" : "") + seconds; + return timeStr; + } + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceVariantText + } + + StyledText { + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + text: { + if (!activePlayer || !activePlayer.length) + return "0:00"; + const dur = Math.max(0, activePlayer.length || 0); + const minutes = Math.floor(dur / 60); + const seconds = Math.floor(dur % 60); + return minutes + ":" + (seconds < 10 ? "0" : "") + seconds; + } + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceVariantText + } + } + } + } + + Item { + id: playbackControls + width: parent.width + height: 50 + anchors.bottom: parent.bottom + + Row { + anchors.centerIn: parent + spacing: Theme.spacingM + height: parent.height + + Item { + width: 50 + height: 50 + anchors.verticalCenter: parent.verticalCenter + visible: activePlayer && activePlayer.shuffleSupported + + Rectangle { + width: 40 + height: 40 + radius: 20 + anchors.centerIn: parent + color: shuffleArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" + + DankIcon { + anchors.centerIn: parent + name: "shuffle" + size: 20 + color: activePlayer && activePlayer.shuffle ? Theme.primary : Theme.surfaceText + } + + MouseArea { + id: shuffleArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + if (activePlayer && activePlayer.canControl && activePlayer.shuffleSupported) { + activePlayer.shuffle = !activePlayer.shuffle; + } + } + } + } + } + + Item { + width: 50 + height: 50 + anchors.verticalCenter: parent.verticalCenter + + Rectangle { + width: 40 + height: 40 + radius: 20 + anchors.centerIn: parent + color: prevBtnArea.containsMouse ? Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) : "transparent" + + DankIcon { + anchors.centerIn: parent + name: "skip_previous" + size: 24 + color: Theme.surfaceText + } + + MouseArea { + id: prevBtnArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: MprisController.previousOrRewind() + } + } + } + + Item { + width: 50 + height: 50 + anchors.verticalCenter: parent.verticalCenter + + Rectangle { + width: 50 + height: 50 + radius: 25 + anchors.centerIn: parent + color: Theme.primary + + DankIcon { + anchors.centerIn: parent + name: activePlayer && activePlayer.playbackState === MprisPlaybackState.Playing ? "pause" : "play_arrow" + size: 28 + color: Theme.background + weight: 500 + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: activePlayer && activePlayer.togglePlaying() + } + + ElevationShadow { + anchors.fill: parent + z: -1 + level: Theme.elevationLevel1 + fallbackOffset: 1 + targetRadius: parent.radius + targetColor: parent.color + shadowOpacity: Theme.elevationLevel1 && Theme.elevationLevel1.alpha !== undefined ? Theme.elevationLevel1.alpha : 0.2 + shadowEnabled: Theme.elevationEnabled + } + } + } + + Item { + width: 50 + height: 50 + anchors.verticalCenter: parent.verticalCenter + + Rectangle { + width: 40 + height: 40 + radius: 20 + anchors.centerIn: parent + color: nextBtnArea.containsMouse ? Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) : "transparent" + + DankIcon { + anchors.centerIn: parent + name: "skip_next" + size: 24 + color: Theme.surfaceText + } + + MouseArea { + id: nextBtnArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: activePlayer && activePlayer.next() + } + } + } + + Item { + width: 50 + height: 50 + anchors.verticalCenter: parent.verticalCenter + visible: activePlayer && activePlayer.loopSupported + + Rectangle { + width: 40 + height: 40 + radius: 20 + anchors.centerIn: parent + color: repeatArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" + + DankIcon { + anchors.centerIn: parent + name: { + if (!activePlayer) + return "repeat"; + switch (activePlayer.loopState) { + case MprisLoopState.Track: + return "repeat_one"; + case MprisLoopState.Playlist: + return "repeat"; + default: + return "repeat"; + } + } + size: 20 + color: activePlayer && activePlayer.loopState !== MprisLoopState.None ? Theme.primary : Theme.surfaceText + } + + MouseArea { + id: repeatArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + if (activePlayer && activePlayer.canControl && activePlayer.loopSupported) { + switch (activePlayer.loopState) { + case MprisLoopState.None: + activePlayer.loopState = MprisLoopState.Playlist; + break; + case MprisLoopState.Playlist: + activePlayer.loopState = MprisLoopState.Track; + break; + case MprisLoopState.Track: + activePlayer.loopState = MprisLoopState.None; + break; + } + } + } + } + } + } + } + } + } + } + } + + Rectangle { + id: playerSelectorButton + width: 40 + height: 40 + radius: 20 + x: isRightEdge ? Theme.spacingM : parent.width - 40 - Theme.spacingM + y: 185 + color: playerSelectorArea.containsMouse || playersExpanded ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) : "transparent" + border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3) + border.width: 1 + z: 100 + visible: (allPlayers?.length || 0) >= 1 + + DankIcon { + anchors.centerIn: parent + name: "assistant_device" + size: 18 + color: Theme.surfaceText + } + + MouseArea { + id: playerSelectorArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + if (playersExpanded) { + hideDropdowns(); + return; + } + hideDropdowns(); + playersExpanded = true; + const buttonsOnRight = !isRightEdge; + const btnY = playerSelectorButton.y + playerSelectorButton.height / 2; + const screenX = buttonsOnRight ? (popoutX + popoutWidth) : popoutX; + const screenY = popoutY + contentOffsetY + btnY; + showPlayersDropdown(Qt.point(screenX, screenY), targetScreen, buttonsOnRight, activePlayer, allPlayers); + } + onEntered: sharedTooltip.show(I18n.tr("Media Players"), playerSelectorButton, 0, 0, isRightEdge ? "right" : "left") + onExited: sharedTooltip.hide() + } + } + + Rectangle { + id: volumeButton + width: 40 + height: 40 + radius: 20 + x: isRightEdge ? Theme.spacingM : parent.width - 40 - Theme.spacingM + y: 130 + color: volumeButtonArea.containsMouse && volumeAvailable || volumeExpanded ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) : "transparent" + border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, volumeAvailable ? 0.3 : 0.15) + border.width: 1 + z: 101 + enabled: volumeAvailable + + property real previousVolume: 0.0 + + DankIcon { + anchors.centerIn: parent + name: getVolumeIcon() + size: 18 + color: volumeAvailable && currentVolume > 0 ? Theme.primary : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, volumeAvailable ? 1.0 : 0.5) + } + + MouseArea { + id: volumeButtonArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onEntered: { + if (volumeExpanded) + return; + hideDropdowns(); + volumeExpanded = true; + const buttonsOnRight = !isRightEdge; + const btnY = volumeButton.y + volumeButton.height / 2; + const screenX = buttonsOnRight ? (popoutX + popoutWidth) : popoutX; + const screenY = popoutY + contentOffsetY + btnY; + showVolumeDropdown(Qt.point(screenX, screenY), targetScreen, buttonsOnRight, activePlayer, allPlayers); + } + onExited: { + if (volumeExpanded) + volumeButtonExited(); + } + onClicked: { + SessionData.suppressOSDTemporarily(); + if (currentVolume > 0) { + volumeButton.previousVolume = currentVolume; + if (usePlayerVolume) { + activePlayer.volume = 0; + } else if (AudioService.sink?.audio) { + AudioService.sink.audio.volume = 0; + } + } else { + const restoreVolume = volumeButton.previousVolume > 0 ? volumeButton.previousVolume : 0.5; + if (usePlayerVolume) { + activePlayer.volume = restoreVolume; + } else if (AudioService.sink?.audio) { + AudioService.sink.audio.volume = restoreVolume; + } + } + } + onWheel: wheelEvent => { + SessionData.suppressOSDTemporarily(); + const delta = wheelEvent.angleDelta.y; + const current = (currentVolume * 100) || 0; + const maxVol = usePlayerVolume ? 100 : AudioService.sinkMaxVolume; + const newVolume = delta > 0 ? Math.min(maxVol, current + 5) : Math.max(0, current - 5); + + if (usePlayerVolume) { + activePlayer.volume = newVolume / 100; + } else if (AudioService.sink?.audio) { + AudioService.sink.audio.volume = newVolume / 100; + } + wheelEvent.accepted = true; + } + } + } + + Rectangle { + id: audioDevicesButton + width: 40 + height: 40 + radius: 20 + x: isRightEdge ? Theme.spacingM : parent.width - 40 - Theme.spacingM + y: 240 + color: audioDevicesArea.containsMouse || devicesExpanded ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) : "transparent" + border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3) + border.width: 1 + z: 100 + + DankIcon { + anchors.centerIn: parent + name: devicesExpanded ? "expand_less" : "speaker" + size: 18 + color: Theme.surfaceText + } + + MouseArea { + id: audioDevicesArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + if (devicesExpanded) { + hideDropdowns(); + return; + } + hideDropdowns(); + devicesExpanded = true; + const buttonsOnRight = !isRightEdge; + const btnY = audioDevicesButton.y + audioDevicesButton.height / 2; + const screenX = buttonsOnRight ? (popoutX + popoutWidth) : popoutX; + const screenY = popoutY + contentOffsetY + btnY; + showAudioDevicesDropdown(Qt.point(screenX, screenY), targetScreen, buttonsOnRight); + } + onEntered: sharedTooltip.show(I18n.tr("Output Device"), audioDevicesButton, 0, 0, isRightEdge ? "right" : "left") + onExited: sharedTooltip.hide() + } + } +} -- cgit v1.3