Initial commit

This commit is contained in:
kossLAN 2025-06-07 04:01:14 -04:00
commit 05cd51b54e
Signed by: kossLAN
SSH key fingerprint: SHA256:bdV0x+wdQHGJ6LgmstH3KV8OpWY+OOFmJcPcB0wQPV8
148 changed files with 10112 additions and 0 deletions

View file

@ -0,0 +1,238 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Qt5Compat.GraphicalEffects
import Quickshell
import "../.."
import "../../widgets" as Widgets
Rectangle {
required property var player
radius: 5
color: "transparent"
implicitHeight: 220
RowLayout {
id: cardLayout
spacing: 15
anchors {
fill: parent
leftMargin: 10
rightMargin: 10
topMargin: 10 // Added top margin for better spacing
bottomMargin: 10 // Added bottom margin for better spacing
}
Rectangle {
id: mprisImage
color: "transparent"
radius: 10
width: 200
height: 200
Layout.alignment: Qt.AlignVCenter
visible: true
Image {
anchors.fill: parent
source: player.trackArtUrl
sourceSize.width: 256
sourceSize.height: 256
fillMode: Image.PreserveAspectFit
layer.enabled: true
layer.effect: OpacityMask {
source: Rectangle {
width: mprisImage.width
height: mprisImage.height
radius: 10
color: "white"
}
maskSource: Rectangle {
width: mprisImage.width
height: mprisImage.height
radius: 10
color: "black"
}
layer.enabled: true
layer.effect: DropShadow {
transparentBorder: true
spread: 0.02
samples: 25
color: "#80000000"
}
}
}
}
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 5
Text {
text: player.trackArtist
color: ShellGlobals.colors.text
font.pointSize: 13
font.bold: true
horizontalAlignment: Text.AlignHCenter
Layout.fillWidth: true
elide: Text.ElideRight
}
Text {
text: player.trackTitle
color: ShellGlobals.colors.text
font.pointSize: 13
horizontalAlignment: Text.AlignHCenter
Layout.fillWidth: true
elide: Text.ElideRight
}
RowLayout {
spacing: 2
Text {
text: timeStr(player.position)
color: ShellGlobals.colors.text
font {
pointSize: 9
bold: true
}
}
ColorQuantizer {
id: colorQuantizer
source: Qt.resolvedUrl(Media.trackedPlayer?.trackArtUrl ?? "")
depth: 0
rescaleSize: 64
}
Slider {
id: slider
from: 0
to: player.length
enabled: false
//enabled: player.canSeek
value: player.position
implicitHeight: 7
Layout.fillWidth: true
Layout.margins: 10
Layout.leftMargin: 5
Layout.rightMargin: 5
Layout.alignment: Qt.AlignBottom
background: Rectangle {
id: sliderContainer
width: slider.availableWidth
height: slider.implicitHeight
color: "white"
radius: 4
layer.enabled: true
layer.effect: OpacityMask {
source: Rectangle {
width: sliderContainer.width
height: sliderContainer.height
radius: sliderContainer.radius
color: "white"
}
maskSource: Rectangle {
width: sliderContainer.width
height: sliderContainer.height
radius: sliderContainer.radius
color: "black"
}
}
Rectangle {
id: handle
width: sliderContainer.width * (slider.value / slider.to)
height: sliderContainer.height
color: colorQuantizer.colors[0].darker(1.2)
Behavior on width {
NumberAnimation {
duration: 100
easing.type: Easing.OutQuad
}
}
}
}
handle: Rectangle {
x: slider.leftPadding + slider.visualPosition * (slider.availableWidth - width)
y: slider.topPadding + slider.availableHeight / 2 - height / 2
width: 16
height: 16
radius: width / 2
color: colorQuantizer.colors[0].darker(1.4)
layer.enabled: true
layer.effect: DropShadow {
horizontalOffset: 0
verticalOffset: 1
radius: 4.0
samples: 9
color: "#30000000"
}
}
}
Text {
text: timeStr(player.length)
color: ShellGlobals.colors.text
font {
pointSize: 9
bold: true
}
}
}
// Music Controls
RowLayout {
spacing: 2
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
Widgets.IconButton {
implicitSize: 36
padding: 4
source: "root:resources/mpris/previous.svg"
onClicked: player.previous()
}
Widgets.IconButton {
implicitSize: 36
padding: 4
source: player?.isPlaying ? "root:resources/mpris/pause.svg" : "root:resources/mpris/play.svg"
onClicked: {
if (!player.canPlay)
return;
player.isPlaying ? player.pause() : player.play();
}
}
Widgets.IconButton {
implicitSize: 36
padding: 4
source: "root:resources/mpris/next.svg"
onClicked: player.next()
}
}
}
}
function timeStr(time: int): string {
const seconds = time % 60;
const minutes = Math.floor(time / 60);
return `${minutes}:${seconds.toString().padStart(2, '0')}`;
}
}

View file

