167 lines
5.1 KiB
QML
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()
|
|
}
|
|
}
|
|
}
|