mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-14 15:24:00 -04:00
245 lines
10 KiB
QML
245 lines
10 KiB
QML
pragma ComponentBehavior: Bound
|
|
|
|
import QtQuick
|
|
import Quickshell.Services.Mpris
|
|
import qs.Common
|
|
import qs.Services
|
|
import qs.DCommon.Widgets
|
|
import qs.Widgets
|
|
import qs.Modules.DDash
|
|
import qs.Modules.DDash.Media
|
|
import "../../../DCommon/Common/FocusNavigation.js" as FocusNavigation
|
|
|
|
Card {
|
|
id: root
|
|
|
|
property MprisPlayer activePlayer: MprisController.activePlayer
|
|
property bool live: Window.window?.visible ?? false
|
|
property bool isSeeking: false
|
|
|
|
readonly property bool playing: activePlayer?.playbackState === MprisPlaybackState.Playing
|
|
readonly property bool compact: height < DashMetrics.heightForRows(2)
|
|
readonly property bool narrow: width - pad * 2 < DashMetrics.overviewTransportWidth
|
|
readonly property bool tiny: compact && width - pad * 2 < DashMetrics.overviewTransportWidth + Theme.iconButtonSize
|
|
readonly property bool showArtwork: !compact && !narrow && height >= DashMetrics.heightForRows(3)
|
|
readonly property string titleText: MprisController.stableTitle || I18n.tr("Unknown Track")
|
|
readonly property string artistText: MprisController.stableArtist || I18n.tr("Unknown Artist")
|
|
readonly property string artUrl: TrackArtService.resolvedArtUrl
|
|
readonly property color accent: MediaAccentService.accent
|
|
readonly property bool showSeekbar: options.seekbar !== false && !tiny
|
|
readonly property bool bento: options.playerStyle === "bento"
|
|
readonly property bool inlineMetadata: compact && titleLabel.implicitHeight + artistLabel.implicitHeight + trackText.spacing > transport.y - Theme.spacingXS
|
|
readonly property var playbackFocusTargets: [playButton, previousButton, nextButton].concat(seekbar.canSeek ? [seekbar] : [])
|
|
|
|
entryId: "media"
|
|
clickable: true
|
|
pad: Theme.spacingM
|
|
|
|
Keys.onShortcutOverride: event => {
|
|
if (!root.interactive || event.key !== Qt.Key_F6)
|
|
return;
|
|
const targets = root.playbackFocusTargets.filter(item => item.visible && item.enabled);
|
|
const boundary = event.modifiers & Qt.ShiftModifier ? targets[0] : targets[targets.length - 1];
|
|
event.accepted = targets.length > 0 && !FocusNavigation.containsFocus(boundary);
|
|
}
|
|
|
|
function handleKeyEvent(event) {
|
|
if (!interactive || event.key !== Qt.Key_F6)
|
|
return false;
|
|
return FocusNavigation.moveFocus(playbackFocusTargets, !!(event.modifiers & Qt.ShiftModifier));
|
|
}
|
|
|
|
Timer {
|
|
interval: DashMetrics.mediaPositionPollInterval
|
|
running: root.live && root.visible && root.interactive && root.showSeekbar && root.playing && !root.isSeeking
|
|
repeat: true
|
|
onTriggered: root.activePlayer?.positionSupported && root.activePlayer.positionChanged()
|
|
}
|
|
|
|
Loader {
|
|
anchors.fill: parent
|
|
anchors.margins: -root.pad
|
|
active: root.live && MediaOptions.albumArtBackdrop && !!root.activePlayer
|
|
|
|
sourceComponent: MediaArtBackdrop {
|
|
radius: root.radius
|
|
activePlayer: root.activePlayer
|
|
}
|
|
}
|
|
|
|
Column {
|
|
anchors.centerIn: parent
|
|
spacing: Theme.spacingXS
|
|
visible: !root.activePlayer
|
|
|
|
DIcon {
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
name: "music_note"
|
|
size: Theme.iconSizeLarge
|
|
color: root.accentColor
|
|
}
|
|
|
|
StyledText {
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
text: I18n.tr("No Media")
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
color: root.mutedColor
|
|
}
|
|
}
|
|
|
|
Item {
|
|
id: content
|
|
anchors.fill: parent
|
|
visible: !!root.activePlayer
|
|
enabled: root.interactive
|
|
|
|
MediaArtSurface {
|
|
id: artwork
|
|
anchors.right: parent.right
|
|
anchors.top: parent.top
|
|
width: root.showArtwork ? Math.max(0, Math.min(DashMetrics.overviewArtHero, parent.width / 3, seekBlock.y - Theme.spacingM)) : 0
|
|
height: width
|
|
artStyle: root.options.artStyle ?? "rounded"
|
|
visible: root.showArtwork
|
|
|
|
MediaArtwork {
|
|
anchors.fill: parent
|
|
cornerRadius: 0
|
|
artUrl: root.artUrl
|
|
}
|
|
}
|
|
|
|
Column {
|
|
id: trackText
|
|
anchors.left: parent.left
|
|
anchors.right: artwork.visible ? artwork.left : parent.right
|
|
anchors.rightMargin: artwork.visible ? Theme.spacingM : 0
|
|
anchors.top: parent.top
|
|
spacing: Theme.spacingXXS
|
|
visible: !root.tiny
|
|
|
|
StyledText {
|
|
id: titleLabel
|
|
width: parent.width
|
|
text: root.inlineMetadata ? root.titleText + " · " + root.artistText : root.titleText
|
|
font.pixelSize: root.compact ? Theme.fontSizeMedium : Theme.fontSizeLarge
|
|
font.weight: Theme.fontWeightMedium
|
|
color: root.contentColor
|
|
elide: Text.ElideRight
|
|
maximumLineCount: root.showArtwork ? 2 : 1
|
|
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
|
}
|
|
|
|
StyledText {
|
|
id: artistLabel
|
|
width: parent.width
|
|
visible: !root.inlineMetadata
|
|
text: root.artistText
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
color: root.mutedColor
|
|
elide: Text.ElideRight
|
|
maximumLineCount: 1
|
|
}
|
|
}
|
|
|
|
Item {
|
|
id: seekBlock
|
|
anchors.left: parent.left
|
|
anchors.right: root.compact && !root.tiny ? transport.left : parent.right
|
|
anchors.rightMargin: root.compact && !root.tiny ? Theme.spacingS : 0
|
|
y: root.compact ? transport.y + (transport.height - height) / 2 : transport.y - height - Theme.spacingS
|
|
height: DashMetrics.overviewSeekHeight
|
|
visible: root.showSeekbar
|
|
|
|
DSeekbar {
|
|
id: seekbar
|
|
anchors.fill: parent
|
|
activePlayer: root.activePlayer
|
|
stableLength: MprisController.activePlayerStableLength
|
|
accentColor: root.accent
|
|
accentTrackColor: MediaAccentService.accentTrack
|
|
accentSubtleColor: MediaAccentService.accentSubtle
|
|
isSeeking: root.isSeeking
|
|
onIsSeekingChanged: root.isSeeking = isSeeking
|
|
}
|
|
}
|
|
|
|
Item {
|
|
id: transport
|
|
|
|
readonly property bool stacked: root.narrow && !root.tiny
|
|
readonly property bool centered: root.bento && !stacked && !root.tiny
|
|
readonly property bool medium: !root.compact && width >= Theme.buttonHeightM * 4
|
|
readonly property real buttonHeight: medium ? Theme.buttonHeightM : Theme.buttonHeightS
|
|
readonly property real spacing: root.compact || stacked ? Theme.spacingXS : Theme.spacingS
|
|
|
|
anchors.right: parent.right
|
|
width: root.tiny ? parent.width : (root.compact ? DashMetrics.overviewTransportWidth : parent.width)
|
|
height: stacked ? buttonHeight * 2 + spacing : buttonHeight
|
|
y: root.tiny ? (parent.height - height) / 2 : parent.height - height
|
|
|
|
DIconButton {
|
|
id: playButton
|
|
x: transport.centered ? previousButton.width + transport.spacing : 0
|
|
anchors.top: parent.top
|
|
size: transport.medium ? "m" : "s"
|
|
width: {
|
|
if (root.tiny || transport.stacked)
|
|
return parent.width;
|
|
if (root.compact)
|
|
return DashMetrics.overviewPlayWidth;
|
|
return (parent.width - transport.spacing * 2) / 2;
|
|
}
|
|
variant: "filled"
|
|
round: false
|
|
checkable: true
|
|
checked: root.playing
|
|
iconFilled: false
|
|
radius: Theme.buttonRadius(width, height, buttonSize, pressed, !root.bento && checked)
|
|
iconName: root.playing ? "pause" : "play_arrow"
|
|
Accessible.name: root.playing ? I18n.tr("Pause") : I18n.tr("Play")
|
|
enabled: !!root.activePlayer?.canTogglePlaying
|
|
contentColor: MediaAccentService.onAccentContainer
|
|
containerColor: MediaAccentService.accentContainer
|
|
onClicked: root.activePlayer.togglePlaying()
|
|
}
|
|
|
|
DIconButton {
|
|
id: previousButton
|
|
x: transport.stacked || transport.centered ? 0 : playButton.width + transport.spacing
|
|
anchors.bottom: parent.bottom
|
|
size: transport.medium ? "m" : "s"
|
|
width: transport.stacked ? (parent.width - transport.spacing) / 2 : (parent.width - playButton.width - transport.spacing * 2) / 2
|
|
variant: root.bento ? "tonal" : "standard"
|
|
round: !root.bento
|
|
radius: Theme.buttonRadius(width, height, buttonSize, pressed, round)
|
|
containerColor: MediaAccentService.accentSecondaryContainer
|
|
contentColor: root.bento ? MediaAccentService.onAccentSecondaryContainer : Theme.onSecondaryContainer
|
|
iconName: "skip_previous"
|
|
Accessible.name: I18n.tr("Previous")
|
|
enabled: !!root.activePlayer?.canGoPrevious || (!!root.activePlayer?.canSeek && root.activePlayer.position > 8)
|
|
visible: !root.tiny
|
|
onClicked: MprisController.previousOrRewind()
|
|
}
|
|
|
|
DIconButton {
|
|
id: nextButton
|
|
anchors.right: parent.right
|
|
anchors.bottom: parent.bottom
|
|
size: transport.medium ? "m" : "s"
|
|
width: previousButton.width
|
|
variant: previousButton.variant
|
|
round: previousButton.round
|
|
radius: Theme.buttonRadius(width, height, buttonSize, pressed, round)
|
|
containerColor: previousButton.containerColor
|
|
contentColor: previousButton.contentColor
|
|
iconName: "skip_next"
|
|
Accessible.name: I18n.tr("Next")
|
|
enabled: !!root.activePlayer?.canGoNext
|
|
visible: !root.tiny
|
|
onClicked: MprisController.next()
|
|
}
|
|
}
|
|
}
|
|
}
|