@ -0,0 +1,95 @@
import QtQuick
import Quickshell.Services.Mpris
import Quickshell.Widgets
import "../.."
Item {
required property var bar;
width: statusInfo.width;
height: parent.height;
MediaSwitcher {
id: mediaSwitcher;
anchor.window: bar;
}
MouseArea {
id: playButton;
hoverEnabled: true;
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: (mouse)=> {
if (mouse.button === Qt.LeftButton) {
mediaSwitcher.visible = !mediaSwitcher.visible;
} else {
if (!Media.trackedPlayer.canPlay || Media.trackedPlayer == null)
return;
if (Media.trackedPlayer.isPlaying)
Media.trackedPlayer.pause();
else
Media.trackedPlayer.play();
}
}
anchors.fill: parent;
}
Item {
id: statusInfo;
width: statusIcon.width + statusIcon.anchors.rightMargin + nowPlayingText.width;
visible: Media.trackedPlayer != null;
anchors {
horizontalCenter: parent.horizontalCenter;
verticalCenter: parent.verticalCenter;
top: parent.top;
bottom: parent.botton;
margins: 3.5;
}
Rectangle {
color: ShellGlobals.colors.innerHighlight;
border.color: ShellGlobals.colors.highlight;
radius: 5;
width: parent.width + 25;
height: parent.height;
visible: playButton.containsMouse;
anchors.centerIn: parent;
}
IconImage {
id: statusIcon;
implicitSize: 13;
source: Media.trackedPlayer?.isPlaying
? Qt.resolvedUrl("../../resources/mpris/pause.svg")
: Qt.resolvedUrl("../../resources/mpris/play.svg");
anchors {
verticalCenter: parent.verticalCenter;
right: nowPlayingText.left;
rightMargin: 10;
}
}
Text {
id: nowPlayingText
color: ShellGlobals.colors.text;
text: `${Media.trackedPlayer?.trackArtist} - ${Media.trackedPlayer?.trackTitle}`;
font.pointSize: 11;
elide: Text.ElideRight;
anchors {
verticalCenter: parent.verticalCenter;
right: parent.right;
}
}
}
function truncate(text) {
if (text?.length > 40) {
return text.substring(0, 40) + " ..."
}
return text
}
}

View file

@ -0,0 +1,96 @@
import QtQuick
import Quickshell.Services.Mpris
import Quickshell.Widgets
import "../.."
Item {
required property var bar;
width: statusInfo.width;
height: parent.height;
MediaSwitcher {
id: mediaSwitcher;
anchor.window: bar;
}
MouseArea {
id: playButton;
hoverEnabled: true;
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: (mouse)=> {
if (mouse.button === Qt.LeftButton) {
mediaSwitcher.visible = !mediaSwitcher.visible;
} else {
if (!Media.trackedPlayer.canPlay || Media.trackedPlayer == null)
return;
if (Media.trackedPlayer.isPlaying)
Media.trackedPlayer.pause();
else
Media.trackedPlayer.play();
}
}
anchors.fill: parent;
}
Item {
id: statusInfo;
width: statusIcon.width + statusIcon.anchors.rightMargin + nowPlayingText.width;
visible: Media.trackedPlayer != null;
anchors {
horizontalCenter: parent.horizontalCenter;
verticalCenter: parent.verticalCenter;
top: parent.top;
bottom: parent.botton;
margins: 3.5;
}
Rectangle {
color: ShellGlobals.colors.innerHighlight;
border.color: ShellGlobals.colors.highlight;
radius: 5;
width: parent.width + 25;
height: parent.height;
visible: playButton.containsMouse;
anchors.centerIn: parent;
}
IconImage {
id: statusIcon;
implicitSize: 13;
source: Media.trackedPlayer?.isPlaying
? Qt.resolvedUrl("../../resources/mpris/pause.svg")
: Qt.resolvedUrl("../../resources/mpris/play.svg");
anchors {
verticalCenter: parent.verticalCenter;
right: nowPlayingText.left;
rightMargin: 10;
}
}
Text {
id: nowPlayingText
color: ShellGlobals.colors.text;
text: `${Media.trackedPlayer?.trackArtist} - ${Media.trackedPlayer?.trackTitle}`;
font.pointSize: 11;
elide: Text.ElideRight;
contentWidth: 100;
anchors {
verticalCenter: parent.verticalCenter;
right: parent.right;
}
}
}
function truncate(text) {
if (text?.length > 40) {
return text.substring(0, 40) + " ..."
}
return text
}
}

View file

@ -0,0 +1,96 @@
import QtQuick
import Quickshell.Services.Mpris
import Quickshell.Widgets
import "../.."
Item {
required property var bar;
width: statusInfo.width;
height: parent.height;
MediaSwitcher {
id: mediaSwitcher;
anchor.window: bar;
}
MouseArea {
id: playButton;
hoverEnabled: true;
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: (mouse)=> {
if (mouse.button === Qt.LeftButton) {
mediaSwitcher.visible = !mediaSwitcher.visible;
} else {
if (!Media.trackedPlayer.canPlay || Media.trackedPlayer == null)
return;
if (Media.trackedPlayer.isPlaying)
Media.trackedPlayer.pause();
else
Media.trackedPlayer.play();
}
}
anchors.fill: parent;
}
Item {
id: statusInfo;
width: statusIcon.width + statusIcon.anchors.rightMargin + nowPlayingText.width;
visible: Media.trackedPlayer != null;
anchors {
horizontalCenter: parent.horizontalCenter;
verticalCenter: parent.verticalCenter;
top: parent.top;
bottom: parent.botton;
margins: 3.5;
}
Rectangle {
color: ShellGlobals.colors.innerHighlight;
border.color: ShellGlobals.colors.highlight;
radius: 5;
width: parent.width + 25;
height: parent.height;
visible: playButton.containsMouse;
anchors.centerIn: parent;
}
IconImage {
id: statusIcon;
implicitSize: 13;
source: Media.trackedPlayer?.isPlaying
? Qt.resolvedUrl("../../resources/mpris/pause.svg")
: Qt.resolvedUrl("../../resources/mpris/play.svg");
anchors {
verticalCenter: parent.verticalCenter;
right: nowPlayingText.left;
rightMargin: 10;
}
}
Text {
id: nowPlayingText
color: ShellGlobals.colors.text;
text: `${Media.trackedPlayer?.trackArtist} - ${Media.trackedPlayer?.trackTitle}`;
font.pointSize: 11;
elide: Text.ElideRight;
width: 100;
anchors {
verticalCenter: parent.verticalCenter;
right: parent.right;
}
}
}
function truncate(text) {
if (text?.length > 40) {
return text.substring(0, 40) + " ..."
}
return text
}
}

