Colors and Notifications

This commit is contained in:
2026-04-15 17:25:01 +02:00
parent 56e07449a8
commit 2382ee6537
9 changed files with 372 additions and 96 deletions

15
Colors.qml Normal file
View File

@@ -0,0 +1,15 @@
pragma Singleton
import QtQuick
// Everforest Dark Hard — edit here to retheme the whole bar.
QtObject {
readonly property color bg: "#2d353b" // bar background, text on highlights
readonly property color bgDark: "#272e33" // popup background
readonly property color surface: "#343f44" // inactive workspace pill, hover bg
readonly property color fg: "#d3c6aa" // primary text and icons
readonly property color fgDim: "#7a8478" // separators, weekday labels, muted/offline
readonly property color accent: "#a7c080" // active workspace, today, nav arrows
readonly property color warning: "#e69875" // moderate load
readonly property color critical: "#e67e80" // high load, low battery, weekends
}

View File

@@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import QtQuick.Layouts
import ".."
PanelWindow {
id: root
@@ -16,7 +17,7 @@ PanelWindow {
implicitHeight: 32
exclusionMode: ExclusionMode.Auto
color: "#1e1e2e"
color: Colors.bg
// Left side — anchored to left edge, never affected by right side width
RowLayout {

View File

@@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import QtQuick.Layouts
import ".."
// Calendar popup — anchored below its trigger item.
// Instantiate inside DateTime.qml; set anchor.window + anchor.item from there.
@@ -15,7 +16,7 @@ PopupWindow {
visible: false
color: "transparent"
implicitWidth: 252 // 7 columns × 36 px
implicitWidth: 276 // 7 columns × 36 px + 12 px margin on each side
implicitHeight: 284
// ── State ────────────────────────────────────────────────────────────────
@@ -24,7 +25,7 @@ PopupWindow {
// ── Public API ───────────────────────────────────────────────────────────
function open() {
// Reset animation state before the window becomes visible.
// Reset content state in case closeAnim ran previously.
content.opacity = 0;
content.scale = 0.95;
closeAnim.stop();
@@ -114,7 +115,7 @@ PopupWindow {
// ── Background ───────────────────────────────────────────────────────
Rectangle {
anchors.fill: parent
color: "#181825" // Catppuccin Mantle
color: Colors.bgDark
radius: 8
}
@@ -127,63 +128,35 @@ PopupWindow {
spacing: 8
// ── Month navigation ─────────────────────────────────────────────
component NavButton: Rectangle {
required property string label
required property var action
width: 24; height: 24; radius: 4
color: hover.hovered ? Colors.surface : "transparent"
Text {
anchors.centerIn: parent
text: parent.label
color: Colors.accent
font.pixelSize: 16
font.weight: Font.Bold
}
HoverHandler { id: hover }
TapHandler { onTapped: parent.action() }
}
RowLayout {
width: parent.width
Rectangle {
width: 24
height: 24
radius: 4
color: prevHover.hovered ? "#313244" : "transparent"
Text {
anchors.centerIn: parent
text: ""
color: "#89b4fa"
font.pixelSize: 16
font.weight: Font.Bold
}
HoverHandler {
id: prevHover
}
TapHandler {
onTapped: root.prevMonth()
}
}
Item {
Layout.fillWidth: true
}
NavButton { label: ""; action: root.prevMonth }
Item { Layout.fillWidth: true }
Text {
text: root.monthNames[root.viewMonth] + " " + root.viewYear
color: "#cdd6f4"
color: Colors.fg
font.pixelSize: 13
font.weight: Font.Medium
}
Item {
Layout.fillWidth: true
}
Rectangle {
width: 24
height: 24
radius: 4
color: nextHover.hovered ? "#313244" : "transparent"
Text {
anchors.centerIn: parent
text: ""
color: "#89b4fa"
font.pixelSize: 16
font.weight: Font.Bold
}
HoverHandler {
id: nextHover
}
TapHandler {
onTapped: root.nextMonth()
}
}
Item { Layout.fillWidth: true }
NavButton { label: ""; action: root.nextMonth }
}
// ── Day-of-week header ────────────────────────────────────────────
@@ -197,7 +170,7 @@ PopupWindow {
width: 36
horizontalAlignment: Text.AlignHCenter
text: modelData
color: index >= 5 ? "#f38ba8" : "#6c7086"
color: index >= 5 ? Colors.critical : Colors.fgDim
font.pixelSize: 11
}
}
@@ -243,7 +216,7 @@ PopupWindow {
width: 28
height: 28
radius: 14
color: cell.isToday ? "#89b4fa" : "transparent"
color: cell.isToday ? Colors.accent : "transparent"
}
Text {
@@ -254,10 +227,10 @@ PopupWindow {
font.weight: cell.isToday ? Font.Bold : Font.Normal
color: {
if (cell.isToday)
return "#1e1e2e";
return Colors.bg;
if (cell.isWeekend)
return "#f38ba8";
return "#cdd6f4";
return Colors.critical;
return Colors.fg;
}
}
}

View File

@@ -1,8 +1,10 @@
pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Wayland
import QtQuick
import QtQuick.Layouts
import ".."
// Date + time widget. Click it to open/close the calendar popup.
// barWindow must be set by the parent (Bar.qml) so the popup has a Wayland parent.
@@ -20,24 +22,45 @@ RowLayout {
anchor.window: root.barWindow
anchor.item: root
anchor.edges: Edges.Bottom
anchor.edges: Edges.Bottom | Edges.Right
anchor.gravity: Edges.Bottom | Edges.Left
}
// Full-screen transparent overlay: captures outside clicks and Escape to close calendar.
// WlrLayershell.keyboardFocus: Exclusive gives it real keyboard focus from the compositor.
PanelWindow {
visible: cal.visible
screen: root.barWindow.screen
color: "transparent"
exclusionMode: ExclusionMode.Ignore
anchors { top: true; bottom: true; left: true; right: true }
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
TapHandler {
onTapped: cal.close()
}
Item {
focus: true
Keys.onEscapePressed: cal.close()
}
}
Text {
id: dateLabel
color: "#a6adc8"
color: Colors.fg
font.pixelSize: 13
}
Text {
text: "|"
color: "#45475a"
color: Colors.fgDim
font.pixelSize: 13
}
Text {
id: timeLabel
color: "#cdd6f4"
color: Colors.fg
font.pixelSize: 13
font.weight: Font.Medium
Behavior on color {

View File

@@ -4,6 +4,7 @@ import Quickshell
import Quickshell.Io
import QtQuick
import QtQuick.Layouts
import ".."
// System stats strip: Volume · Network · CPU load · Memory · Temperature · Brightness · Battery
// All stats refreshed every 5 s via a single bash process.
@@ -20,6 +21,7 @@ RowLayout {
property int cpuCores: 1
property real memUsedGB: 0
property real memTotalGB: 0
readonly property real memRatio: memTotalGB > 0 ? memUsedGB / memTotalGB : 0
property int tempC: 0
property int brightPct: 100
property int batPct: -1 // -1 means no battery detected
@@ -113,8 +115,9 @@ RowLayout {
}
// ── Volume ────────────────────────────────────────────────────────────────
RowLayout {
spacing: 4
Item {
implicitWidth: volRow.implicitWidth
implicitHeight: volRow.implicitHeight
Process { id: pavucontrolProc; command: ["pavucontrol"] }
Process { id: volUpProc; command: ["pactl", "set-sink-volume", "@DEFAULT_SINK@", "+5%"] }
@@ -137,15 +140,21 @@ RowLayout {
}
}
Text {
text: root.volMuted ? "󰖁" : (root.volPct > 50 ? "󰕾" : root.volPct > 0 ? "󰖀" : "󰕿")
color: root.volMuted ? "#585b70" : "#cdd6f4"
font.pixelSize: 15
}
Text {
text: root.volMuted ? "mute" : root.volPct + "%"
color: "#a6adc8"
font.pixelSize: 13
RowLayout {
id: volRow
anchors.fill: parent
spacing: 4
Text {
text: root.volMuted ? "󰖁" : (root.volPct > 50 ? "󰕾" : root.volPct > 0 ? "󰖀" : "󰕿")
color: root.volMuted ? Colors.fgDim : Colors.fg
font.pixelSize: 15
}
Text {
text: root.volMuted ? "mute" : root.volPct + "%"
color: Colors.fg
font.pixelSize: 13
}
}
}
@@ -154,13 +163,13 @@ RowLayout {
spacing: 4
Text {
text: root.netIp ? "󰱓" : "󰲛"
color: root.netIp ? "#cdd6f4" : "#585b70"
color: root.netIp ? Colors.fg : Colors.fgDim
font.pixelSize: 15
}
Text {
visible: root.netIp !== ""
text: root.netIp
color: "#a6adc8"
color: Colors.fg
font.pixelSize: 13
}
}
@@ -168,11 +177,11 @@ RowLayout {
// ── CPU load average ──────────────────────────────────────────────────────
RowLayout {
spacing: 4
Text { text: "󰻠"; color: "#cdd6f4"; font.pixelSize: 15 }
Text { text: "󰻠"; color: Colors.fg; font.pixelSize: 15 }
Text {
text: root.loadAvg.toFixed(2)
color: root.loadAvg > root.cpuCores ? "#f38ba8" :
root.loadAvg > root.cpuCores * 0.7 ? "#fab387" : "#a6adc8"
color: root.loadAvg > root.cpuCores ? Colors.critical :
root.loadAvg > root.cpuCores * 0.7 ? Colors.warning : Colors.fg
font.pixelSize: 13
}
}
@@ -180,11 +189,11 @@ RowLayout {
// ── Memory ────────────────────────────────────────────────────────────────
RowLayout {
spacing: 4
Text { text: "󰍛"; color: "#cdd6f4"; font.pixelSize: 15 }
Text { text: "󰍛"; color: Colors.fg; font.pixelSize: 15 }
Text {
text: root.memUsedGB.toFixed(1) + "/" + root.memTotalGB.toFixed(1) + "G"
color: root.memTotalGB > 0 && (root.memUsedGB / root.memTotalGB) > 0.8 ? "#f38ba8" :
root.memTotalGB > 0 && (root.memUsedGB / root.memTotalGB) > 0.6 ? "#fab387" : "#a6adc8"
color: root.memRatio > 0.8 ? Colors.critical :
root.memRatio > 0.6 ? Colors.warning : Colors.fg
font.pixelSize: 13
}
}
@@ -192,10 +201,10 @@ RowLayout {
// ── Temperature ───────────────────────────────────────────────────────────
RowLayout {
spacing: 4
Text { text: "󰔐"; color: "#cdd6f4"; font.pixelSize: 15 }
Text { text: "󰔐"; color: Colors.fg; font.pixelSize: 15 }
Text {
text: root.tempC + "°"
color: root.tempC > 85 ? "#f38ba8" : root.tempC > 65 ? "#fab387" : "#a6adc8"
color: root.tempC > 85 ? Colors.critical : root.tempC > 65 ? Colors.warning : Colors.fg
font.pixelSize: 13
}
}
@@ -203,10 +212,10 @@ RowLayout {
// ── Brightness ────────────────────────────────────────────────────────────
RowLayout {
spacing: 4
Text { text: "󰃟"; color: "#cdd6f4"; font.pixelSize: 15 }
Text { text: "󰃟"; color: Colors.fg; font.pixelSize: 15 }
Text {
text: root.brightPct + "%"
color: "#a6adc8"
color: Colors.fg
font.pixelSize: 13
}
}
@@ -221,12 +230,12 @@ RowLayout {
root.batPct > 60 ? "󰁾" :
root.batPct > 40 ? "󰁼" :
root.batPct > 20 ? "󰁺" : "󰂃"
color: root.batPct < 20 && root.batStatus !== "Charging" ? "#f38ba8" : "#cdd6f4"
color: root.batPct < 20 && root.batStatus !== "Charging" ? Colors.critical : Colors.fg
font.pixelSize: 15
}
Text {
text: root.batTime ? root.batPct + "% · " + root.batTime : root.batPct + "%"
color: root.batPct < 20 && root.batStatus !== "Charging" ? "#f38ba8" : "#a6adc8"
color: root.batPct < 20 && root.batStatus !== "Charging" ? Colors.critical : Colors.fg
font.pixelSize: 13
}
}

View File

@@ -4,6 +4,7 @@ import Quickshell
import Quickshell.Hyprland
import QtQuick
import QtQuick.Layouts
import ".."
// A row of clickable workspace buttons for one screen.
// The button for the workspace currently active on *this* screen is highlighted.
@@ -26,18 +27,13 @@ RowLayout {
required property HyprlandWorkspace modelData
readonly property bool isActive: root.monitor ? (root.monitor.activeWorkspace?.id === modelData.id) : modelData.focused
readonly property bool isUrgent: modelData.urgent
implicitWidth: Math.max(28, label.implicitWidth + 16)
implicitHeight: 22
radius: 4
color: {
if (isActive)
return "#89b4fa";
if (hoverArea.hovered)
return "#45475a";
return "#313244";
}
color: isActive ? Colors.accent : isUrgent ? Colors.warning : Colors.surface
Behavior on color {
ColorAnimation {
@@ -49,7 +45,7 @@ RowLayout {
id: label
anchors.centerIn: parent
text: btn.modelData.name
color: btn.isActive ? "#1e1e2e" : "#cdd6f4" // Base / Text
color: (btn.isActive || btn.isUrgent) ? Colors.bg : Colors.fg
font.pixelSize: 13
font.weight: btn.isActive ? Font.Bold : Font.Normal

View File

@@ -0,0 +1,255 @@
pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Services.Notifications
import Quickshell.Widgets
import QtQuick
import QtQuick.Layouts
import ".."
// Notification toast overlay.
// Toasts slide in from the right, stack downward, and auto-dismiss.
// Urgency colouring: Low → fgDim stripe, Normal → accent stripe, Critical → critical border + stripe.
PanelWindow {
id: root
anchors {
top: true
right: true
}
exclusionMode: ExclusionMode.Ignore
color: "transparent"
implicitWidth: 356 // 340 toast + 8 left + 8 right margin
implicitHeight: Math.max(1, (clearAllBtn.visible ? clearAllBtn.height + 6 : 0) + notifList.contentHeight + 16)
// ── Notification server ──────────────────────────────────────────────────
NotificationServer {
id: server
onNotification: function (notif) {
notifModel.insert(0, {
"nid": notif.id,
"app": notif.appName,
"icon": notif.appIcon,
"summary": notif.summary !== "" ? notif.summary : notif.appName,
"body": notif.body,
"urgency": notif.urgency
});
}
}
ListModel {
id: notifModel
}
function removeById(nid) {
for (let i = 0; i < notifModel.count; i++) {
if (notifModel.get(i).nid === nid) {
notifModel.remove(i);
return;
}
}
}
// ── Toast list ───────────────────────────────────────────────────────────
// ── Clear all button (visible when 2+ notifications) ────────────────────
Rectangle {
id: clearAllBtn
visible: notifModel.count >= 2
anchors { top: parent.top; right: parent.right; topMargin: 8; rightMargin: 8 }
width: 340
height: 26
color: clearHover.hovered ? Colors.surface : Colors.bgDark
border.color: Colors.surface
border.width: 1
radius: 6
Text {
anchors.centerIn: parent
text: "Clear all (" + notifModel.count + ")"
color: Colors.fgDim
font.pixelSize: 12
}
HoverHandler { id: clearHover }
TapHandler { onTapped: notifModel.clear() }
}
ListView {
id: notifList
anchors {
top: clearAllBtn.visible ? clearAllBtn.bottom : parent.top
right: parent.right
topMargin: clearAllBtn.visible ? 6 : 8
rightMargin: 8
}
width: 340
height: contentHeight
model: notifModel
spacing: 6
interactive: false
add: Transition {
NumberAnimation {
property: "x"
from: 356
to: 0
duration: 220
easing.type: Easing.OutCubic
}
NumberAnimation {
property: "opacity"
from: 0
to: 1
duration: 180
}
}
remove: Transition {
NumberAnimation {
property: "opacity"
to: 0
duration: 130
easing.type: Easing.InCubic
}
NumberAnimation {
property: "x"
to: 356
duration: 180
easing.type: Easing.InCubic
}
}
displaced: Transition {
NumberAnimation {
properties: "y"
duration: 220
easing.type: Easing.OutCubic
}
NumberAnimation {
property: "opacity"
to: 1
duration: 220
}
}
delegate: Item {
id: toast
required property int index
required property int nid
required property string app
required property string icon
required property string summary
required property string body
required property int urgency
width: 340
height: toastBg.height
// ── Background card ──────────────────────────────────────────────
Rectangle {
id: toastBg
width: parent.width
height: cardContent.implicitHeight + 20
color: Colors.bgDark
border.color: toast.urgency === 2 ? Colors.critical : Colors.surface
border.width: 1
radius: 8
// Urgency stripe on left edge
Rectangle {
anchors {
left: parent.left
top: parent.top
bottom: parent.bottom
topMargin: 1
bottomMargin: 1
leftMargin: 1
}
width: 3
radius: 8
color: toast.urgency === 0 ? Colors.fgDim : toast.urgency === 2 ? Colors.critical : Colors.accent
}
ColumnLayout {
id: cardContent
anchors {
left: parent.left
right: parent.right
top: parent.top
leftMargin: 14
rightMargin: 10
topMargin: 10
}
spacing: 4
// ── Header row: icon + summary + app name + close button ─
RowLayout {
Layout.fillWidth: true
spacing: 6
// App icon (shown when available)
IconImage {
visible: toast.icon !== ""
source: toast.icon
width: 16
height: 16
mipmap: true
}
Text {
text: toast.summary
color: toast.urgency === 2 ? Colors.critical : toast.urgency === 0 ? Colors.fgDim : Colors.accent
font.pixelSize: 13
font.weight: Font.Medium
Layout.fillWidth: true
elide: Text.ElideRight
}
Text {
text: toast.app
color: Colors.fgDim
font.pixelSize: 11
elide: Text.ElideRight
Layout.maximumWidth: 90
}
Rectangle {
width: 18
height: 18
radius: 4
color: closeHover.hovered ? Colors.surface : "transparent"
HoverHandler {
id: closeHover
}
TapHandler {
onTapped: root.removeById(toast.nid)
}
Text {
anchors.centerIn: parent
text: "✕"
color: Colors.fgDim
font.pixelSize: 11
}
}
}
// ── Body text ────────────────────────────────────────────
Text {
visible: toast.body !== ""
text: toast.body
color: Colors.fg
font.pixelSize: 12
Layout.fillWidth: true
wrapMode: Text.WordWrap
maximumLineCount: 3
elide: Text.ElideRight
bottomPadding: 2
}
}
}
}
}
}

1
qmldir Normal file
View File

@@ -0,0 +1 @@
singleton Colors 1.0 Colors.qml

View File

@@ -4,8 +4,9 @@ pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import "bar"
import "notifications"
// Root scope — creates one Bar per connected screen.
// Root scope — creates one Bar per connected screen plus a single notification overlay.
Scope {
Variants {
model: Quickshell.screens
@@ -15,4 +16,6 @@ Scope {
screen: modelData
}
}
NotificationPopups {}
}