forked from Shinonome/dots-hyprland
bar: media indicator
This commit is contained in:
@@ -5,6 +5,10 @@ import QtQuick.Layouts
|
|||||||
import Quickshell
|
import Quickshell
|
||||||
|
|
||||||
Scope {
|
Scope {
|
||||||
|
id: bar
|
||||||
|
readonly property int barHeight: 40
|
||||||
|
readonly property int sideCenterModuleWidth: 360
|
||||||
|
|
||||||
Variants {
|
Variants {
|
||||||
model: Quickshell.screens
|
model: Quickshell.screens
|
||||||
|
|
||||||
@@ -14,7 +18,7 @@ Scope {
|
|||||||
property var modelData
|
property var modelData
|
||||||
|
|
||||||
screen: modelData
|
screen: modelData
|
||||||
height: 40
|
height: barHeight
|
||||||
color: Appearance.colors.colLayer0
|
color: Appearance.colors.colLayer0
|
||||||
|
|
||||||
// Left section
|
// Left section
|
||||||
@@ -25,17 +29,21 @@ Scope {
|
|||||||
// Middle section
|
// Middle section
|
||||||
RowLayout {
|
RowLayout {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
implicitWidth: 500
|
|
||||||
spacing: 8
|
spacing: 8
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
|
Layout.preferredWidth: sideCenterModuleWidth
|
||||||
spacing: 4
|
spacing: 4
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
implicitWidth: 350
|
||||||
|
|
||||||
Resources {
|
Resources {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Media {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
@@ -50,12 +58,13 @@ Scope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Layout.fillWidth: true
|
Layout.preferredWidth: sideCenterModuleWidth
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
spacing: 4
|
spacing: 4
|
||||||
|
|
||||||
ClockWidget {
|
ClockWidget {
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
UtilButtons {
|
UtilButtons {
|
||||||
|
|||||||
@@ -27,13 +27,12 @@ Rectangle {
|
|||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
implicitWidth: (isCharging ? boltIcon.width : 0) - rowLayout.spacing
|
implicitWidth: (isCharging ? boltIcon.width : 0)
|
||||||
|
|
||||||
Behavior on implicitWidth {
|
Behavior on implicitWidth {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Appearance.animation.elementDecel.duration
|
duration: Appearance.animation.elementDecel.duration
|
||||||
easing.type: Appearance.animation.elementDecel.type
|
easing.type: Appearance.animation.elementDecel.type
|
||||||
easing.bezierCurve: Appearance.animation.elementDecel.bezierCurve
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import "../common"
|
||||||
|
import "../common/widgets"
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
import Quickshell.Services.Mpris
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
readonly property MprisPlayer activePlayer: MprisController.activePlayer
|
||||||
|
readonly property string cleanedTitle: activePlayer?.trackTitle.replace(/【[^】]*】/, "") || "No media"
|
||||||
|
|
||||||
|
Layout.fillHeight: true
|
||||||
|
implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2
|
||||||
|
implicitHeight: 40
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
// Background
|
||||||
|
Rectangle {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: parent.width
|
||||||
|
implicitHeight: 32
|
||||||
|
color: Appearance.colors.colLayer1
|
||||||
|
radius: Appearance.rounding.small
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
running: activePlayer?.playbackState == MprisPlaybackState.Playing
|
||||||
|
interval: 1000
|
||||||
|
repeat: true
|
||||||
|
onTriggered: activePlayer.positionChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
acceptedButtons: Qt.MiddleButton | Qt.BackButton | Qt.ForwardButton | Qt.RightButton
|
||||||
|
onPressed: (event) => {
|
||||||
|
if (event.button === Qt.MiddleButton) {
|
||||||
|
activePlayer.togglePlaying();
|
||||||
|
} else if (event.button === Qt.BackButton) {
|
||||||
|
activePlayer.previous();
|
||||||
|
} else if (event.button === Qt.ForwardButton || event.button === Qt.RightButton) {
|
||||||
|
activePlayer.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: rowLayout
|
||||||
|
|
||||||
|
spacing: 4
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
CircularProgress {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
Layout.leftMargin: rowLayout.spacing
|
||||||
|
lineWidth: 2
|
||||||
|
value: activePlayer?.position / activePlayer?.length
|
||||||
|
size: 26
|
||||||
|
secondaryColor: Appearance.m3colors.m3secondaryContainer
|
||||||
|
primaryColor: Appearance.m3colors.m3onSecondaryContainer
|
||||||
|
|
||||||
|
MaterialSymbol {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: activePlayer?.isPlaying ? "pause" : "play_arrow"
|
||||||
|
font.pointSize: Appearance.font.pointSize.normal
|
||||||
|
color: Appearance.m3colors.m3onSecondaryContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
width: rowLayout.width - (CircularProgress.size + rowLayout.spacing * 2) // TODO ADJUST THIS
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
Layout.fillWidth: true // Ensures the text takes up available space
|
||||||
|
Layout.rightMargin: rowLayout.spacing
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
elide: Text.ElideRight // Truncates the text on the right
|
||||||
|
color: Appearance.colors.colOnLayer1
|
||||||
|
text: `${cleanedTitle} • ${activePlayer?.trackArtist}`
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -107,14 +107,12 @@ Rectangle {
|
|||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Appearance.animation.elementDecel.duration
|
duration: Appearance.animation.elementDecel.duration
|
||||||
easing.type: Appearance.animation.elementDecel.type
|
easing.type: Appearance.animation.elementDecel.type
|
||||||
easing.bezierCurve: Appearance.animation.elementDecel.bezierCurve
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Behavior on radiusLeft {
|
Behavior on radiusLeft {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Appearance.animation.elementDecel.duration
|
duration: Appearance.animation.elementDecel.duration
|
||||||
easing.type: Appearance.animation.elementDecel.type
|
easing.type: Appearance.animation.elementDecel.type
|
||||||
easing.bezierCurve: Appearance.animation.elementDecel.bezierCurve
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,7 +120,6 @@ Rectangle {
|
|||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Appearance.animation.elementDecel.duration
|
duration: Appearance.animation.elementDecel.duration
|
||||||
easing.type: Appearance.animation.elementDecel.type
|
easing.type: Appearance.animation.elementDecel.type
|
||||||
easing.bezierCurve: Appearance.animation.elementDecel.bezierCurve
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,7 +174,6 @@ Rectangle {
|
|||||||
ColorAnimation {
|
ColorAnimation {
|
||||||
duration: Appearance.animation.elementDecel.duration
|
duration: Appearance.animation.elementDecel.duration
|
||||||
easing.type: Appearance.animation.elementDecel.type
|
easing.type: Appearance.animation.elementDecel.type
|
||||||
easing.bezierCurve: Appearance.animation.elementDecel.bezierCurve
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,13 +141,12 @@ Singleton {
|
|||||||
|
|
||||||
animation: QtObject {
|
animation: QtObject {
|
||||||
property QtObject elementDecel: QtObject {
|
property QtObject elementDecel: QtObject {
|
||||||
property int duration: 100
|
property int duration: 180
|
||||||
property int type: Easing.BezierSpline
|
property int type: Easing.OutCirc
|
||||||
property list<real> bezierCurve: [0, 0.55, 0.45, 1]
|
|
||||||
}
|
}
|
||||||
property QtObject menuDecel: QtObject {
|
property QtObject menuDecel: QtObject {
|
||||||
property int duration: 250
|
property int duration: 350
|
||||||
property int type: Easing.OutCubic
|
property int type: Easing.OutQuint
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
pragma Singleton
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQml.Models
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
import Quickshell.Services.Mpris
|
||||||
|
import "../.."
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root;
|
||||||
|
property MprisPlayer trackedPlayer: null;
|
||||||
|
property MprisPlayer activePlayer: trackedPlayer ?? Mpris.players.values[0] ?? null;
|
||||||
|
signal trackChanged(reverse: bool);
|
||||||
|
|
||||||
|
property bool __reverse: false;
|
||||||
|
|
||||||
|
property var activeTrack;
|
||||||
|
|
||||||
|
Instantiator {
|
||||||
|
model: Mpris.players;
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
required property MprisPlayer modelData;
|
||||||
|
target: modelData;
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
if (root.trackedPlayer == null || modelData.isPlaying) {
|
||||||
|
root.trackedPlayer = modelData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onDestruction: {
|
||||||
|
if (root.trackedPlayer == null || !root.trackedPlayer.isPlaying) {
|
||||||
|
for (const player of Mpris.players.values) {
|
||||||
|
if (player.playbackState.isPlaying) {
|
||||||
|
root.trackedPlayer = player;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trackedPlayer == null && Mpris.players.values.length != 0) {
|
||||||
|
trackedPlayer = Mpris.players.values[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPlaybackStateChanged() {
|
||||||
|
if (root.trackedPlayer !== modelData) root.trackedPlayer = modelData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: activePlayer
|
||||||
|
|
||||||
|
function onPostTrackChanged() {
|
||||||
|
root.updateTrack();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTrackArtUrlChanged() {
|
||||||
|
console.log("arturl:", activePlayer.trackArtUrl)
|
||||||
|
//root.updateTrack();
|
||||||
|
if (root.activePlayer.uniqueId == root.activeTrack.uniqueId && root.activePlayer.trackArtUrl != root.activeTrack.artUrl) {
|
||||||
|
// cantata likes to send cover updates *BEFORE* updating the track info.
|
||||||
|
// as such, art url changes shouldn't be able to break the reverse animation
|
||||||
|
const r = root.__reverse;
|
||||||
|
root.updateTrack();
|
||||||
|
root.__reverse = r;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onActivePlayerChanged: this.updateTrack();
|
||||||
|
|
||||||
|
function updateTrack() {
|
||||||
|
//console.log(`update: ${this.activePlayer?.trackTitle ?? ""} : ${this.activePlayer?.trackArtists}`)
|
||||||
|
this.activeTrack = {
|
||||||
|
uniqueId: this.activePlayer?.uniqueId ?? 0,
|
||||||
|
artUrl: this.activePlayer?.trackArtUrl ?? "",
|
||||||
|
title: this.activePlayer?.trackTitle || "Unknown Title",
|
||||||
|
artist: this.activePlayer?.trackArtist || "Unknown Artist",
|
||||||
|
album: this.activePlayer?.trackAlbum || "Unknown Album",
|
||||||
|
};
|
||||||
|
|
||||||
|
this.trackChanged(__reverse);
|
||||||
|
this.__reverse = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
property bool isPlaying: this.activePlayer && this.activePlayer.isPlaying;
|
||||||
|
property bool canTogglePlaying: this.activePlayer?.canTogglePlaying ?? false;
|
||||||
|
function togglePlaying() {
|
||||||
|
if (this.canTogglePlaying) this.activePlayer.togglePlaying();
|
||||||
|
}
|
||||||
|
|
||||||
|
property bool canGoPrevious: this.activePlayer?.canGoPrevious ?? false;
|
||||||
|
function previous() {
|
||||||
|
if (this.canGoPrevious) {
|
||||||
|
this.__reverse = true;
|
||||||
|
this.activePlayer.previous();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property bool canGoNext: this.activePlayer?.canGoNext ?? false;
|
||||||
|
function next() {
|
||||||
|
if (this.canGoNext) {
|
||||||
|
this.__reverse = false;
|
||||||
|
this.activePlayer.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property bool canChangeVolume: this.activePlayer && this.activePlayer.volumeSupported && this.activePlayer.canControl;
|
||||||
|
|
||||||
|
property bool loopSupported: this.activePlayer && this.activePlayer.loopSupported && this.activePlayer.canControl;
|
||||||
|
property var loopState: this.activePlayer?.loopState ?? MprisLoopState.None;
|
||||||
|
function setLoopState(loopState: var) {
|
||||||
|
if (this.loopSupported) {
|
||||||
|
this.activePlayer.loopState = loopState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property bool shuffleSupported: this.activePlayer && this.activePlayer.shuffleSupported && this.activePlayer.canControl;
|
||||||
|
property bool hasShuffle: this.activePlayer?.shuffle ?? false;
|
||||||
|
function setShuffle(shuffle: bool) {
|
||||||
|
if (this.shuffleSupported) {
|
||||||
|
this.activePlayer.shuffle = shuffle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setActivePlayer(player: MprisPlayer) {
|
||||||
|
const targetPlayer = player ?? Mpris.players[0];
|
||||||
|
console.log(`setactive: ${targetPlayer} from ${activePlayer}`)
|
||||||
|
|
||||||
|
if (targetPlayer && this.activePlayer) {
|
||||||
|
this.__reverse = Mpris.players.indexOf(targetPlayer) < Mpris.players.indexOf(this.activePlayer);
|
||||||
|
} else {
|
||||||
|
// always animate forward if going to null
|
||||||
|
this.__reverse = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.trackedPlayer = targetPlayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
IpcHandler {
|
||||||
|
target: "mpris"
|
||||||
|
|
||||||
|
function pauseAll(): void {
|
||||||
|
for (const player of Mpris.players.values) {
|
||||||
|
if (player.canPause) player.pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function playPause(): void { root.togglePlaying(); }
|
||||||
|
function previous(): void { root.previous(); }
|
||||||
|
function next(): void { root.next(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,7 +24,6 @@ Button {
|
|||||||
ColorAnimation {
|
ColorAnimation {
|
||||||
duration: Appearance.animation.elementDecel.duration
|
duration: Appearance.animation.elementDecel.duration
|
||||||
easing.type: Appearance.animation.elementDecel.type
|
easing.type: Appearance.animation.elementDecel.type
|
||||||
easing.bezierCurve: Appearance.animation.elementDecel.bezierCurve
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user