View file

@ -0,0 +1,96 @@
import QtQuick
import Quickshell.Services.Mpris
import Quickshell.Widgets
import "../.."
Item {
required property var bar;
width: statusInfo.width;
height: parent.height;
MediaSwitcher {
id: mediaSwitcher;
anchor.window: bar;
}
MouseArea {
id: playButton;
hoverEnabled: true;
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: (mouse)=> {
if (mouse.button === Qt.LeftButton) {
mediaSwitcher.visible = !mediaSwitcher.visible;
} else {
if (!Media.trackedPlayer.canPlay || Media.trackedPlayer == null)
return;
if (Media.trackedPlayer.isPlaying)
Media.trackedPlayer.pause();
else
Media.trackedPlayer.play();
}
}
anchors.fill: parent;
}
Item {
id: statusInfo;
width: statusIcon.width + statusIcon.anchors.rightMargin + nowPlayingText.width;
visible: Media.trackedPlayer != null;
anchors {
horizontalCenter: parent.horizontalCenter;
verticalCenter: parent.verticalCenter;
top: parent.top;
bottom: parent.botton;
margins: 3.5;
}
Rectangle {
color: ShellGlobals.colors.innerHighlight;
border.color: ShellGlobals.colors.highlight;
radius: 5;
width: parent.width + 25;
height: parent.height;
visible: playButton.containsMouse;
anchors.centerIn: parent;
}
IconImage {
id: statusIcon;
implicitSize: 13;
source: Media.trackedPlayer?.isPlaying
? Qt.resolvedUrl("../../resources/mpris/pause.svg")
: Qt.resolvedUrl("../../resources/mpris/play.svg");
anchors {
verticalCenter: parent.verticalCenter;
right: nowPlayingText.left;
rightMargin: 10;
}
}
Text {
id: nowPlayingText
color: ShellGlobals.colors.text;
text: `${Media.trackedPlayer?.trackArtist} - ${Media.trackedPlayer?.trackTitle}`;
font.pointSize: 11;
width: 250;
elide: Text.ElideRight;
anchors {
verticalCenter: parent.verticalCenter;
right: parent.right;
}
}
}
function truncate(text) {
if (text?.length > 40) {
return text.substring(0, 40) + " ..."
}
return text
}
}

View file

@ -0,0 +1,96 @@
import QtQuick
import Quickshell.Services.Mpris
import Quickshell.Widgets
import "../.."
Item {
required property var bar;
width: statusInfo.width;
height: parent.height;
MediaSwitcher {
id: mediaSwitcher;
anchor.window: bar;
anchor.rect.x: parentWindow.width / 2 - width / 2;
anchor.rect.y: parentWindow.height;
}
MouseArea {
id: playButton;
hoverEnabled: true;
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: (mouse)=> {
if (mouse.button === Qt.LeftButton) {
if (mediaSwitcher.visible) {
mediaSwitcher.hide();
} else {
mediaSwitcher.show();
}
//mediaSwitcher.visible = !mediaSwitcher.visible;
} else {
if (!Media.trackedPlayer.canPlay || Media.trackedPlayer == null)
return;
if (Media.trackedPlayer.isPlaying)
Media.trackedPlayer.pause();
else
Media.trackedPlayer.play();
}
}
anchors.fill: parent;
}
Item {
id: statusInfo;
width: statusIcon.width + statusIcon.anchors.rightMargin + nowPlayingText.width;
visible: Media.trackedPlayer != null;
anchors {
horizontalCenter: parent.horizontalCenter;
verticalCenter: parent.verticalCenter;
top: parent.top;
bottom: parent.botton;
margins: 3.5;
}
Rectangle {
color: ShellGlobals.colors.innerHighlight;
border.color: ShellGlobals.colors.highlight;
radius: 3;
width: parent.width + 25;
height: parent.height;
visible: playButton.containsMouse;
anchors.centerIn: parent;
}
IconImage {
id: statusIcon;
implicitSize: 13;
source: Media.trackedPlayer?.isPlaying
? "root:resources/mpris/pause.svg"
: "root:resources/mpris/play.svg";
anchors {
verticalCenter: parent.verticalCenter;
right: nowPlayingText.left;
rightMargin: 10;
}
}
Text {
id: nowPlayingText
color: ShellGlobals.colors.text;
text: `${Media.trackedPlayer?.trackArtist} - ${Media.trackedPlayer?.trackTitle}`;
font.pointSize: 11;
width: Math.min(implicitWidth, 250);
elide: Text.ElideRight;
anchors {
verticalCenter: parent.verticalCenter;
right: parent.right;
}
}
}
}

