better widget style

This commit is contained in:
lucy 2025-12-28 17:36:11 +01:00
parent 484e1a8143
commit e6a8808858
6 changed files with 204 additions and 157 deletions

View File

@ -5,25 +5,25 @@ import Quickshell
Singleton { Singleton {
id: customColors id: customColors
// Core Backgrounds // Core Backgrounds
readonly property color background: "#0A0E14" readonly property color background: "#1A1B26"
readonly property color foreground: "#B3B1AD" readonly property color foreground: "#C0CAF5"
readonly property color cursor: "#E6B450" readonly property color cursor: "#C0CAF5"
// The 16 Colors of the Apocalypse // The 16 Colors of the Apocalypse
readonly property color color0: "#0A0E14" readonly property color color0: "#414868"
readonly property color color1: "#FF3333" readonly property color color1: "#F7768E"
readonly property color color2: "#C2D94C" readonly property color color2: "#9ECE6A"
readonly property color color3: "#FF8F40" readonly property color color3: "#E0AF68"
readonly property color color4: "#59C2FF" readonly property color color4: "#7AA2F7"
readonly property color color5: "#FFEE99" readonly property color color5: "#BB9AF7"
readonly property color color6: "#95E6CB" readonly property color color6: "#7DCFFF"
readonly property color color7: "#B3B1AD" readonly property color color7: "#A9B1D6"
readonly property color color8: "#4D5566" readonly property color color8: "#414868"
readonly property color color9: "#FF3333" readonly property color color9: "#F7768E"
readonly property color color10: "#C2D94C" readonly property color color10: "#9ECE6A"
readonly property color color11: "#FF8F40" readonly property color color11: "#E0AF68"
readonly property color color12: "#59C2FF" readonly property color color12: "#7AA2F7"
readonly property color color13: "#FFEE99" readonly property color color13: "#BB9AF7"
readonly property color color14: "#95E6CB" readonly property color color14: "#7DCFFF"
readonly property color color15: "#B3B1AD" readonly property color color15: "#C0CAF5"
} }

View File

