pragma ComponentBehavior: Bound import Quickshell.Services.Mpris import Quickshell.Widgets import QtQuick import QtQuick.Layouts import QtQuick.Controls import qs import qs.settings import qs.widgets Rectangle { id: root color: Colors.surface radius: Settings.config.rounding * 2 implicitWidth: 600 implicitHeight: 200 visible: getSpotify() != null MouseArea { id: hoverDetect hoverEnabled: true anchors.fill: parent onExited: title.x = 0 } function getSpotify() { for (var i = 0; i < Mpris.players.values.length; i++) { if (Mpris.players.values[i].identity == "Spotify" || Mpris.players.values[i] == "spotify") { return Mpris.players.values[i]; } else { return null; } } return null; } property var spotify: getSpotify() != null ? getSpotify() : null property var title: getSpotify() != null ? getSpotify().trackTitle : "" property var album: getSpotify() != null ? getSpotify().trackAlbum : "" property var art: getSpotify() != null ? getSpotify().trackArtUrl : "" property var artist: getSpotify() != null ? getSpotify().trackArtist : "" ClippingWrapperRectangle { id: songWrapper radius: Settings.config.rounding anchors.margins: 10 margin: 10 anchors.fill: parent color: "transparent" RowLayout { id: songLayout spacing: 0 ClippingWrapperRectangle { id: coverRounder radius: 8 Image { id: songCover source: root.art sourceSize { width: 160 height: 160 } } } ColumnLayout { id: songInfo Layout.alignment: Qt.AlignVCenter Layout.fillWidth: true Layout.leftMargin: 20 Item { id: titleContainer Layout.fillWidth: true x: 0 implicitHeight: title.implicitHeight clip: true // Keeps the text inside this "window" CText { id: title Layout.maximumWidth: 300 text: root.title font.pixelSize: 30 SequentialAnimation on x { id: scrollAnimation running: hoverDetect.containsMouse && title.width > titleContainer.width loops: Animation.Infinite // Pause at the start PauseAnimation { duration: 2000 } // Scroll to the end NumberAnimation { to: titleContainer.width - title.width duration: Math.max(2000, (title.width - titleContainer.width) * 30) easing.type: Easing.InOutQuad } // Pause at the end PauseAnimation { duration: 2000 } // Scroll back to the start NumberAnimation { to: 0 duration: Math.max(2000, (title.width - titleContainer.width) * 30) easing.type: Easing.InOutQuad } } } } CText { id: album text: root.album + " - " + root.artist opacity: 0.6 Layout.maximumWidth: 250 elide: Text.ElideRight } ProgressBar { id: songProgress FrameAnimation { // only emit the signal when the position is actually changing. running: root.spotify.playbackState == MprisPlaybackState.Playing || root.visible // emit the positionChanged signal every frame. onTriggered: root.spotify.positionChanged() } implicitWidth: 200 implicitHeight: 10 from: 0 to: root.spotify != null ? root.spotify.length : 0 value: root.spotify != null ? root.spotify.position : 0 background: Rectangle { implicitWidth: 200 implicitHeight: 6 color: Colors.surface_container_highest radius: 32 } contentItem: Item { implicitWidth: 200 implicitHeight: 4 // Progress indicator for determinate state. Rectangle { width: songProgress.visualPosition * parent.width height: parent.height radius: 32 color: Colors.primary visible: !songProgress.indeterminate } } } RowLayout { id: playerControls spacing: 15 CIcon { id: previous text: "\ue045" Layout.alignment: Qt.AlignLeft MouseArea { id: prevHandler anchors.fill: parent acceptedButtons: Qt.LeftButton cursorShape: Qt.PointingHandCursor onClicked: { root.spotify.previous(); title.x = 0; } } } CIcon { id: pause text: root.spotify.isPlaying ? "\ue034" : "\ue037" MouseArea { id: pauseHandler anchors.fill: parent acceptedButtons: Qt.LeftButton cursorShape: Qt.PointingHandCursor onClicked: { root.spotify.togglePlaying(); title.x = 0; } } } CIcon { id: next Layout.alignment: Qt.AlignRight text: "\ue044" MouseArea { id: nextHandler anchors.fill: parent acceptedButtons: Qt.LeftButton cursorShape: Qt.PointingHandCursor onClicked: { root.spotify.next(); title.x = 0; } } } } } } } }