View file

@ -0,0 +1,122 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import "../.."
PopupWindow {
id: root;
width: 500
height: 500
color: "transparent";
visible: false;
anchor.rect.x: parentWindow.width / 2 - width / 2
anchor.rect.y: parentWindow.height + 5;
Rectangle {
color: ShellGlobals.colors.window;
radius:5;
border.color: mouse.hovered
? ShellGlobals.colors.highlight
: ShellGlobals.colors.light;
border.width: 2;
anchors.fill: parent;
// NOTE: You cannot stack mouseArea's that have hovered enabled.
// This is the workaround for panel hover detection.
HoverHandler {
id: mouse
enabled: true;
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad;
onHoveredChanged: {
if (hovered == false) {
root.visible = false;
}
}
}
ColumnLayout {
spacing: 5;
anchors {
horizontalCenter: parent.horizontalCenter;
top: parent.top;
margins: 20;
}
Image {
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: 300
Layout.preferredHeight: 300
source: Media.trackedPlayer.trackArtUrl
fillMode: Image.PreserveAspectFit
sourceSize {
width: 512
height: 512
}
}
Text {
text: truncate(Media.trackedPlayer.trackArtist);
color: ShellGlobals.colors.text;
font.pointSize: 13;
Layout.alignment: Qt.AlignHCenter;
Layout.topMargin: 20;
}
Text {
text: truncate(Media.trackedPlayer.trackTitle);
color: ShellGlobals.colors.text;
font.pointSize: 13;
Layout.alignment: Qt.AlignHCenter;
}
RowLayout {
spacing: 20;
Layout.alignment: Qt.AlignHCenter;
Layout.topMargin: 10;
IconButton {
implicitSize: 32
source: Qt.resolvedUrl("../../resources/mpris/previous.svg");
onClicked: {
Media.trackedPlayer.previous();
}
}
IconButton {
implicitSize: 36;
source: Media.trackedPlayer?.isPlaying
? Qt.resolvedUrl("../../resources/mpris/pause.svg")
: Qt.resolvedUrl("../../resources/mpris/play.svg");
onClicked: {
if (!Media.trackedPlayer.canPlay || Media.trackedPlayer == null)
return;
if (Media.trackedPlayer.isPlaying)
Media.trackedPlayer.pause();
else
Media.trackedPlayer.play();
}
}
IconButton {
implicitSize: 32;
source: Qt.resolvedUrl("../../resources/mpris/next.svg");
onClicked: {
Media.trackedPlayer.next();
}
}
}
}
}
// TODO: make this some sort of global function
function truncate(text) {
if (text?.length > 60) {
return text.substring(0, 60) + " ..."
}
return text
}
}

View file

