aboutsummaryrefslogtreecommitdiff
path: root/modules/system/quickshell/MediaCard.qml
diff options
context:
space:
mode:
Diffstat (limited to 'modules/system/quickshell/MediaCard.qml')
-rw-r--r--modules/system/quickshell/MediaCard.qml244
1 files changed, 244 insertions, 0 deletions
diff --git a/modules/system/quickshell/MediaCard.qml b/modules/system/quickshell/MediaCard.qml
new file mode 100644
index 0000000..5aba34c
--- /dev/null
+++ b/modules/system/quickshell/MediaCard.qml
@@ -0,0 +1,244 @@
+import QtQuick
+import QtQuick.Layouts
+import Quickshell
+import Quickshell.Services.Mpris
+
+Item {
+ id: root
+
+ property QtObject player: null
+ property bool isExpanded: false
+ signal clicked()
+
+ Layout.fillWidth: true
+ implicitHeight: layout.implicitHeight
+
+ Behavior on implicitHeight {
+ NumberAnimation { duration: 200; easing.type: Easing.OutCubic }
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ enabled: !root.isExpanded
+ onClicked: root.clicked()
+ }
+
+ function formatTime(s) {
+ const mins = Math.floor(s / 60)
+ const secs = Math.floor(s % 60)
+ return `${mins}:${secs.toString().padStart(2, '0')}`
+ }
+
+ ColumnLayout {
+ id: layout
+ anchors.left: parent.left
+ anchors.right: parent.right
+ spacing: 16
+
+ // Top Row: Minimal view and header for expanded view
+ RowLayout {
+ Layout.fillWidth: true
+ spacing: 12
+
+ Squircle {
+ width: root.isExpanded ? 56 : 40
+ height: root.isExpanded ? 56 : 40
+ cornerRadius: root.isExpanded ? 10 : 8
+ fillColor: root.player ? Theme.transparent : Theme.surface
+ clip: true
+
+ Behavior on width { NumberAnimation { duration: 200 } }
+ Behavior on height { NumberAnimation { duration: 200 } }
+ Behavior on cornerRadius { NumberAnimation { duration: 200 } }
+
+ Image {
+ anchors.fill: parent
+ source: root.player?.trackArtUrl || ""
+ fillMode: Image.PreserveAspectCrop
+ visible: status === Image.Ready && root.player !== null
+ }
+
+ IconCircle {
+ anchors.centerIn: parent
+ size: root.isExpanded ? 28 : 20
+ source: "audio-x-generic-symbolic"
+ visible: !root.player?.trackArtUrl || parent.children[0].status !== Image.Ready || root.player === null
+ }
+ }
+
+ ColumnLayout {
+ Layout.fillWidth: true
+ Layout.alignment: Qt.AlignVCenter
+ spacing: 2
+
+ Text {
+ Layout.fillWidth: true
+ text: root.player?.trackTitle || "Music"
+ color: root.player ? Theme.text : Theme.textDisabled
+ font.pixelSize: 13
+ font.weight: Font.DemiBold
+ elide: Text.ElideRight
+ }
+
+ Text {
+ Layout.fillWidth: true
+ text: root.player?.trackArtist || ""
+ visible: text !== "" && root.player !== null
+ color: Theme.textDim
+ font.pixelSize: 12
+ font.weight: Font.Medium
+ elide: Text.ElideRight
+ }
+
+ Text {
+ Layout.fillWidth: true
+ text: root.player?.trackAlbum || ""
+ visible: root.isExpanded && text !== "" && root.player !== null
+ color: Theme.textDisabled
+ font.pixelSize: 11
+ font.weight: Font.Normal
+ elide: Text.ElideRight
+ }
+ }
+
+ // Minimal Controls: Only visible when not expanded
+ Row {
+ visible: !root.isExpanded
+ spacing: 12
+ Layout.alignment: Qt.AlignVCenter
+
+ Item {
+ width: 24; height: 24
+ Image {
+ anchors.centerIn: parent
+ source: Quickshell.iconPath(root.player?.playbackState === MprisPlaybackState.Playing ? "media-playback-pause-symbolic" : "media-playback-start-symbolic")
+ sourceSize: Qt.size(24, 24)
+ opacity: root.player?.canTogglePlaying ? (minPlayMouse.pressed ? 0.7 : 1.0) : 0.3
+ }
+ MouseArea {
+ id: minPlayMouse
+ anchors.fill: parent
+ enabled: root.player?.canTogglePlaying ?? false
+ onClicked: if (root.player) root.player.togglePlaying()
+ }
+ }
+
+ Item {
+ width: 24; height: 24
+ Image {
+ anchors.centerIn: parent
+ source: Quickshell.iconPath("media-skip-forward-symbolic")
+ sourceSize: Qt.size(20, 20)
+ opacity: root.player?.canGoNext ? (minNextMouse.pressed ? 0.5 : 0.8) : 0.3
+ }
+ MouseArea {
+ id: minNextMouse
+ anchors.fill: parent
+ enabled: root.player?.canGoNext ?? false
+ onClicked: if (root.player) root.player.next()
+ }
+ }
+ }
+ }
+
+ // Expanded Controls: Only visible when expanded and a player exists
+ ColumnLayout {
+ visible: root.isExpanded && root.player !== null
+ Layout.fillWidth: true
+ spacing: 8
+
+ ColumnLayout {
+ Layout.fillWidth: true
+ spacing: 2
+
+ ThinSlider {
+ id: progressSlider
+ Layout.fillWidth: true
+ from: 0
+ to: root.player?.length || 1
+ value: root.player?.position || 0
+ enabled: root.player?.canSeek ?? false
+
+ onMoved: if (root.player) root.player.position = value
+
+ Timer {
+ running: root.player?.playbackState === MprisPlaybackState.Playing && root.isExpanded
+ interval: 500
+ repeat: true
+ onTriggered: progressSlider.value = root.player.position
+ }
+ }
+
+ RowLayout {
+ Layout.fillWidth: true
+ Text {
+ text: root.formatTime(root.player?.position || 0)
+ color: Theme.textPlaceholder
+ font.pixelSize: 9
+ font.weight: Font.Medium
+ }
+ Item { Layout.fillWidth: true }
+ Text {
+ text: root.formatTime(root.player?.length || 0)
+ color: Theme.textPlaceholder
+ font.pixelSize: 9
+ font.weight: Font.Medium
+ }
+ }
+ }
+
+ RowLayout {
+ Layout.alignment: Qt.AlignHCenter
+ spacing: 32
+
+ Item {
+ width: 24; height: 24
+ Image {
+ anchors.centerIn: parent
+ source: Quickshell.iconPath("media-skip-backward-symbolic")
+ sourceSize: Qt.size(20, 20)
+ opacity: root.player?.canGoPrevious ? (prevMouse.pressed ? 0.5 : 0.8) : 0.3
+ }
+ MouseArea {
+ id: prevMouse
+ anchors.fill: parent
+ enabled: root.player?.canGoPrevious ?? false
+ onClicked: if (root.player) root.player.previous()
+ }
+ }
+
+ Item {
+ width: 32; height: 32
+ Image {
+ anchors.centerIn: parent
+ source: Quickshell.iconPath(root.player?.playbackState === MprisPlaybackState.Playing ? "media-playback-pause-symbolic" : "media-playback-start-symbolic")
+ sourceSize: Qt.size(28, 28)
+ opacity: root.player?.canTogglePlaying ? (maxPlayMouse.pressed ? 0.7 : 1.0) : 0.3
+ }
+ MouseArea {
+ id: maxPlayMouse
+ anchors.fill: parent
+ enabled: root.player?.canTogglePlaying ?? false
+ onClicked: if (root.player) root.player.togglePlaying()
+ }
+ }
+
+ Item {
+ width: 24; height: 24
+ Image {
+ anchors.centerIn: parent
+ source: Quickshell.iconPath("media-skip-forward-symbolic")
+ sourceSize: Qt.size(20, 20)
+ opacity: root.player?.canGoNext ? (maxNextMouse.pressed ? 0.5 : 0.8) : 0.3
+ }
+ MouseArea {
+ id: maxNextMouse
+ anchors.fill: parent
+ enabled: root.player?.canGoNext ?? false
+ onClicked: if (root.player) root.player.next()
+ }
+ }
+ }
+ }
+ }
+}