180 lines
5.7 KiB
QML
180 lines
5.7 KiB
QML
import QtQuick
|
|
import QtQuick.Layouts
|
|
import Quickshell.Services.Mpris
|
|
import Caelestia.Components
|
|
import Caelestia.Config
|
|
import qs.components
|
|
import qs.components.controls
|
|
import qs.services
|
|
|
|
ColumnLayout {
|
|
id: root
|
|
|
|
function lengthStr(length: int): string {
|
|
if (length < 0)
|
|
return "-1:-1";
|
|
|
|
const hours = Math.floor(length / 3600);
|
|
const mins = Math.floor((length % 3600) / 60);
|
|
const secs = Math.floor(length % 60).toString().padStart(2, "0");
|
|
|
|
if (hours > 0)
|
|
return `${hours}:${mins.toString().padStart(2, "0")}:${secs}`;
|
|
return `${mins}:${secs}`;
|
|
}
|
|
|
|
spacing: Tokens.spacing.extraSmall
|
|
|
|
Timer {
|
|
running: Players.active?.isPlaying ?? false
|
|
interval: GlobalConfig.dashboard.mediaUpdateInterval
|
|
triggeredOnStart: true
|
|
repeat: true
|
|
onTriggered: Players.active?.positionChanged()
|
|
}
|
|
|
|
StyledText {
|
|
Layout.fillWidth: true
|
|
text: Players.active?.trackTitle ?? ""
|
|
font: Tokens.font.title.large
|
|
elide: Text.ElideRight
|
|
animate: true
|
|
}
|
|
|
|
StyledText {
|
|
Layout.fillWidth: true
|
|
text: Players.active?.trackArtist ?? qsTr("Unknown artist")
|
|
color: Colours.palette.m3onSurfaceVariant
|
|
font: Tokens.font.title.medium
|
|
elide: Text.ElideRight
|
|
animate: true
|
|
}
|
|
|
|
StyledText {
|
|
Layout.fillWidth: true
|
|
text: Players.active?.trackAlbum ?? qsTr("Unknown album")
|
|
color: Colours.palette.m3secondary
|
|
font: Tokens.font.title.medium
|
|
elide: Text.ElideRight
|
|
animate: true
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.topMargin: Tokens.spacing.extraLargeIncreased
|
|
Layout.fillWidth: true
|
|
spacing: Tokens.spacing.small
|
|
|
|
TextMetrics {
|
|
id: timeMetrics
|
|
|
|
text: Players.active ? root.lengthStr(Math.max(Players.active.position, Players.active.length)).replace(/[1-9]/g, "0") : "00:00"
|
|
font: Tokens.font.label.medium
|
|
}
|
|
|
|
StyledText {
|
|
Layout.preferredWidth: timeMetrics.width
|
|
text: root.lengthStr(Players.active?.position ?? -1)
|
|
color: Colours.palette.m3onSurfaceVariant
|
|
font: timeMetrics.font
|
|
horizontalAlignment: Text.AlignHCenter
|
|
}
|
|
|
|
StyledSlider {
|
|
Layout.fillWidth: true
|
|
value: Players.active ? Players.active.position / (Players.active.length || 1) : 0
|
|
enabled: Players.active?.canSeek ?? false
|
|
wavy: true
|
|
animateWave: Players.active?.isPlaying ?? false
|
|
waveFrequency: 5
|
|
waveDuration: 2000
|
|
onInteraction: value => {
|
|
const active = Players.active;
|
|
if (active?.canSeek && active?.positionSupported)
|
|
active.position = value * active.length;
|
|
}
|
|
}
|
|
|
|
StyledText {
|
|
Layout.preferredWidth: timeMetrics.width
|
|
text: root.lengthStr(Players.active?.length ?? -1)
|
|
color: Colours.palette.m3onSurfaceVariant
|
|
font: timeMetrics.font
|
|
horizontalAlignment: Text.AlignHCenter
|
|
}
|
|
}
|
|
|
|
ButtonRow {
|
|
Layout.topMargin: Tokens.spacing.largeIncreased
|
|
Layout.fillWidth: true
|
|
spacing: Tokens.spacing.extraSmall
|
|
|
|
IconButton {
|
|
type: IconButton.Tonal
|
|
icon: "shuffle"
|
|
isRound: true
|
|
shapeMorph: true
|
|
checked: Players.active?.shuffle ?? false
|
|
font: Tokens.font.icon.builders.medium.weight(Font.Medium).build()
|
|
disabled: !Players.active?.shuffleSupported
|
|
onClicked: Players.active.shuffle = !Players.active?.shuffle
|
|
implicitWidth: Math.round(implicitHeight * 0.9)
|
|
}
|
|
|
|
IconButton {
|
|
id: previousBtn
|
|
|
|
type: IconButton.Tonal
|
|
icon: "skip_previous"
|
|
isRound: true
|
|
shapeMorph: true
|
|
font: Tokens.font.icon.large
|
|
disabled: !Players.active?.canGoPrevious
|
|
onClicked: Players.active?.previous()
|
|
}
|
|
|
|
IconButton {
|
|
id: playPauseBtn
|
|
|
|
icon: Players.active?.isPlaying ? "pause" : "play_arrow"
|
|
isRound: true
|
|
shapeMorph: true
|
|
fillWidth: true
|
|
checked: Players.active?.isPlaying ?? false
|
|
font: Tokens.font.icon.large
|
|
disabled: !Players.active?.canTogglePlaying
|
|
onClicked: Players.active?.togglePlaying()
|
|
}
|
|
|
|
IconButton {
|
|
id: nextBtn
|
|
|
|
type: IconButton.Tonal
|
|
icon: "skip_next"
|
|
isRound: true
|
|
shapeMorph: true
|
|
font: Tokens.font.icon.large
|
|
disabled: !Players.active?.canGoNext
|
|
onClicked: Players.active?.next()
|
|
}
|
|
|
|
IconButton {
|
|
type: IconButton.Tonal
|
|
icon: Players.active?.loopState === MprisLoopState.Track ? "repeat_one" : "repeat"
|
|
isRound: true
|
|
shapeMorph: true
|
|
checked: Players.active?.loopState === MprisLoopState.Track || Players.active?.loopState === MprisLoopState.Playlist
|
|
font: Tokens.font.icon.builders.medium.weight(Font.Medium).build()
|
|
disabled: !Players.active?.loopSupported
|
|
onClicked: {
|
|
const state = Players.active.loopState;
|
|
if (state === MprisLoopState.None)
|
|
Players.active.loopState = MprisLoopState.Track;
|
|
else if (state === MprisLoopState.Track)
|
|
Players.active.loopState = MprisLoopState.Playlist;
|
|
else
|
|
Players.active.loopState = MprisLoopState.None;
|
|
}
|
|
implicitWidth: Math.round(implicitHeight * 0.9)
|
|
}
|
|
}
|
|
}
|