@ -0,0 +1,179 @@
import QtQuick
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell
import Quickshell.Services.Mpris
import "../.."
PopupWindow {
id: root
width: mediaPlayerContainer.width + 15;
height: mediaPlayerContainer.height + 15;
color: "transparent"
visible: false
anchor.rect.x: parentWindow.width / 2 - width / 2
anchor.rect.y: parentWindow.height;
HoverHandler {
id: hoverHandler
enabled: true
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
onHoveredChanged: {
if (hovered == false) {
root.visible = false
}
}
}
Rectangle {
id: mediaPlayerContainer;
width: 500;
height: mediaPlayerColumn.height + 20;
color: ShellGlobals.colors.window
radius: 5
layer.enabled: true
layer.effect: DropShadow {
transparentBorder: true;
spread: 0.02;
samples: 25;
color: "#80000000";
}
anchors.centerIn: parent;
//border.color: hoverHandler.hovered
// ? ShellGlobals.colors.highlight
// : ShellGlobals.colors.light
//border.width: 2
ColumnLayout {
id: mediaPlayerColumn;
spacing: 10
anchors {
top: parent.top
left: parent.left
right: parent.right
margins: 10
}
Repeater {
model: Mpris.players
Rectangle {
// TODO: do color quant for a background gradient and then blur it
required property var modelData;
radius: 5;
color: ShellGlobals.colors.light;
height: 80
Layout.fillWidth: true
RowLayout {
spacing: 15
anchors {
fill: parent
margins: 10
}
Item {
Layout.preferredWidth: 60
Layout.preferredHeight: 60
Rectangle {
id: mask
anchors.fill: parent
radius: 5;
visible: false
}
Image {
anchors.fill: parent
source: modelData.trackArtUrl
fillMode: Image.PreserveAspectFit
layer.enabled: true
layer.effect: OpacityMask {
maskSource: mask
}
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 5
Layout.alignment: Qt.AlignVCenter
Text {
text: modelData.trackArtist;
color: ShellGlobals.colors.text
font.pointSize: 13
font.bold: true
Layout.alignment: Qt.AlignLeft
Layout.fillWidth: true
width: 350;
elide: Text.ElideRight;
}
Text {
text: modelData.trackTitle;
color: ShellGlobals.colors.text;
font.pointSize: 13;
Layout.alignment: Qt.AlignLeft;
Layout.fillWidth: true;
//width: 350;
elide: Text.ElideRight;
}
}
// Spacer to push controls to the right
Item {
Layout.fillWidth: true
Layout.minimumWidth: 20
}
// Controls container
RowLayout {
spacing: 2
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
IconButton {
implicitSize: 24
source: Qt.resolvedUrl("../../resources/mpris/previous.svg")
onClicked: modelData.previous()
}
IconButton {
implicitSize: 24
source: modelData?.isPlaying
? Qt.resolvedUrl("../../resources/mpris/pause.svg")
: Qt.resolvedUrl("../../resources/mpris/play.svg")
onClicked: {
if (!modelData.canPlay)
return
modelData.isPlaying
? modelData.pause()
: modelData.play()
}
}
IconButton {
implicitSize: 24
source: Qt.resolvedUrl("../../resources/mpris/next.svg")
onClicked: modelData.next()
}
}
}
}
}
}
}
function truncate(text) {
if (text?.length > 30) {
return text.substring(0, 30) + " ..."
}
return text
}
}

View file

@ -0,0 +1,178 @@
import QtQuick
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell
import Quickshell.Services.Mpris
import "../.."
PopupWindow {
id: root
width: mediaPlayerContainer.width + 15;
height: mediaPlayerContainer.height + 15;
color: "transparent"
visible: false
anchor.rect.x: parentWindow.width / 2 - width / 2
anchor.rect.y: parentWindow.height;
HoverHandler {
id: hoverHandler
enabled: true
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
onHoveredChanged: {
if (hovered == false) {
root.visible = false
}
}
}
Rectangle {
id: mediaPlayerContainer;
width: 500;
height: mediaPlayerColumn.height + 20;
color: ShellGlobals.colors.window
radius: 5
layer.enabled: true
layer.effect: DropShadow {
transparentBorder: true;
spread: 0.02;
samples: 25;
color: "#80000000";
}
anchors.centerIn: parent;
//border.color: hoverHandler.hovered
// ? ShellGlobals.colors.highlight
// : ShellGlobals.colors.light
//border.width: 2
ColumnLayout {
id: mediaPlayerColumn;
spacing: 10
anchors {
top: parent.top
left: parent.left
right: parent.right
margins: 10
}
Repeater {
model: Mpris.players
Rectangle {
// TODO: do color quant for a background gradient and then blur it
required property var modelData;
radius: 5;
color: ShellGlobals.colors.light;
height: 80
Layout.fillWidth: true
RowLayout {
spacing: 15
anchors {
fill: parent
margins: 10
}
Item {
Layout.preferredWidth: 60
Layout.preferredHeight: 60
Rectangle {
id: mask
anchors.fill: parent
radius: 5;
visible: false
}
Image {
anchors.fill: parent
source: modelData.trackArtUrl
fillMode: Image.PreserveAspectFit
layer.enabled: true
layer.effect: OpacityMask {
maskSource: mask
}
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 5
Layout.alignment: Qt.AlignVCenter
Text {
text: modelData.trackArtist;
color: ShellGlobals.colors.text
font.pointSize: 13
font.bold: true
Layout.alignment: Qt.AlignLeft
Layout.fillWidth: true
width: 350;
elide: Text.ElideRight;
}
Text {
text: modelData.trackTitle;
color: ShellGlobals.colors.text;
font.pointSize: 13;
Layout.alignment: Qt.AlignLeft;
Layout.fillWidth: true;
elide: Text.ElideRight;
}
}
// Spacer to push controls to the right
Item {
Layout.fillWidth: true
Layout.minimumWidth: 20
}
// Controls container
RowLayout {
spacing: 2
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
IconButton {
implicitSize: 24
source: Qt.resolvedUrl("../../resources/mpris/previous.svg")
onClicked: modelData.previous()
}
IconButton {
implicitSize: 24
source: modelData?.isPlaying
? Qt.resolvedUrl("../../resources/mpris/pause.svg")
: Qt.resolvedUrl("../../resources/mpris/play.svg")
onClicked: {
if (!modelData.canPlay)
return
modelData.isPlaying
? modelData.pause()
: modelData.play()
}
}
IconButton {
implicitSize: 24
source: Qt.resolvedUrl("../../resources/mpris/next.svg")
onClicked: modelData.next()
}
}
}
}
}
}
}
function truncate(text) {
if (text?.length > 30) {
return text.substring(0, 30) + " ..."
}
return text
}
}

View file

@ -0,0 +1,178 @@
import QtQuick
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell
import Quickshell.Services.Mpris
import "../.."
PopupWindow {
id: root
width: mediaPlayerContainer.width + 15;
height: mediaPlayerContainer.height + 15;
color: "transparent"
visible: false
anchor.rect.x: parentWindow.width / 2 - width / 2
anchor.rect.y: parentWindow.height;
HoverHandler {
id: hoverHandler
enabled: true
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
onHoveredChanged: {
if (hovered == false) {
root.visible = false
}
}
}
Rectangle {
id: mediaPlayerContainer;
width: 500;
height: mediaPlayerColumn.height + 20;
color: ShellGlobals.colors.window
radius: 5
layer.enabled: true
layer.effect: DropShadow {
transparentBorder: true;
spread: 0.02;
samples: 25;
color: "#80000000";
}
anchors.centerIn: parent;
//border.color: hoverHandler.hovered
// ? ShellGlobals.colors.highlight
// : ShellGlobals.colors.light
//border.width: 2
ColumnLayout {
id: mediaPlayerColumn;
spacing: 10
anchors {
top: parent.top
left: parent.left
right: parent.right
margins: 10
}
Repeater {
model: Mpris.players
Rectangle {
// TODO: do color quant for a background gradient and then blur it
required property var modelData;
radius: 5;
color: ShellGlobals.colors.light;
height: 80
Layout.fillWidth: true
RowLayout {
spacing: 15
anchors {
fill: parent
margins: 10
}
Item {
Layout.preferredWidth: 60
Layout.preferredHeight: 60
Rectangle {
id: mask
anchors.fill: parent
radius: 5;
visible: false
}
Image {
anchors.fill: parent
source: modelData.trackArtUrl
fillMode: Image.PreserveAspectFit
layer.enabled: true
layer.effect: OpacityMask {
maskSource: mask
}
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 5
Layout.alignment: Qt.AlignVCenter
Text {
text: modelData.trackArtist;
color: ShellGlobals.colors.text
font.pointSize: 13
font.bold: true
Layout.alignment: Qt.AlignLeft
Layout.fillWidth: true
//width: 350;
elide: Text.ElideRight;
}
Text {
text: modelData.trackTitle;
color: ShellGlobals.colors.text;
font.pointSize: 13;
Layout.alignment: Qt.AlignLeft;
Layout.fillWidth: true;
elide: Text.ElideRight;
}
}
// Spacer to push controls to the right
//Item {
// Layout.fillWidth: true
// Layout.minimumWidth: 20
//}
// Controls container
RowLayout {
spacing: 2
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
IconButton {
implicitSize: 24
source: Qt.resolvedUrl("../../resources/mpris/previous.svg")
onClicked: modelData.previous()
}
IconButton {
implicitSize: 24
source: modelData?.isPlaying
? Qt.resolvedUrl("../../resources/mpris/pause.svg")
: Qt.resolvedUrl("../../resources/mpris/play.svg")
onClicked: {
if (!modelData.canPlay)
return
modelData.isPlaying
? modelData.pause()
: modelData.play()
}
}
IconButton {
implicitSize: 24
source: Qt.resolvedUrl("../../resources/mpris/next.svg")
onClicked: modelData.next()
}
}
}
}
}
}
}
function truncate(text) {
if (text?.length > 30) {
return text.substring(0, 30) + " ..."
}
return text
}
}

View file

@ -0,0 +1,171 @@
import QtQuick
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell
import Quickshell.Services.Mpris
import "../.."
PopupWindow {
id: root
width: mediaPlayerContainer.width + 15;
height: mediaPlayerContainer.height + 15;
color: "transparent"
visible: false
anchor.rect.x: parentWindow.width / 2 - width / 2
anchor.rect.y: parentWindow.height;
HoverHandler {
id: hoverHandler
enabled: true
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
onHoveredChanged: {
if (hovered == false) {
root.visible = false
}
}
}
Rectangle {
id: mediaPlayerContainer;
width: 500;
height: mediaPlayerColumn.height + 20;
color: ShellGlobals.colors.window
radius: 5
layer.enabled: true
layer.effect: DropShadow {
transparentBorder: true;
spread: 0.02;
samples: 25;
color: "#80000000";
}
anchors.centerIn: parent;
//border.color: hoverHandler.hovered
// ? ShellGlobals.colors.highlight
// : ShellGlobals.colors.light
//border.width: 2
ColumnLayout {
id: mediaPlayerColumn;
spacing: 10
anchors {
top: parent.top
left: parent.left
right: parent.right
margins: 10
}
Repeater {
model: Mpris.players
Rectangle {
// TODO: do color quant for a background gradient and then blur it
required property var modelData;
radius: 5;
color: ShellGlobals.colors.light;
height: 80
Layout.fillWidth: true
RowLayout {
spacing: 15
anchors {
fill: parent
margins: 10
}
Item {
Layout.preferredWidth: 60
Layout.preferredHeight: 60
Rectangle {
id: mask
anchors.fill: parent
radius: 5;
visible: false
}
Image {
anchors.fill: parent
source: modelData.trackArtUrl
fillMode: Image.PreserveAspectFit
layer.enabled: true
layer.effect: OpacityMask {
maskSource: mask
}
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 5
Layout.alignment: Qt.AlignVCenter
Text {
text: modelData.trackArtist;
color: ShellGlobals.colors.text
font.pointSize: 13
font.bold: true
Layout.alignment: Qt.AlignLeft
Layout.fillWidth: true
elide: Text.ElideRight;
}
Text {
text: modelData.trackTitle;
color: ShellGlobals.colors.text;
font.pointSize: 13;
Layout.alignment: Qt.AlignLeft;
Layout.fillWidth: true;
elide: Text.ElideRight;
}
}
// Controls container
RowLayout {
spacing: 2
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
IconButton {
implicitSize: 24
source: Qt.resolvedUrl("../../resources/mpris/previous.svg")
onClicked: modelData.previous()
}
IconButton {
implicitSize: 24
source: modelData?.isPlaying
? Qt.resolvedUrl("../../resources/mpris/pause.svg")
: Qt.resolvedUrl("../../resources/mpris/play.svg")
onClicked: {
if (!modelData.canPlay)
return
modelData.isPlaying
? modelData.pause()
: modelData.play()
}
}
IconButton {
implicitSize: 24
source: Qt.resolvedUrl("../../resources/mpris/next.svg")
onClicked: modelData.next()
}
}
}
}
}
}
}
function truncate(text) {
if (text?.length > 30) {
return text.substring(0, 30) + " ..."
}
return text
}
}

View file

@ -0,0 +1,191 @@
import QtQuick
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell
import Quickshell.Services.Mpris
import "../../widgets/" as Widgets
import "../.."
PopupWindow {
id: root
width: mediaPlayerContainer.width + 10;
height: mediaPlayerContainer.height + 10;
color: "transparent"
visible: mediaPlayerContainer.opacity > 0;
anchor.rect.x: parentWindow.width / 2 - width / 2;
anchor.rect.y: parentWindow.height;
function show() {
mediaPlayerContainer.opacity = 1;
}
function hide() {
mediaPlayerContainer.opacity = 0;
}
HoverHandler {
id: hoverHandler;
enabled: true;
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad;
onHoveredChanged: {
if (hovered == false) {
hide();
}
}
}
Rectangle {
id: mediaPlayerContainer;
width: 500;
height: mediaPlayerColumn.height + 20;
color: ShellGlobals.colors.window;
radius: 5;
opacity: 0;
layer.enabled: true;
layer.effect: DropShadow {
transparentBorder: true;
spread: 0.02;
samples: 25;
color: "#80000000";
}
anchors.centerIn: parent;
Behavior on opacity {
NumberAnimation {
duration: 300;
easing.type: Easing.OutCubic;
}
}
ColumnLayout {
id: mediaPlayerColumn;
spacing: 10
anchors {
top: parent.top
left: parent.left
right: parent.right
margins: 10
}
Repeater {
model: Mpris.players
Rectangle {
required property var modelData;
radius: 5;
color: ShellGlobals.colors.midlight;
border.color: ShellGlobals.colors.light;
height: 75;
Layout.fillWidth: true;
RowLayout {
spacing: 15;
anchors {
fill: parent;
leftMargin: 10;
rightMargin: 10;
topMargin: 0;
bottomMargin: 0;
}
Item {
Layout.preferredWidth: 60;
Layout.preferredHeight: 60;
Layout.alignment: Qt.AlignVCenter;
visible: modelData.trackArtUrl != "";
Rectangle {
id: mask;
anchors.fill: parent;
radius: 5;
visible: false;
}
Image {
anchors.fill: parent;
source: modelData.trackArtUrl;
fillMode: Image.PreserveAspectFit;
layer.enabled: true;
layer.effect: OpacityMask {
maskSource: mask;
}
}
}
ColumnLayout {
Layout.fillWidth: true;
Layout.fillHeight: true;
spacing: 5;
Layout.alignment: Qt.AlignVCenter;
Item { Layout.fillHeight: true; }
Text {
text: modelData.trackArtist;
color: ShellGlobals.colors.text;
font.pointSize: 13;
font.bold: true;
Layout.alignment: Qt.AlignLeft;
Layout.fillWidth: true;
elide: Text.ElideRight;
}
Text {
text: modelData.trackTitle;
color: ShellGlobals.colors.text;
font.pointSize: 13;
Layout.alignment: Qt.AlignLeft;
Layout.fillWidth: true;
elide: Text.ElideRight;
}
Item { Layout.fillHeight: true; }
}
RowLayout {
spacing: 2;
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter;
Widgets.IconButton {
implicitSize: 28;
padding: 4;
source: "root:resources/mpris/previous.svg";
onClicked: modelData.previous();
}
Widgets.IconButton {
implicitSize: 28;
padding: 4;
source: modelData?.isPlaying
? "root:resources/mpris/pause.svg"
: "root:resources/mpris/play.svg";
onClicked: {
if (!modelData.canPlay)
return;
modelData.isPlaying
? modelData.pause()
: modelData.play();
}
}
Widgets.IconButton {
implicitSize: 28;
padding: 4;
source: "root:resources/mpris/next.svg";
onClicked: modelData.next();
}
}
}
}
}
}
}
}

View file

@ -0,0 +1,45 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Services.Mpris
Singleton {
property MprisPlayer trackedPlayer;
id: root;
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;
}
}
}
}

View file

@ -0,0 +1,151 @@
import QtQuick
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell
import Quickshell.Services.Mpris
import "../.."
PopupWindow {
id: root
width: mediaPlayerContainer.width + 10
height: mediaPlayerContainer.height + 10
color: "transparent"
visible: mediaPlayerContainer.opacity > 0
anchor.rect.x: parentWindow.width / 2 - width / 2
anchor.rect.y: parentWindow.height
function show() {
mediaPlayerContainer.opacity = 1;
}
function hide() {
mediaPlayerContainer.opacity = 0;
}
HoverHandler {
id: hoverHandler
enabled: true
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
onHoveredChanged: {
if (hovered == false) {
hide();
}
}
}
Rectangle {
id: mediaPlayerContainer
width: 500
height: mediaPlayerColumn.height + 20
color: ShellGlobals.colors.base
radius: 5
opacity: 0
anchors.centerIn: parent
layer.enabled: true
layer.effect: OpacityMask {
source: Rectangle {
width: mediaPlayerContainer.width
height: mediaPlayerContainer.height
radius: mediaPlayerContainer.radius
color: "white"
}
maskSource: Rectangle {
width: mediaPlayerContainer.width
height: mediaPlayerContainer.height
radius: mediaPlayerContainer.radius
color: "black"
}
layer.enabled: true
layer.effect: DropShadow {
transparentBorder: true
spread: 0.02
samples: 25
color: "#80000000"
}
}
Behavior on opacity {
NumberAnimation {
duration: 300
easing.type: Easing.OutCubic
}
}
ColorQuantizer {
id: colorQuantizer
source: Qt.resolvedUrl(Media.trackedPlayer?.trackArtUrl ?? "")
depth: 2
rescaleSize: 64
onColorsChanged: {
Media.colors = colors;
}
}
ShaderEffect {
property color topLeftColor: colorQuantizer?.colors[0] ?? "white"
property color topRightColor: colorQuantizer?.colors[1] ?? "black"
property color bottomLeftColor: colorQuantizer?.colors[2] ?? "white"
property color bottomRightColor: colorQuantizer?.colors[3] ?? "black"
anchors.fill: parent
fragmentShader: "root:/shaders/vertexgradient.frag.qsb"
vertexShader: "root:/shaders/vertexgradient.vert.qsb"
Behavior on topLeftColor {
ColorAnimation {
duration: 500
easing.type: Easing.InOutQuad
}
}
Behavior on topRightColor {
ColorAnimation {
duration: 500
easing.type: Easing.InOutQuad
}
}
Behavior on bottomLeftColor {
ColorAnimation {
duration: 500
easing.type: Easing.InOutQuad
}
}
Behavior on bottomRightColor {
ColorAnimation {
duration: 500
easing.type: Easing.InOutQuad
}
}
}
ColumnLayout {
id: mediaPlayerColumn
spacing: 10
Layout.fillWidth: true
Layout.preferredWidth: parent.width
Layout.margins: 10
implicitHeight: childrenRect.height
anchors {
top: parent.top
left: parent.left
right: parent.right
margins: 10
}
// Media Cards
Repeater {
model: Mpris.players
Card {
required property var modelData
player: modelData
Layout.fillWidth: true
}
}
}
}
}

View file

@ -0,0 +1,174 @@
import QtQuick
import Quickshell.Widgets
import Quickshell.Services.Mpris
import Qt5Compat.GraphicalEffects
import "../.."
Item {
id: root
required property var bar
width: statusInfo.width + 125
height: parent.height
visible: Mpris.players.values.length != 0
Player {
id: mediaPlayer
anchor.window: bar
anchor.rect.x: parentWindow.width / 2 - width / 2
anchor.rect.y: parentWindow.height
}
MouseArea {
id: playButton
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: mouse => {
if (mouse.button === Qt.LeftButton) {
if (mediaPlayer.visible) {
mediaPlayer.hide();
} else {
mediaPlayer.show();
}
} else {
if (!Media.trackedPlayer.canPlay || Media.trackedPlayer == null)
return;
if (Media.trackedPlayer.isPlaying)
Media.trackedPlayer.pause();
else
Media.trackedPlayer.play();
}
}
anchors.fill: parent
}
ShaderEffect {
id: gradientShader
property color topLeftColor: Media?.colors[0] ?? "white"
property color topRightColor: Media?.colors[1] ?? "black"
property color bottomLeftColor: Media?.colors[2] ?? "white"
property color bottomRightColor: Media?.colors[3] ?? "black"
anchors.fill: parent
visible: false
fragmentShader: "root:/shaders/vertexgradient.frag.qsb"
vertexShader: "root:/shaders/vertexgradient.vert.qsb"
Behavior on topLeftColor {
ColorAnimation {
duration: 500
easing.type: Easing.InOutQuad
}
}
Behavior on topRightColor {
ColorAnimation {
duration: 500
easing.type: Easing.InOutQuad
}
}
Behavior on bottomLeftColor {
ColorAnimation {
duration: 500
easing.type: Easing.InOutQuad
}
}
Behavior on bottomRightColor {
ColorAnimation {
duration: 500
easing.type: Easing.InOutQuad
}
}
}
Rectangle {
id: artRect
anchors.fill: gradientShader
antialiasing: true
visible: false
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop {
position: 0.0
color: "transparent"
}
GradientStop {
position: 0.5
color: "white"
}
GradientStop {
position: 1.0
color: "transparent"
}
}
}
OpacityMask {
id: clip
source: gradientShader
anchors.fill: gradientShader
maskSource: artRect
cached: false
visible: false
}
GaussianBlur {
id: blur
visible: root.visible
source: clip
anchors.fill: clip
radius: 16
samples: radius * 2
transparentBorder: true
}
Item {
id: statusInfo
width: statusIcon.width + statusIcon.anchors.rightMargin + nowPlayingText.width
height: parent.height
visible: Media.trackedPlayer != null
anchors {
horizontalCenter: parent.horizontalCenter
verticalCenter: parent.verticalCenter
top: parent.top
bottom: parent.botton
margins: 3.5
}
//Rectangle {
// color: ShellGlobals.colors.accent
// radius: 3
// width: parent.width + 25
// height: parent.height - 7
// visible: playButton.containsMouse
// anchors.centerIn: parent
//}
IconImage {
id: statusIcon
implicitSize: 13
source: Media.trackedPlayer?.isPlaying ? "root:resources/mpris/pause.svg" : "root:resources/mpris/play.svg"
anchors {
verticalCenter: parent.verticalCenter
right: nowPlayingText.left
rightMargin: 10
}
}
Text {
id: nowPlayingText
color: ShellGlobals.colors.text
text: `${Media.trackedPlayer?.trackArtist} - ${Media.trackedPlayer?.trackTitle}`
font.pointSize: 11
width: Math.min(implicitWidth, 250)
elide: Text.ElideRight
anchors {
verticalCenter: parent.verticalCenter
right: parent.right
}
}
}
}