@ -1,14 +1,12 @@
import Quickshell import Quickshell
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import qs
import "../../" import "../../"
PanelWindow { PanelWindow {
id: root id: root
required property var modelData required property var modelData
implicitHeight: 35 implicitHeight: 34
//color: Colors.background
color: Colors.background color: Colors.background
anchors { anchors {
top: true top: true

View File

@ -9,81 +9,100 @@ import Quickshell.Widgets
import "../settings/" import "../settings/"
import "../../" import "../../"
RowLayout { Item {
id: root id: root
implicitWidth: mprisRepeater.implicitWidth + 10
implicitHeight: 34
// 1. Let Repeater loop through the ObjectModel for us // 1. Let Repeater loop through the ObjectModel for us
Repeater { Repeater {
id: mprisRepeater id: mprisRepeater
model: Mpris.players model: Mpris.players
delegate: RowLayout { delegate: Item {
id: delegateLayout
required property var modelData required property var modelData
// 2. 🕵 FILTER LOGIC implicitHeight: 34
// Check if this specific player is Spotify. implicitWidth: delegateLayout.implicitWidth
// We verify 'modelData' exists and check the name. MouseArea {
property bool isSpotify: modelData && modelData.identity.toLowerCase().includes("spotify") id: playbackControl
anchors.fill: parent
// 3. 👻 HIDE NON-SPOTIFY PLAYERS cursorShape: Qt.PointingHandCursor
visible: isSpotify acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: mouse => {
// If hidden, take up ZERO space if (mouse.button == Qt.LeftButton) {
Layout.preferredWidth: isSpotify ? Math.min(implicitWidth, 400) : 0 console.log("Left button press");
Layout.fillHeight: true }
if (mouse.button == Qt.RightButton) {
// 4. 🎵 USE 'modelData' DIRECTLY parent.modelData.togglePlaying();
// property string title: modelData.metadata["xesam:title"] || "No Title" }
// property string artist: modelData.metadata["xesam:artist"] || "Unknown" }
// property string artUrl: modelData.metadata["mpris:artUrl"] || "" onDoubleClicked: mouse => {
// property bool isPlaying: modelData.playbackStatus === MprisPlaybackStatus.Playing if (mouse.button == Qt.LeftButton) {
property string title: modelData.trackTitle parent.modelData.next();
property string artist: modelData.trackArtist }
property string artUrl: modelData.trackArtUrl
property bool isPlaying: modelData.isPlaying
spacing: 10
// 🖼 ALBUM ART
ClippingWrapperRectangle {
Layout.alignment: Qt.AlignVCenter
radius: 20
IconImage {
source: delegateLayout.artUrl // Access property from delegate
asynchronous: true
implicitSize: 24
} }
} }
RowLayout {
id: delegateLayout
// 2. 🕵 FILTER LOGIC
// Check if this specific player is Spotify.
// We verify 'modelData' exists and check the name.
property bool isSpotify: modelData && modelData.identity.toLowerCase().includes("spotify")
// 📝 TEXT INFO // 3. 👻 HIDE NON-SPOTIFY PLAYERS
ColumnLayout { visible: isSpotify
Layout.alignment: Qt.AlignVCenter
spacing: 0
visible: parent.visible
Text { // If hidden, take up ZERO space
text: delegateLayout.title Layout.preferredWidth: isSpotify ? Math.min(implicitWidth, 400) : 0
color: Colors.foreground Layout.fillHeight: true
font.bold: true
font.pixelSize: Settings.fontSize property string title: modelData.trackTitle
font.family: Settings.font property string artist: modelData.trackArtist
elide: Text.ElideRight property string artUrl: modelData.trackArtUrl
Layout.preferredWidth: implicitWidth property bool isPlaying: modelData.isPlaying
spacing: 10
// 🖼 ALBUM ART
ClippingWrapperRectangle {
Layout.alignment: Qt.AlignVCenter
radius: 20
IconImage {
source: delegateLayout.artUrl // Access property from delegate
asynchronous: true
implicitSize: root.implicitHeight * 0.6
}
} }
Text { // 📝 TEXT INFO
font.pixelSize: Settings.fontSize - 2 ColumnLayout {
font.family: Settings.font Layout.alignment: Qt.AlignVCenter
text: delegateLayout.artist spacing: 0
color: Colors.foreground visible: parent.visible
opacity: 0.7
Layout.preferredWidth: implicitWidth Text {
text: delegateLayout.title
color: Colors.foreground
font.bold: true
font.pixelSize: Settings.fontSize
font.family: Settings.font
elide: Text.ElideRight
Layout.preferredWidth: implicitWidth
}
Text {
font.pixelSize: Settings.fontSize - 2
font.family: Settings.font
text: delegateLayout.artist
color: Colors.foreground
opacity: 0.7
Layout.preferredWidth: implicitWidth
}
} }
} }
// CONTROLS
} }
} }
} }

View File

@ -1,34 +1,48 @@
import QtQuick import QtQuick
import Quickshell.Services.UPower import Quickshell.Services.UPower
import QtQuick.Layouts
import "../settings/" import "../settings/"
import "../../" import "../../"
Item { Item {
id: root id: root
implicitWidth: 80 implicitWidth: powerLayout.implicitWidth + 10
Text { implicitHeight: 34
id: powerProfile MouseArea {
text: PowerProfile.toString(PowerProfiles.profile) anchors.fill: parent
font.weight: 900 cursorShape: Qt.PointingHandCursor
color: Colors.foreground acceptedButtons: Qt.LeftButton | Qt.RightButton
font.family: Settings.font onClicked: mouse => {
font.pixelSize: Settings.fontSize const modes = [PowerProfile.PowerSaver, PowerProfile.Balanced, PowerProfile.Performance];
let current = PowerProfiles.profile;
let currentIndex = modes.indexOf(current);
let nextIndex = (currentIndex + 1) % modes.length;
let prevIndex = (currentIndex - 1) % modes.length;
if (mouse.button == Qt.LeftButton)
PowerProfiles.profile = modes[nextIndex];
if (mouse.button == Qt.RightButton)
PowerProfiles.profile = modes[prevIndex];
}
}
ColumnLayout {
id: powerLayout
anchors.centerIn: parent anchors.centerIn: parent
MouseArea { spacing: 0
cursorShape: Qt.PointingHandCursor Text {
acceptedButtons: Qt.LeftButton | Qt.RightButton id: powerProfile
anchors.fill: parent text: PowerProfile.toString(PowerProfiles.profile)
onClicked: mouse => { font.weight: 900
const modes = [PowerProfile.PowerSaver, PowerProfile.Balanced, PowerProfile.Performance]; color: Colors.foreground
let current = PowerProfiles.profile; font.family: Settings.font
let currentIndex = modes.indexOf(current); font.pixelSize: Settings.fontSize
let nextIndex = (currentIndex + 1) % modes.length; }
let prevIndex = (currentIndex - 1) % modes.length; Text {
if (mouse.button == Qt.LeftButton) text: "Profile"
PowerProfiles.profile = modes[nextIndex]; font.weight: 900
if (mouse.button == Qt.RightButton) color: Colors.foreground
PowerProfiles.profile = modes[prevIndex]; font.family: Settings.font
} font.pixelSize: Settings.fontSize - 2
opacity: 0.7
} }
} }
} }

View File

@ -1,30 +1,29 @@
import QtQuick import QtQuick
import Quickshell.Services.Pipewire import Quickshell.Services.Pipewire
import Quickshell.Widgets import Quickshell.Widgets
import QtQuick.Layouts
import Quickshell.Io import Quickshell.Io
import "../../" import "../../"
import "../settings/" import "../settings/"
Item { Item {
id: root id: root
implicitWidth: volRow.implicitWidth + 10
implicitHeight: volRow.implicitHeight
// grab the default speaker (Sink)
property var sink: Pipewire.defaultAudioSink
Process {
id: pavu
command: ["pavucontrol"] // The command and args list
}
MouseArea { MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: mouse => { onClicked: mouse => {
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
pavu.startDetached(); pavu.startDetached();
} }
} }
anchors.fill: parent }
// Scroll to change volume (The fancy stuff!) implicitWidth: styleLayout.implicitWidth + 10
implicitHeight: 34
property var sink: Pipewire.defaultAudioSink
Process {
id: pavu
command: ["pavucontrol"] // The command and args list
} }
// Logic to pick the correct icon name // Logic to pick the correct icon name
function getVolumeIcon() { function getVolumeIcon() {
@ -50,29 +49,37 @@ Item {
return "audio-volume-high"; return "audio-volume-high";
} }
Row { ColumnLayout {
id: volRow id: styleLayout
anchors.centerIn: parent anchors.centerIn: parent
spacing: 5 spacing: 0
Row {
IconImage { spacing: 10
anchors.verticalCenter: parent.verticalCenter Text {
width: 12 PwObjectTracker {
height: 12 objects: Pipewire.ready ? root.sink : []
}
source: "root:/icons/" + root.getVolumeIcon() + "-symbolic.svg" font.weight: 900
} color: Colors.foreground
font.family: Settings.font
Text { font.pixelSize: Settings.fontSize
PwObjectTracker { text: Pipewire.ready ? Math.round(root.sink.audio.volume * 100) + "%" : "0%"
objects: Pipewire.ready ? Pipewire.defaultAudioSink : []
} }
width: 20
IconImage {
anchors.verticalCenter: parent.verticalCenter
width: 12
height: 12
source: "root:/icons/" + root.getVolumeIcon() + "-symbolic.svg"
}
}
Text {
font.weight: 900 font.weight: 900
color: Colors.foreground color: Colors.foreground
font.family: Settings.font font.family: Settings.font
font.pixelSize: Settings.fontSize font.pixelSize: Settings.fontSize - 2
text: Pipewire.ready ? Math.round(Pipewire.defaultAudioSink.audio.volume * 100) + "%" : "0%" opacity: 0.7
text: Pipewire.ready ? Pipewire.defaultAudioSink.nickname : "failure"
} }
} }
} }

