Files
dotfiles/roles/quickshell/files/bar/MusicPlayerControls.qml
2026-04-06 01:30:28 +02:00

167 lines
5.1 KiB
QML

import Quickshell
import Quickshell.Services.Mpris
import QtQuick
import QtQuick.Layouts
import "../components"
PopupWindow {
id: root
required property var player
implicitWidth: 280
implicitHeight: bg.implicitHeight
// Tickle positionChanged every frame while playing so the player.position
// binding re-evaluates. Paused while the user is dragging the seek bar.
FrameAnimation {
running: (root.player?.isPlaying ?? false) && !progressArea.pressed
onTriggered: root.player.positionChanged()
}
Rectangle {
id: bg
anchors.fill: parent
color: Theme.bg
border.color: Theme.border
border.width: Theme.borderWidth
radius: Theme.radius
implicitHeight: layout.implicitHeight + 28
ColumnLayout {
id: layout
anchors { fill: parent; margins: 14 }
spacing: 8
// Track info
Text {
text: root.player?.trackTitle ?? ""
color: Theme.text
font.pixelSize: 13
font.bold: true
Layout.fillWidth: true
elide: Text.ElideRight
}
Text {
text: root.player?.trackArtist ?? ""
color: Theme.textDim
font.pixelSize: 11
Layout.fillWidth: true
elide: Text.ElideRight
visible: (root.player?.trackArtist ?? "") !== ""
Layout.topMargin: -4
}
// Progress bar
Item {
visible: root.player?.positionSupported && root.player?.lengthSupported
&& (root.player?.length ?? 0) > 0
Layout.fillWidth: true
height: 12 // visual track is centred inside; extra height = easier to hit
// Track
Rectangle {
anchors.verticalCenter: parent.verticalCenter
width: parent.width
height: 3
radius: 2
color: Theme.progressTrack
// Filled portion
Rectangle {
width: root.player?.length > 0
? parent.width * (root.player.position / root.player.length)
: 0
height: parent.height
radius: parent.radius
color: Theme.accent
}
}
MouseArea {
id: progressArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
function seek(mx) {
if (!(root.player?.canSeek)) return
const ratio = Math.max(0, Math.min(1, mx / width))
root.player.position = ratio * (root.player?.length ?? 0)
}
onClicked: seek(mouseX)
onPositionChanged: if (pressed) seek(mouseX)
}
}
// Media controls
RowLayout {
Layout.fillWidth: true
spacing: 0
Item { Layout.fillWidth: true }
ControlButton {
icon: "⏮"
iconSize: 18
enabled: root.player?.canGoPrevious ?? false
onActivated: root.player.previous()
}
Item { width: 8 }
ControlButton {
icon: root.player?.isPlaying ? "⏸" : "▶"
iconSize: 22
enabled: true
onActivated: root.player?.togglePlaying()
}
Item { width: 8 }
ControlButton {
icon: "⏭"
iconSize: 18
enabled: root.player?.canGoNext ?? false
onActivated: root.player.next()
}
Item { Layout.fillWidth: true }
}
}
}
// Inline helper — a button that highlights on hover
component ControlButton: Rectangle {
id: btn
required property string icon
required property int iconSize
required property bool enabled
signal activated
implicitWidth: label.implicitWidth + 16
implicitHeight: label.implicitHeight + 10
radius: Theme.radius
color: btnArea.containsMouse ? Qt.rgba(1, 1, 1, 0.08) : 'transparent'
Behavior on color { ColorAnimation { duration: 80 } }
Text {
id: label
anchors.centerIn: parent
text: btn.icon
font.pixelSize: btn.iconSize
color: btn.enabled ? Theme.text : Theme.textDisabled
}
MouseArea {
id: btnArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: if (btn.enabled) btn.activated()
}
}
}