feat: add small mpris widget to dash

This commit is contained in:
Nydragon 2024-11-14 23:06:18 +01:00
parent d63a102e36
commit 4e9a6eaa4a
Signed by: nydragon
SSH key fingerprint: SHA256:WcjW5NJPQ8Dx4uQDmoIlVPLWE27Od3fxoe0IUvuoPHE
12 changed files with 259 additions and 101 deletions

View file

@ -11,6 +11,7 @@ pkgs.mkShell {
# Required for qmlls to find the correct type declarations
# Sadly Quickshell doesn't export some types declaratively
export QMLLS_BUILD_DIRS=${pkgs.kdePackages.qtdeclarative}/lib/qt-6/qml/:${quickshell}/lib/qt-6/qml/
export QML_IMPORT_PATH=$PWD/src
${pkgs.pre-commit}/bin/pre-commit install -f
'';
}

View file

@ -3,6 +3,8 @@ import QtQuick
import QtQuick.Layouts
import QtQml
import "base"
import "widgets/mpris"
import "provider"
PanelWindow {
id: homeWindow
@ -23,6 +25,7 @@ PanelWindow {
MouseArea {
id: mouse
anchors.fill: parent
onClicked: homeWindow.root.enabled = false
@ -44,6 +47,23 @@ PanelWindow {
Component.onCompleted: () => maxSize = homeWindow.screen.width * (2 / 7)
ListView {
width: parent.width
height: parent.height
model: Notifications.incoming
delegate: Rectangle {
required property var modelData
width: 100
height: 50
Text {
text: parent.modelData.appName
width: parent.width
height: parent.height
}
}
}
Behavior on width {
PropertyAnimation {
id: anim
@ -69,26 +89,7 @@ PanelWindow {
Layout.margins: 15
Layout.alignment: Qt.AlignBottom
BRectangle {
Layout.fillWidth: true
Layout.preferredHeight: 200
radius: 15
RowLayout {
anchors.fill: parent
clip: true
Rectangle {
Layout.margins: 20
Layout.preferredWidth: parent.height - (Layout.margins * 2)
Layout.preferredHeight: parent.height - (Layout.margins * 2)
Layout.maximumWidth: {
const mWidth = parent.width - (Layout.margins * 2);
return mWidth > 0 ? mWidth : 0;
}
Layout.fillHeight: true
}
}
}
MprisSmall {}
}
}
}

View file

@ -25,18 +25,13 @@ Rectangle {
maskSource: mask
}
Item {
Rectangle {
id: mask
width: image.width
height: image.height
layer.enabled: true
visible: false
Rectangle {
width: image.width
height: image.height
radius: roundedImage.radius
color: "black"
}
radius: roundedImage.radius
color: "black"
}
}

44
src/base/BlurredImage.qml Normal file
View file

@ -0,0 +1,44 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Effects
Rectangle {
id: root
required property var source
color: "transparent"
Image {
id: background
anchors.fill: parent
source: root.source
Layout.alignment: Qt.AlignHCenter
visible: false
}
MultiEffect {
id: image
autoPaddingEnabled: false
source: background
anchors.fill: background
blurEnabled: true
blurMax: 64
blurMultiplier: 2
blur: 1
brightness: -0.15
contrast: -0.35
maskEnabled: true
maskSource: mask
anchors.margins: root.border.width - 1
}
Rectangle {
id: mask
width: image.width
height: image.height
layer.enabled: true
visible: false
radius: root.radius
color: "black"
}
}

View file

@ -15,14 +15,13 @@ Singleton {
bodyMarkupSupported: false
bodySupported: true
imageSupported: true
}
Item {
Component.onCompleted: () => {
notif._.notification.connect(n => {
list.push(n);
});
onNotification: n => {
n.tracked = true;
incoming.push(n);
}
}
property var list: []
property list<Notification> backlog: notif._.trackedNotifications
property list<Notification> incoming: []
}

View file

@ -9,15 +9,22 @@ Singleton {
property var current: player.all[player.index]
property var all: Mpris.players.values
property list<MprisPlayer> all: Mpris.players.values
property int index: {
const ind = Mpris.players.values.findIndex(p => p.playbackState === MprisPlaybackState.Playing);
return ind >= 0 ? ind : 0;
}
property var next: () => {
onIndexChanged: () => {
player.current = player.all[player.index] ?? player.current;
}
function next(): void {
player.index = (player.index + 1) % all.length;
}
property var prev: () => {
function prev(): void {
const newInd = player.index - 1;
player.index = newInd < 0 ? all.length - 1 : newInd;
}

5
src/provider/qmldir Normal file
View file

@ -0,0 +1,5 @@
module Provider
singleton Player 0.1 Player.qml
singleton Inhibitor 0.1 Inhibitor.qml
singleton Notifications 0.1 Notifications.qml
singleton Time 0.1 Time.qml

View file

@ -72,7 +72,7 @@ ColumnLayout {
, ["media-playlist-repeat", MprisLoopState.Playlist] //
]
property int index: map.findIndex(e => e[1] === Player.current?.loopState)
source: loopButton.visible ? Quickshell.iconPath(map[index][0]) : ""
source: loopButton.visible && map[index] && map[index][0] ? Quickshell.iconPath(map[index][0]) : ""
onClicked: {
const ind = (index + 1) % map.length;
Player.current.loopState = map[ind][1];

View file

@ -0,0 +1,135 @@
import QtQuick.Effects
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick
import Quickshell.Services.Mpris
import Quickshell
import "../../base"
import "../../provider"
BRectangle {
id: mprisSmall
Layout.fillWidth: true
Layout.preferredHeight: 200
radius: 15
visible: Player.current ?? false
BlurredImage {
source: Player.current?.trackArtUrl ?? ""
anchors.fill: parent
radius: parent.radius
}
RowLayout {
anchors.fill: parent
clip: true
BRoundedImage {
id: im
color: "transparent"
visible: false
source: Player.current?.trackArtUrl ?? ""
radius: 15
}
MultiEffect {
id: effect
source: im
autoPaddingEnabled: true
shadowBlur: 1.0
shadowColor: 'black'
shadowEnabled: true
Layout.margins: 20
Layout.preferredWidth: parent.height - (Layout.margins * 2)
Layout.preferredHeight: parent.height - (Layout.margins * 2)
Layout.maximumWidth: {
const mWidth = parent.width - (Layout.margins * 2);
return mWidth > 0 ? mWidth : 0;
}
Layout.fillHeight: true
}
ColumnLayout {
Layout.maximumWidth: parent.width / 2
Layout.fillWidth: true
clip: true
Text {
text: Player.current?.trackTitle ?? "Unknown Track"
color: "white"
Layout.alignment: Qt.AlignCenter
Layout.maximumWidth: parent.width
elide: Text.ElideRight
}
Text {
text: Player.current?.trackAlbum ?? "Unknown Album"
color: "white"
Layout.alignment: Qt.AlignCenter
Layout.maximumWidth: parent.width
elide: Text.ElideRight
}
Text {
text: Player.current?.trackAlbumArtist ?? "Unknown Artist"
color: "white"
Layout.alignment: Qt.AlignCenter
Layout.maximumWidth: parent.width
elide: Text.ElideRight
}
RowLayout {
Layout.alignment: Qt.AlignCenter
BIconButton {
source: Quickshell.iconPath("media-seek-backward")
onClicked: Player.current.previous()
size: 20
}
BIconButton {
source: Quickshell.iconPath(Player.isPlaying ? "media-playback-pause" : "media-playback-start")
onClicked: Player.current.togglePlaying()
size: 20
}
BIconButton {
source: Quickshell.iconPath("media-seek-forward")
onClicked: Player.current.next()
size: 20
}
}
Slider {
id: slider
Layout.fillWidth: true
Layout.minimumWidth: 10
Layout.minimumHeight: 3
from: 0
to: Player.current?.length ?? 0
value: Player.current?.position ?? 0
enabled: (Player.current?.canSeek && Player.current?.positionSupported) ?? false
onMoved: {
if (Player.current)
Player.current.position = value;
}
Component.onCompleted: {
const con = () => mprisSmall.player?.positionChanged.connect(() => {
slider.value = Player.current?.position;
});
con();
Player.currentChanged.connect(() => {
con();
});
}
FrameAnimation {
// only emit the signal when the position is actually changing.
running: Player.current?.playbackState == MprisPlaybackState.Playing
// emit the positionChanged signal every frame.
onTriggered: Player.current?.positionChanged()
}
}
}
}
}

View file

@ -3,11 +3,9 @@ import Quickshell.Services.Pipewire
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Quickshell.Services.Mpris
import "root:base"
import "root:provider"
import QtQuick.Effects
import "root:widgets/MprisBig"
import "../../base"
import "../../provider"
import "../../widgets/MprisBig"
PanelWindow {
id: audioman
@ -25,53 +23,15 @@ PanelWindow {
anchors.fill: parent
onClicked: audioman.visible = false
BRectangle {
BlurredImage {
id: display
x: 10
y: 10
width: 500
height: 600
radius: 10
Image {
id: background
anchors.fill: parent
source: Player.current?.trackArtUrl ?? ""
Layout.alignment: Qt.AlignHCenter
visible: false
anchors.margins: display.border.width - 1
}
MultiEffect {
id: image
autoPaddingEnabled: false
source: background
anchors.fill: background
blurEnabled: true
blurMax: 64
blurMultiplier: 2
blur: 1
brightness: -0.15
contrast: -0.35
maskEnabled: true
maskSource: mask
}
Item {
id: mask
width: image.width
height: image.height
layer.enabled: true
visible: false
Rectangle {
width: image.width
height: image.height
radius: display.radius
color: "black"
}
}
source: Player.current?.trackArtUrl ?? ""
color: "#BD93F9"
ScrollView {
id: test

View file

@ -9,7 +9,7 @@ import Quickshell.Services.Notifications
MouseArea {
id: toast
property int lifetime: 5000
readonly property int lifetime: 5000
property int countdownTime: lifetime
required property string appName
@ -21,6 +21,7 @@ MouseArea {
required property bool hasActionIcons
required property var actions
required property int index
Component.onCompleted: () => console.log(toast.actions.values)
function close(): void {
popupcol.model.remove(toast.index, 1);
@ -125,22 +126,10 @@ MouseArea {
Repeater {
model: toast.actions
delegate: Button {
id: actionButton
delegate: ToastAction {
required property var modelData
IconImage {
visible: toast.hasActionIcons
Component.onCompleted: () => {
if (toast.hasActionIcons) {
source = actionButton.modelData.identifier;
}
}
}
text: modelData.text
onClicked: () => modelData?.invoke()
notifAction: modelData
hasIcons: toast.hasActionIcons
}
}

View file

@ -0,0 +1,22 @@
import QtQuick.Controls
import Quickshell.Widgets
import QtQuick
Button {
id: actionButton
required property var notifAction
required property bool hasIcons
IconImage {
visible: parent.hasIcons
Component.onCompleted: () => {
if (parent.hasIcons) {
source = actionButton.notifAction?.identifier ?? "";
}
}
}
text: notifAction?.text ?? ""
onClicked: () => notifAction?.invoke()
}