View File

@ -6,6 +6,8 @@ import Quickshell.Hyprland
import "." import "."
import "../../" import "../../"
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell.Widgets
import "../settings/"
WlrLayershell { WlrLayershell {
id: root id: root
@ -29,7 +31,7 @@ WlrLayershell {
right: true right: true
} }
margins { margins {
top: 30 top: 45
right: 10 right: 10
} }
@ -52,7 +54,7 @@ WlrLayershell {
ListView { ListView {
id: notifList id: notifList
anchors.fill: parent anchors.fill: parent
anchors.margins: 10 anchors.margins: 0
// Use 'spacing' to put gaps between notifications // Use 'spacing' to put gaps between notifications
spacing: 10 spacing: 10
@ -68,12 +70,12 @@ WlrLayershell {
delegate: Item { delegate: Item {
id: notifyItem id: notifyItem
implicitWidth: ListView.view.width implicitWidth: ListView.view.width
implicitHeight: 80 // Fixed height is usually better for icon layouts implicitHeight: 85 // Fixed height is usually better for icon layouts
required property var modelData required property var modelData
Timer { Timer {
id: timout id: timout
interval: 3000000 interval: 3000
running: true running: true
onRunningChanged: notifyItem.modelData.dismiss() onRunningChanged: notifyItem.modelData.dismiss()
} }
@ -81,37 +83,40 @@ WlrLayershell {
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
color: Colors.background color: Colors.background
radius: 10 radius: 20
border.color: Colors.color5 border.color: Colors.color5
border.width: 2
// 2. Use RowLayout to put Image | Text side-by-side // 2. Use RowLayout to put Image | Text side-by-side
RowLayout { RowLayout {
id: fullLayout id: fullLayout
anchors.margins: 10 anchors.margins: 10
anchors.fill: parent anchors.fill: parent
spacing: 15 spacing: 10
// 🖼 THE IMAGE ON THE LEFT // 🖼 THE IMAGE ON THE LEFT
Image { ClippingWrapperRectangle {
radius: 10
// Use the image if available, otherwise hide this space? implicitWidth: 64
// Or you could use an icon fallback. implicitHeight: 64
source: notifyItem.modelData.image
// Hide if no image exists so text takes full width
visible: notifyItem.modelData.image !== "" visible: notifyItem.modelData.image !== ""
IconImage {
// Fixed size for consistency // Use the image if available, otherwise hide this space?
sourceSize.width: 48 // Or you could use an icon fallback.
sourceSize.height: 48 source: notifyItem.modelData.image
Layout.preferredWidth: 48
Layout.preferredHeight: 48
// Crop it nicely so it doesn't stretch // Hide if no image exists so text takes full width
fillMode: Image.PreserveAspectCrop visible: notifyItem.modelData.image !== ""
// Optional: Cache it for performance // Fixed size for consistency
asynchronous: true implicitSize: 30
// Crop it nicely so it doesn't stretch
// Optional: Cache it for performance
asynchronous: true
}
} }
// 📝 THE TEXT ON THE RIGHT // 📝 THE TEXT ON THE RIGHT
@ -119,12 +124,14 @@ WlrLayershell {
id: textLayout id: textLayout
// Take up all remaining width // Take up all remaining width
Layout.fillWidth: true Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter | Qt.AlignTop // Center vertically Layout.alignment: Qt.AlignVCenter // Center vertically
spacing: 2 spacing: 2
Text { Text {
text: notifyItem.modelData.summary text: notifyItem.modelData.summary
color: Colors.foreground color: Colors.foreground
font.family: Settings.font
font.pixelSize: Settings.fontSize
font.bold: true font.bold: true
elide: Text.ElideRight elide: Text.ElideRight
Layout.fillWidth: true Layout.fillWidth: true
@ -135,7 +142,9 @@ WlrLayershell {
color: Colors.foreground color: Colors.foreground
// Limit to 2 lines // Limit to 2 lines
maximumLineCount: 2 font.family: Settings.font
font.pixelSize: Settings.fontSize - 2
maximumLineCount: 3
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
elide: Text.ElideRight elide: Text.ElideRight
Layout.fillWidth: true Layout.fillWidth: true