Files
dms-gamebar/GameBarMediaWidget.qml
T
2026-09-21 21:50:32 +02:00

249 lines
8.2 KiB
QML

import QtQuick
import Quickshell.Services.Mpris
import qs.Common
import qs.Services
import qs.Widgets
Item {
id: widget
readonly property var activePlayer: MprisController.activePlayer
readonly property var players: MprisController.availablePlayers || []
readonly property bool hasPlayer: activePlayer !== null && activePlayer !== undefined
readonly property bool isPlaying: hasPlayer && activePlayer.playbackState === MprisPlaybackState.Playing
function formatTime(seconds) {
const total = Math.max(0, Math.floor(seconds || 0));
const mins = Math.floor(total / 60);
const secs = total % 60;
return mins + ":" + (secs < 10 ? "0" : "") + secs;
}
function playerName(player) {
if (!player)
return "";
return player.identity || player.dbusName || "Player";
}
Item {
id: content
anchors.fill: parent
visible: widget.hasPlayer
DankAlbumArt {
id: art
anchors.left: parent.left
anchors.top: parent.top
width: 84
height: 84
activePlayer: widget.activePlayer
showAnimation: widget.isPlaying
}
Column {
id: meta
anchors.left: art.right
anchors.leftMargin: Theme.spacingM
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: Theme.spacingXS
spacing: 2
StyledText {
width: parent.width
text: (widget.activePlayer && widget.activePlayer.trackTitle) ? widget.activePlayer.trackTitle : "Nothing playing"
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
elide: Text.ElideRight
}
StyledText {
width: parent.width
text: (widget.activePlayer && widget.activePlayer.trackArtist) ? widget.activePlayer.trackArtist : ""
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
elide: Text.ElideRight
visible: text !== ""
}
StyledText {
width: parent.width
text: (widget.activePlayer && widget.activePlayer.trackAlbum) ? widget.activePlayer.trackAlbum : ""
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
elide: Text.ElideRight
visible: text !== ""
}
}
DankSeekbar {
id: seekbar
anchors.left: parent.left
anchors.right: parent.right
anchors.top: art.bottom
anchors.topMargin: Theme.spacingS
height: 20
activePlayer: widget.activePlayer
}
StyledText {
id: positionLabel
anchors.left: parent.left
anchors.top: seekbar.bottom
text: widget.formatTime(widget.activePlayer ? widget.activePlayer.position : 0)
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
StyledText {
anchors.right: parent.right
anchors.top: seekbar.bottom
text: widget.formatTime(MprisController.activePlayerStableLength)
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
visible: MprisController.activePlayerStableLength > 0
}
Row {
id: transport
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: positionLabel.bottom
anchors.topMargin: Theme.spacingXS
spacing: Theme.spacingM
DankActionButton {
anchors.verticalCenter: parent.verticalCenter
iconName: "skip_previous"
buttonSize: 38
iconSize: 22
iconColor: Theme.surfaceText
enabled: widget.hasPlayer && widget.activePlayer.canGoPrevious
onClicked: MprisController.previousOrRewind()
}
Rectangle {
anchors.verticalCenter: parent.verticalCenter
width: 46
height: 46
radius: width / 2
color: Theme.primary
opacity: (widget.hasPlayer && widget.activePlayer.canTogglePlaying) ? 1 : 0.5
DankIcon {
anchors.centerIn: parent
name: widget.isPlaying ? "pause" : "play_arrow"
size: 26
color: Theme.onPrimary
weight: 500
}
MouseArea {
anchors.fill: parent
enabled: widget.hasPlayer && widget.activePlayer.canTogglePlaying
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: widget.activePlayer.togglePlaying()
}
}
DankActionButton {
anchors.verticalCenter: parent.verticalCenter
iconName: "skip_next"
buttonSize: 38
iconSize: 22
iconColor: Theme.surfaceText
enabled: widget.hasPlayer && widget.activePlayer.canGoNext
onClicked: MprisController.next()
}
}
// Source switcher - one chip per MPRIS player that is currently around.
DankFlickable {
id: sourceRow
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
height: 30
clip: true
contentHeight: height
contentWidth: sourceChips.width
flickableDirection: Flickable.HorizontalFlick
visible: widget.players.length > 1
Row {
id: sourceChips
height: sourceRow.height
spacing: Theme.spacingXS
Repeater {
model: widget.players
delegate: Rectangle {
id: chip
required property var modelData
readonly property bool current: modelData === widget.activePlayer
width: chipLabel.implicitWidth + Theme.spacingM * 2
height: 26
anchors.verticalCenter: parent.verticalCenter
radius: height / 2
color: current ? Theme.primarySelected : (chipArea.containsMouse ? Theme.surfaceHover : Theme.withAlpha(Theme.surfaceLight, Theme.popupTransparency))
border.color: current ? Theme.primary : Theme.outlineLight
border.width: current ? 1 : Theme.layerOutlineWidth
StyledText {
id: chipLabel
anchors.centerIn: parent
text: widget.playerName(chip.modelData)
font.pixelSize: Theme.fontSizeSmall
font.weight: chip.current ? Font.Medium : Font.Normal
color: chip.current ? Theme.primary : Theme.surfaceText
}
MouseArea {
id: chipArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: MprisController.setActivePlayer(chip.modelData)
}
}
}
}
}
}
Column {
anchors.centerIn: parent
spacing: Theme.spacingS
visible: !widget.hasPlayer
DankIcon {
anchors.horizontalCenter: parent.horizontalCenter
name: "music_off"
size: Theme.iconSizeLarge
color: Theme.surfaceVariantText
}
StyledText {
anchors.horizontalCenter: parent.horizontalCenter
text: "No media player running"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
}
}