diff --git a/modules/Bar.qml b/modules/Bar.qml index e7e7f91..4086627 100644 --- a/modules/Bar.qml +++ b/modules/Bar.qml @@ -95,7 +95,7 @@ PanelWindow { // Media M.BarGroup { borderColor: M.Theme.base0E - M.Mpris {} + M.Mpris { bar: bar } M.Volume { visible: M.Modules.volume.enable } } diff --git a/modules/Mpris.qml b/modules/Mpris.qml index 3060154..28e5f5a 100644 --- a/modules/Mpris.qml +++ b/modules/Mpris.qml @@ -1,4 +1,5 @@ import QtQuick +import Quickshell import Quickshell.Services.Mpris import "." as M @@ -37,8 +38,21 @@ M.BarSection { anchors.verticalCenter: parent.verticalCenter } + required property var bar + TapHandler { cursorShape: Qt.PointingHandCursor - onTapped: root.player?.togglePlaying() + onTapped: menuLoader.active = !menuLoader.active + } + + Loader { + id: menuLoader + active: false + sourceComponent: M.MprisMenu { + player: root.player + screen: root.bar.screen + anchorX: root.mapToGlobal(root.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0) + onDismissed: menuLoader.active = false + } } } diff --git a/modules/MprisMenu.qml b/modules/MprisMenu.qml new file mode 100644 index 0000000..d1e9f2b --- /dev/null +++ b/modules/MprisMenu.qml @@ -0,0 +1,217 @@ +import QtQuick +import Quickshell.Services.Mpris +import "." as M + +M.PopupPanel { + id: menuWindow + + panelWidth: 280 + + required property MprisPlayer player + + // Album art placeholder (or real art if available) + Rectangle { + width: menuWindow.panelWidth + height: 80 + color: M.Theme.base02 + radius: M.Theme.radius + + Image { + anchors.fill: parent + source: menuWindow.player?.trackArtUrl ?? "" + fillMode: Image.PreserveAspectCrop + visible: status === Image.Ready + + // Round top corners to match panel + layer.enabled: true + layer.effect: Item { + // simple clip — the panel background behind handles the rounding + } + } + + // Fallback icon when no art + Text { + anchors.centerIn: parent + text: "\uF001" + color: M.Theme.base04 + font.pixelSize: 28 + font.family: M.Theme.iconFontFamily + visible: !menuWindow.player?.trackArtUrl + } + } + + // Track info + Item { + width: menuWindow.panelWidth + height: titleCol.implicitHeight + 8 + + Column { + id: titleCol + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: 12 + anchors.rightMargin: 12 + spacing: 2 + + Text { + width: parent.width + text: menuWindow.player?.trackTitle || "No track" + color: M.Theme.base05 + font.pixelSize: M.Theme.fontSize + 1 + font.family: M.Theme.fontFamily + font.bold: true + elide: Text.ElideRight + } + + Text { + width: parent.width + text: { + const p = menuWindow.player; + if (!p) return ""; + const artist = Array.isArray(p.trackArtists) ? p.trackArtists.join(", ") : (p.trackArtists || ""); + return [artist, p.trackAlbum].filter(s => s).join(" — "); + } + color: M.Theme.base04 + font.pixelSize: M.Theme.fontSize - 1 + font.family: M.Theme.fontFamily + elide: Text.ElideRight + visible: text !== "" + } + } + } + + // Progress bar + Item { + width: menuWindow.panelWidth + height: 20 + + readonly property real pos: menuWindow.player?.position ?? 0 + readonly property real dur: menuWindow.player?.length ?? 0 + readonly property real frac: dur > 0 ? pos / dur : 0 + + // Time labels + Text { + anchors.left: parent.left + anchors.leftMargin: 12 + anchors.verticalCenter: parent.verticalCenter + text: _fmt(parent.pos) + color: M.Theme.base04 + font.pixelSize: M.Theme.fontSize - 2 + font.family: M.Theme.fontFamily + + function _fmt(ms) { + const s = Math.floor(ms / 1000); + const m = Math.floor(s / 60); + return m + ":" + String(s % 60).padStart(2, "0"); + } + } + Text { + anchors.right: parent.right + anchors.rightMargin: 12 + anchors.verticalCenter: parent.verticalCenter + text: _fmt(parent.dur) + color: M.Theme.base04 + font.pixelSize: M.Theme.fontSize - 2 + font.family: M.Theme.fontFamily + + function _fmt(ms) { + const s = Math.floor(ms / 1000); + const m = Math.floor(s / 60); + return m + ":" + String(s % 60).padStart(2, "0"); + } + } + + // Bar + Item { + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + width: parent.width - 80 + height: 4 + + Rectangle { + anchors.fill: parent + color: M.Theme.base02 + radius: 2 + } + Rectangle { + width: parent.width * Math.min(1, Math.max(0, parent.parent.frac)) + height: parent.height + color: M.Theme.base0E + radius: 2 + } + } + } + + // Transport controls + Item { + width: menuWindow.panelWidth + height: 36 + + Row { + anchors.centerIn: parent + spacing: 24 + + // Previous + Text { + text: "\uF048" + color: menuWindow.player?.canGoPrevious ? M.Theme.base05 : M.Theme.base03 + font.pixelSize: M.Theme.fontSize + 4 + font.family: M.Theme.iconFontFamily + anchors.verticalCenter: parent.verticalCenter + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + enabled: menuWindow.player?.canGoPrevious ?? false + onClicked: menuWindow.player.previous() + } + } + + // Play/Pause + Text { + text: menuWindow.player?.playbackState === MprisPlaybackState.Playing ? "\uF04C" : "\uF04B" + color: M.Theme.base0E + font.pixelSize: M.Theme.fontSize + 8 + font.family: M.Theme.iconFontFamily + anchors.verticalCenter: parent.verticalCenter + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: menuWindow.player?.togglePlaying() + } + } + + // Next + Text { + text: "\uF051" + color: menuWindow.player?.canGoNext ? M.Theme.base05 : M.Theme.base03 + font.pixelSize: M.Theme.fontSize + 4 + font.family: M.Theme.iconFontFamily + anchors.verticalCenter: parent.verticalCenter + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + enabled: menuWindow.player?.canGoNext ?? false + onClicked: menuWindow.player.next() + } + } + } + } + + // Player name + Item { + width: menuWindow.panelWidth + height: 20 + + Text { + anchors.centerIn: parent + text: menuWindow.player?.identity ?? "" + color: M.Theme.base03 + font.pixelSize: M.Theme.fontSize - 2 + font.family: M.Theme.fontFamily + } + } +} diff --git a/modules/qmldir b/modules/qmldir index 8d06d25..54c3c05 100644 --- a/modules/qmldir +++ b/modules/qmldir @@ -20,6 +20,7 @@ Osd 1.0 Osd.qml ThemedIcon 1.0 ThemedIcon.qml Battery 1.0 Battery.qml Mpris 1.0 Mpris.qml +MprisMenu 1.0 MprisMenu.qml Network 1.0 Network.qml Bluetooth 1.0 Bluetooth.qml Backlight 1.0 Backlight.qml