From e6a88088583b9cbbc6aa10c2735a30fcca8f621d Mon Sep 17 00:00:00 2001 From: lucy Date: Sun, 28 Dec 2025 17:36:11 +0100 Subject: [PATCH] better widget style --- Colors.qml | 38 ++++---- modules/bar/Bar.qml | 4 +- modules/bar/Mpris.qml | 137 ++++++++++++++++------------ modules/bar/PowerProfiles.qml | 60 +++++++----- modules/bar/Volume.qml | 65 +++++++------ modules/notifications/NotiPopup.qml | 57 +++++++----- 6 files changed, 204 insertions(+), 157 deletions(-) diff --git a/Colors.qml b/Colors.qml index e2d2d91..0e0a638 100644 --- a/Colors.qml +++ b/Colors.qml @@ -5,25 +5,25 @@ import Quickshell Singleton { id: customColors // Core Backgrounds - readonly property color background: "#0A0E14" - readonly property color foreground: "#B3B1AD" - readonly property color cursor: "#E6B450" + readonly property color background: "#1A1B26" + readonly property color foreground: "#C0CAF5" + readonly property color cursor: "#C0CAF5" // The 16 Colors of the Apocalypse - readonly property color color0: "#0A0E14" - readonly property color color1: "#FF3333" - readonly property color color2: "#C2D94C" - readonly property color color3: "#FF8F40" - readonly property color color4: "#59C2FF" - readonly property color color5: "#FFEE99" - readonly property color color6: "#95E6CB" - readonly property color color7: "#B3B1AD" - readonly property color color8: "#4D5566" - readonly property color color9: "#FF3333" - readonly property color color10: "#C2D94C" - readonly property color color11: "#FF8F40" - readonly property color color12: "#59C2FF" - readonly property color color13: "#FFEE99" - readonly property color color14: "#95E6CB" - readonly property color color15: "#B3B1AD" + readonly property color color0: "#414868" + readonly property color color1: "#F7768E" + readonly property color color2: "#9ECE6A" + readonly property color color3: "#E0AF68" + readonly property color color4: "#7AA2F7" + readonly property color color5: "#BB9AF7" + readonly property color color6: "#7DCFFF" + readonly property color color7: "#A9B1D6" + readonly property color color8: "#414868" + readonly property color color9: "#F7768E" + readonly property color color10: "#9ECE6A" + readonly property color color11: "#E0AF68" + readonly property color color12: "#7AA2F7" + readonly property color color13: "#BB9AF7" + readonly property color color14: "#7DCFFF" + readonly property color color15: "#C0CAF5" } diff --git a/modules/bar/Bar.qml b/modules/bar/Bar.qml index be38160..3e20c45 100644 --- a/modules/bar/Bar.qml +++ b/modules/bar/Bar.qml @@ -1,14 +1,12 @@ import Quickshell import QtQuick import QtQuick.Layouts -import qs import "../../" PanelWindow { id: root required property var modelData - implicitHeight: 35 - //color: Colors.background + implicitHeight: 34 color: Colors.background anchors { top: true diff --git a/modules/bar/Mpris.qml b/modules/bar/Mpris.qml index 082c275..291313d 100644 --- a/modules/bar/Mpris.qml +++ b/modules/bar/Mpris.qml @@ -9,81 +9,100 @@ import Quickshell.Widgets import "../settings/" import "../../" -RowLayout { +Item { id: root + implicitWidth: mprisRepeater.implicitWidth + 10 + implicitHeight: 34 // 1. Let Repeater loop through the ObjectModel for us Repeater { id: mprisRepeater model: Mpris.players - delegate: RowLayout { - id: delegateLayout + delegate: Item { required property var modelData - // 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") - - // 3. 👻 HIDE NON-SPOTIFY PLAYERS - visible: isSpotify - - // If hidden, take up ZERO space - Layout.preferredWidth: isSpotify ? Math.min(implicitWidth, 400) : 0 - Layout.fillHeight: true - - // 4. 🎵 USE 'modelData' DIRECTLY - // property string title: modelData.metadata["xesam:title"] || "No Title" - // property string artist: modelData.metadata["xesam:artist"] || "Unknown" - // property string artUrl: modelData.metadata["mpris:artUrl"] || "" - // property bool isPlaying: modelData.playbackStatus === MprisPlaybackStatus.Playing - property string title: modelData.trackTitle - 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 + implicitHeight: 34 + implicitWidth: delegateLayout.implicitWidth + MouseArea { + id: playbackControl + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: mouse => { + if (mouse.button == Qt.LeftButton) { + console.log("Left button press"); + } + if (mouse.button == Qt.RightButton) { + parent.modelData.togglePlaying(); + } + } + onDoubleClicked: mouse => { + if (mouse.button == Qt.LeftButton) { + parent.modelData.next(); + } } } + 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 - ColumnLayout { - Layout.alignment: Qt.AlignVCenter - spacing: 0 - visible: parent.visible + // 3. 👻 HIDE NON-SPOTIFY PLAYERS + visible: isSpotify - Text { - text: delegateLayout.title - color: Colors.foreground - font.bold: true - font.pixelSize: Settings.fontSize - font.family: Settings.font - elide: Text.ElideRight - Layout.preferredWidth: implicitWidth + // If hidden, take up ZERO space + Layout.preferredWidth: isSpotify ? Math.min(implicitWidth, 400) : 0 + Layout.fillHeight: true + + property string title: modelData.trackTitle + 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: root.implicitHeight * 0.6 + } } - Text { - font.pixelSize: Settings.fontSize - 2 - font.family: Settings.font - text: delegateLayout.artist - color: Colors.foreground - opacity: 0.7 - Layout.preferredWidth: implicitWidth + // 📝 TEXT INFO + ColumnLayout { + Layout.alignment: Qt.AlignVCenter + spacing: 0 + visible: parent.visible + + 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 } } } diff --git a/modules/bar/PowerProfiles.qml b/modules/bar/PowerProfiles.qml index 85c5e5d..eb8e30b 100644 --- a/modules/bar/PowerProfiles.qml +++ b/modules/bar/PowerProfiles.qml @@ -1,34 +1,48 @@ import QtQuick import Quickshell.Services.UPower +import QtQuick.Layouts import "../settings/" import "../../" Item { id: root - implicitWidth: 80 - Text { - id: powerProfile - text: PowerProfile.toString(PowerProfiles.profile) - font.weight: 900 - color: Colors.foreground - font.family: Settings.font - font.pixelSize: Settings.fontSize + implicitWidth: powerLayout.implicitWidth + 10 + implicitHeight: 34 + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: mouse => { + 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 - MouseArea { - cursorShape: Qt.PointingHandCursor - acceptedButtons: Qt.LeftButton | Qt.RightButton - anchors.fill: parent - onClicked: mouse => { - 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]; - } + spacing: 0 + Text { + id: powerProfile + text: PowerProfile.toString(PowerProfiles.profile) + font.weight: 900 + color: Colors.foreground + font.family: Settings.font + font.pixelSize: Settings.fontSize + } + Text { + text: "Profile" + font.weight: 900 + color: Colors.foreground + font.family: Settings.font + font.pixelSize: Settings.fontSize - 2 + opacity: 0.7 } } } diff --git a/modules/bar/Volume.qml b/modules/bar/Volume.qml index 9623011..8f2bf48 100644 --- a/modules/bar/Volume.qml +++ b/modules/bar/Volume.qml @@ -1,30 +1,29 @@ import QtQuick import Quickshell.Services.Pipewire import Quickshell.Widgets +import QtQuick.Layouts import Quickshell.Io import "../../" import "../settings/" Item { 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 { + anchors.fill: parent cursorShape: Qt.PointingHandCursor onClicked: mouse => { if (mouse.button === Qt.LeftButton) { 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 function getVolumeIcon() { @@ -50,29 +49,37 @@ Item { return "audio-volume-high"; } - Row { - id: volRow + ColumnLayout { + id: styleLayout anchors.centerIn: parent - spacing: 5 - - IconImage { - anchors.verticalCenter: parent.verticalCenter - width: 12 - height: 12 - - source: "root:/icons/" + root.getVolumeIcon() + "-symbolic.svg" - } - - Text { - PwObjectTracker { - objects: Pipewire.ready ? Pipewire.defaultAudioSink : [] + spacing: 0 + Row { + spacing: 10 + Text { + PwObjectTracker { + objects: Pipewire.ready ? root.sink : [] + } + font.weight: 900 + color: Colors.foreground + font.family: Settings.font + font.pixelSize: Settings.fontSize + text: Pipewire.ready ? Math.round(root.sink.audio.volume * 100) + "%" : "0%" } - width: 20 + + IconImage { + anchors.verticalCenter: parent.verticalCenter + width: 12 + height: 12 + source: "root:/icons/" + root.getVolumeIcon() + "-symbolic.svg" + } + } + Text { font.weight: 900 color: Colors.foreground font.family: Settings.font - font.pixelSize: Settings.fontSize - text: Pipewire.ready ? Math.round(Pipewire.defaultAudioSink.audio.volume * 100) + "%" : "0%" + font.pixelSize: Settings.fontSize - 2 + opacity: 0.7 + text: Pipewire.ready ? Pipewire.defaultAudioSink.nickname : "failure" } } } diff --git a/modules/notifications/NotiPopup.qml b/modules/notifications/NotiPopup.qml index 1e6d96d..b208dac 100644 --- a/modules/notifications/NotiPopup.qml +++ b/modules/notifications/NotiPopup.qml @@ -6,6 +6,8 @@ import Quickshell.Hyprland import "." import "../../" import QtQuick.Layouts +import Quickshell.Widgets +import "../settings/" WlrLayershell { id: root @@ -29,7 +31,7 @@ WlrLayershell { right: true } margins { - top: 30 + top: 45 right: 10 } @@ -52,7 +54,7 @@ WlrLayershell { ListView { id: notifList anchors.fill: parent - anchors.margins: 10 + anchors.margins: 0 // Use 'spacing' to put gaps between notifications spacing: 10 @@ -68,12 +70,12 @@ WlrLayershell { delegate: Item { id: notifyItem 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 Timer { id: timout - interval: 3000000 + interval: 3000 running: true onRunningChanged: notifyItem.modelData.dismiss() } @@ -81,37 +83,40 @@ WlrLayershell { Rectangle { anchors.fill: parent color: Colors.background - radius: 10 + radius: 20 border.color: Colors.color5 + border.width: 2 // 2. Use RowLayout to put Image | Text side-by-side RowLayout { id: fullLayout anchors.margins: 10 anchors.fill: parent - spacing: 15 + spacing: 10 // 🖼️ THE IMAGE ON THE LEFT - Image { - - // Use the image if available, otherwise hide this space? - // Or you could use an icon fallback. - source: notifyItem.modelData.image - - // Hide if no image exists so text takes full width + ClippingWrapperRectangle { + radius: 10 + implicitWidth: 64 + implicitHeight: 64 visible: notifyItem.modelData.image !== "" + IconImage { - // Fixed size for consistency - sourceSize.width: 48 - sourceSize.height: 48 - Layout.preferredWidth: 48 - Layout.preferredHeight: 48 + // Use the image if available, otherwise hide this space? + // Or you could use an icon fallback. + source: notifyItem.modelData.image - // Crop it nicely so it doesn't stretch - fillMode: Image.PreserveAspectCrop + // Hide if no image exists so text takes full width + visible: notifyItem.modelData.image !== "" - // Optional: Cache it for performance - asynchronous: true + // Fixed size for consistency + implicitSize: 30 + + // Crop it nicely so it doesn't stretch + + // Optional: Cache it for performance + asynchronous: true + } } // 📝 THE TEXT ON THE RIGHT @@ -119,12 +124,14 @@ WlrLayershell { id: textLayout // Take up all remaining width Layout.fillWidth: true - Layout.alignment: Qt.AlignVCenter | Qt.AlignTop // Center vertically + Layout.alignment: Qt.AlignVCenter // Center vertically spacing: 2 Text { text: notifyItem.modelData.summary color: Colors.foreground + font.family: Settings.font + font.pixelSize: Settings.fontSize font.bold: true elide: Text.ElideRight Layout.fillWidth: true @@ -135,7 +142,9 @@ WlrLayershell { color: Colors.foreground // Limit to 2 lines - maximumLineCount: 2 + font.family: Settings.font + font.pixelSize: Settings.fontSize - 2 + maximumLineCount: 3 wrapMode: Text.WordWrap elide: Text.ElideRight Layout.fillWidth: true