Files
2026-07-26 16:51:25 +03:00

207 lines
7.2 KiB
QML

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Services.Mpris
import Quickshell.Wayland
Item {
id: root
UiTokens { id: theme }
property bool open: false
property var selectedPlayer: null
property var player: selectedPlayer
|| Mpris.players.values.find(candidate => candidate.playbackState === MprisPlaybackState.Playing)
|| Mpris.players.values[0]
|| null
signal dismissed()
function formatTime(value) {
if (!value || value < 0) return "0:00";
const seconds = Math.floor(value);
return Math.floor(seconds / 60) + ":" + String(seconds % 60).padStart(2, "0");
}
PanelWindow {
visible: root.open
color: "transparent"
anchors {
top: true
bottom: true
left: true
right: true
}
exclusionMode: ExclusionMode.Ignore
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: root.open ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
WlrLayershell.namespace: "quickshell-media-popup"
MouseArea {
anchors.fill: parent
onClicked: root.dismissed()
}
Rectangle {
id: card
width: Math.min(410, parent.width - 32)
height: root.player ? 330 : 150
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 52
radius: 20
color: "#11111b"
border.color: "#313244"
border.width: 1
z: 1
MouseArea {
anchors.fill: parent
onClicked: function(mouse) { mouse.accepted = true; }
}
ColumnLayout {
anchors.fill: parent
anchors.margins: 16
spacing: 10
RowLayout {
Layout.fillWidth: true
Label {
text: "Media"
color: "#cdd6f4"
font.pixelSize: 20
font.bold: true
Layout.fillWidth: true
}
Label {
text: root.player ? root.player.identity : "No player"
color: "#9399b2"
elide: Text.ElideRight
}
CCButton {
theme: theme
text: "Close"
onClicked: root.dismissed()
}
}
RowLayout {
visible: root.player !== null
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 14
Rectangle {
Layout.preferredWidth: 132
Layout.preferredHeight: 132
radius: 14
color: "#181825"
clip: true
Image {
id: cover
anchors.fill: parent
source: root.player ? root.player.trackArtUrl : ""
fillMode: Image.PreserveAspectCrop
asynchronous: true
}
Label {
anchors.centerIn: parent
visible: cover.status !== Image.Ready
text: "♪"
color: "#8b8176"
font.pixelSize: 42
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 4
Label {
text: root.player ? (root.player.trackTitle || "Unknown track") : ""
color: "#cdd6f4"
font.pixelSize: 17
font.bold: true
elide: Text.ElideRight
Layout.fillWidth: true
}
Label {
text: root.player ? (root.player.trackArtist || root.player.trackAlbum || "Unknown artist") : ""
color: "#9399b2"
elide: Text.ElideRight
Layout.fillWidth: true
}
Item { Layout.fillHeight: true }
Label {
text: root.player ? root.formatTime(root.player.position) + " / " + root.formatTime(root.player.length) : ""
color: "#9399b2"
font.pixelSize: 11
}
}
}
CCSlider {
theme: theme
visible: root.player && root.player.length > 0
Layout.fillWidth: true
from: 0
to: root.player ? root.player.length : 1
value: root.player ? root.player.position : 0
onMoved: {
if (root.player && root.player.canSeek) root.player.position = value;
}
}
RowLayout {
visible: root.player !== null
Layout.alignment: Qt.AlignHCenter
spacing: 14
CCButton {
theme: theme
text: "Prev"
enabled: !!(root.player && root.player.canGoPrevious)
onClicked: root.player.previous()
}
CCButton {
theme: theme
text: root.player && root.player.playbackState === MprisPlaybackState.Playing ? "Pause" : "Play"
enabled: !!(root.player && root.player.canTogglePlaying)
onClicked: root.player.togglePlaying()
}
CCButton {
theme: theme
text: "Next"
enabled: !!(root.player && root.player.canGoNext)
onClicked: root.player.next()
}
}
RowLayout {
visible: Mpris.players.values.length > 1
Layout.fillWidth: true
Label {
text: "Players"
color: "#9399b2"
Layout.fillWidth: true
}
Repeater {
model: Mpris.players
delegate: CCButton {
required property var modelData
theme: theme
text: modelData.identity
onClicked: root.selectedPlayer = modelData
}
}
}
}
}
}
}