quickshell-toki-night/modules/dashboard/dash/Media.qml

243 lines
6.2 KiB
QML

import qs.config
import qs.custom
import qs.services
import qs.util
import QtQuick
import QtQuick.Shapes
Item {
id: root
anchors.fill: parent
property real playerProgress: {
const active = Players.active;
return active?.length ? active.position / active.length : 0;
}
Behavior on playerProgress {
Anim {
duration: Config.anim.durations.large
}
}
Timer {
running: Players.active?.isPlaying ?? false
interval: 400
triggeredOnStart: true
repeat: true
onTriggered: Players.active?.positionChanged()
}
Shape {
id: progress
preferredRendererType: Shape.CurveRenderer
readonly property int thickness: 8
readonly property int angle: 300
ShapePath {
id: path
fillColor: "transparent"
strokeColor: Qt.alpha(Config.colors.media, 0.2)
strokeWidth: progress.thickness
capStyle: ShapePath.RoundCap
PathAngleArc {
centerX: cover.x + cover.width / 2
centerY: cover.y + cover.height / 2
radiusX: (cover.width + progress.thickness) / 2 + 7
radiusY: (cover.height + progress.thickness) / 2 + 7
startAngle: -90 - progress.angle / 2
sweepAngle: progress.angle
}
Behavior on strokeColor {
CAnim {}
}
}
ShapePath {
fillColor: "transparent"
strokeColor: Config.colors.media
strokeWidth: progress.thickness
capStyle: ShapePath.RoundCap
PathAngleArc {
centerX: cover.x + cover.width / 2
centerY: cover.y + cover.height / 2
radiusX: (cover.width + progress.thickness) / 2 + 7
radiusY: (cover.height + progress.thickness) / 2 + 7
startAngle: -90 - progress.angle / 2
// NOTE: Cap progress angle to account for bad MPRIS players
sweepAngle: progress.angle * Math.min(root.playerProgress, 1)
}
Behavior on strokeColor {
CAnim {}
}
}
}
CustomClippingRect {
id: cover
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: 18 + progress.thickness
implicitHeight: width
color: Config.colors.inactive
radius: Infinity
MaterialIcon {
anchors.centerIn: parent
grade: 200
text: "art_track"
color: Config.colors.tertiary
font.pointSize: (parent.width * 0.4) || 1
}
Image {
id: image
anchors.fill: parent
source: Players.active?.trackArtUrl ?? ""
asynchronous: true
fillMode: Image.PreserveAspectCrop
sourceSize.width: width
sourceSize.height: height
}
}
CustomText {
id: title
anchors.top: cover.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: album.text === "" ? 24 : 18
anchors.leftMargin: 15
anchors.rightMargin: 15
animate: true
horizontalAlignment: Text.AlignHCenter
text: (Players.active?.trackTitle ?? qsTr("No media")) || qsTr("Unknown title")
color: Config.colors.secondary
font.pointSize: Config.font.size.normal
elide: Text.ElideRight
}
CustomText {
id: artist
anchors.top: title.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 15
anchors.rightMargin: 15
animate: true
horizontalAlignment: Text.AlignHCenter
text: (Players.active?.trackArtist ?? qsTr("No media")) || qsTr("Unknown artist")
opacity: Players.active ? 1 : 0
color: Config.colors.primary
elide: Text.ElideRight
}
CustomText {
id: album
anchors.top: artist.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 15
anchors.rightMargin: 15
animate: true
horizontalAlignment: Text.AlignHCenter
text: Players.active?.trackAlbum ?? ""
opacity: Players.active ? 1 : 0
color: Config.colors.tertiary
font.pointSize: Config.font.size.small
elide: Text.ElideRight
}
Row {
id: controls
anchors.top: album.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: album.text === "" ? -4 : 2
spacing: 7
Control {
icon: "skip_previous"
canUse: Players.active?.canGoPrevious ?? false
function onClicked(): void {
Players.active?.previous();
}
}
Control {
icon: Players.active?.isPlaying ? "pause" : "play_arrow"
canUse: Players.active?.canTogglePlaying ?? false
function onClicked(): void {
Players.active?.togglePlaying();
}
}
Control {
icon: "skip_next"
canUse: Players.active?.canGoNext ?? false
function onClicked(): void {
Players.active?.next();
}
}
}
component Control: CustomRect {
id: control
required property string icon
required property bool canUse
function onClicked(): void {
}
implicitWidth: Math.max(icon.implicitHeight, icon.implicitHeight) + 12
implicitHeight: implicitWidth
StateLayer {
anchors.fill: parent
disabled: !control.canUse
radius: 1000
function onClicked(): void {
control.onClicked();
}
}
MaterialIcon {
id: icon
anchors.centerIn: parent
anchors.verticalCenterOffset: font.pointSize * 0.05
animate: true
text: control.icon
color: control.canUse ? Config.colors.media : Config.colors.inactive
font.pointSize: Config.font.size.large
}
}
}