10 Commits
Author SHA1 Message Date
zach 4f544981c1 delete unknown json objects + write missing objects to config file 2026-08-16 12:17:50 +02:00
zach 02bb7e43d8 Merge pull request 'Feat: Reposition bar' (#147) from feat/reposition-bar into main
Reviewed-on: #147
Reviewed-by: AramJonghu <aramjonghu@tutamail.com>
2026-08-16 11:02:22 +02:00
zach fb0c75a2ba remove debug logging
C++ / fmt (pull_request) Successful in 8s
JS/TS / fmt (pull_request) Successful in 11s
JS/TS / lint (pull_request) Successful in 15s
Python / fmt (pull_request) Successful in 24s
Python / lint (pull_request) Successful in 25s
Python / test (pull_request) Successful in 48s
Python / typecheck (pull_request) Successful in 1m1s
Rust / fmt (pull_request) Successful in 34s
Rust / build (pull_request) Successful in 1m45s
C++ / build (pull_request) Successful in 4m18s
Python / buildcheck (pull_request) Successful in 2m20s
Rust / clippy (pull_request) Successful in 1m1s
C++ / clang-tidy (pull_request) Successful in 5m23s
2026-08-15 19:38:33 +02:00
zach 5efa0de3a5 WidgetBase for bar widget + fixed resources and dashboard popouts in left- and bottom-edge bar
C++ / fmt (pull_request) Successful in 3s
JS/TS / fmt (pull_request) Successful in 17s
JS/TS / lint (pull_request) Successful in 20s
Python / fmt (pull_request) Successful in 31s
Python / lint (pull_request) Successful in 30s
Python / test (pull_request) Successful in 1m40s
Python / typecheck (pull_request) Successful in 1m38s
C++ / build (pull_request) Successful in 2m15s
Rust / build (pull_request) Successful in 1m41s
Rust / fmt (pull_request) Successful in 59s
Python / buildcheck (pull_request) Successful in 2m40s
Rust / clippy (pull_request) Successful in 2m15s
C++ / clang-tidy (pull_request) Successful in 6m29s
2026-08-15 19:37:44 +02:00
zach 858ef68565 Merge branch 'main' into feat/reposition-bar
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 21s
JS/TS / lint (pull_request) Successful in 22s
Python / lint (pull_request) Successful in 31s
Python / fmt (pull_request) Successful in 34s
Python / test (pull_request) Successful in 59s
Python / typecheck (pull_request) Successful in 1m22s
Rust / fmt (pull_request) Successful in 34s
Rust / build (pull_request) Successful in 1m46s
C++ / build (pull_request) Successful in 2m33s
Rust / clippy (pull_request) Successful in 1m5s
Python / buildcheck (pull_request) Successful in 2m29s
C++ / clang-tidy (pull_request) Successful in 3m56s
2026-08-15 18:04:14 +02:00
zach 5e97ea6867 Merge pull request 'CI: typecheck fixes include dependencies + arch Dockerfile build fix' (#148) from fix/cli-typecheck into main
Reviewed-on: #148
Reviewed-by: zach <zach@brohn.se>
2026-08-15 18:03:54 +02:00
zach 9823c66299 restructure bar components layout
C++ / fmt (pull_request) Successful in 4s
JS/TS / lint (pull_request) Successful in 19s
JS/TS / fmt (pull_request) Successful in 21s
Python / fmt (pull_request) Successful in 30s
Python / lint (pull_request) Successful in 31s
Python / test (pull_request) Successful in 1m2s
Python / typecheck (pull_request) Failing after 1m5s
Rust / fmt (pull_request) Successful in 43s
C++ / build (pull_request) Successful in 2m25s
Rust / build (pull_request) Successful in 1m42s
Python / buildcheck (pull_request) Successful in 2m27s
Rust / clippy (pull_request) Successful in 1m29s
C++ / clang-tidy (pull_request) Successful in 3m56s
2026-08-15 14:18:00 +02:00
zach 0092b41a05 fix config file writing + positioning issues
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 22s
JS/TS / lint (pull_request) Successful in 27s
Python / fmt (pull_request) Successful in 33s
Python / lint (pull_request) Successful in 32s
Python / test (pull_request) Successful in 1m3s
Python / typecheck (pull_request) Failing after 1m4s
Rust / fmt (pull_request) Successful in 41s
Rust / build (pull_request) Successful in 1m39s
C++ / build (pull_request) Successful in 2m33s
Rust / clippy (pull_request) Successful in 1m18s
Python / buildcheck (pull_request) Successful in 2m23s
C++ / clang-tidy (pull_request) Successful in 3m56s
2026-08-14 22:15:11 +02:00
AramJonghu abf7382771 ci: libcava dev headers
C++ / fmt (pull_request) Successful in 5s
JS/TS / fmt (pull_request) Successful in 12s
JS/TS / lint (pull_request) Successful in 16s
Python / lint (pull_request) Successful in 33s
Python / fmt (pull_request) Successful in 36s
Python / test (pull_request) Successful in 1m3s
Python / typecheck (pull_request) Successful in 1m21s
Rust / fmt (pull_request) Successful in 33s
C++ / build (pull_request) Successful in 2m26s
Rust / build (pull_request) Successful in 1m43s
Python / buildcheck (pull_request) Successful in 2m12s
Rust / clippy (pull_request) Successful in 1m41s
C++ / clang-tidy (pull_request) Successful in 4m10s
2026-08-14 20:00:27 +02:00
AramJonghu a1a8a6e695 ci: add relevant dependencies; resolves MissingImport
C++ / fmt (pull_request) Successful in 4s
JS/TS / lint (pull_request) Successful in 15s
JS/TS / fmt (pull_request) Successful in 21s
Python / fmt (pull_request) Successful in 31s
Python / lint (pull_request) Successful in 34s
Python / test (pull_request) Successful in 1m0s
Python / typecheck (pull_request) Successful in 1m20s
Rust / fmt (pull_request) Successful in 34s
C++ / build (pull_request) Successful in 2m29s
Rust / build (pull_request) Successful in 1m45s
Rust / clippy (pull_request) Successful in 1m2s
Python / buildcheck (pull_request) Successful in 2m18s
C++ / clang-tidy (pull_request) Successful in 3m52s
2026-08-14 19:40:28 +02:00
59 changed files with 670 additions and 625 deletions
+1 -1
View File
@@ -97,7 +97,7 @@ jobs:
py3-pip py3-pip
python3 -m venv .venv python3 -m venv .venv
. .venv/bin/activate . .venv/bin/activate
pip install --no-cache-dir basedpyright pip install --no-cache-dir basedpyright typer pillow materialyoucolor jinja2
- name: Type check - name: Type check
run: | run: |
+147 -129
View File
@@ -18,13 +18,14 @@ Item {
property real leftFadeStrength: overflowing && leftFadeEnabled ? fadeStrengthMoving : fadeStrengthIdle property real leftFadeStrength: overflowing && leftFadeEnabled ? fadeStrengthMoving : fadeStrengthIdle
property int leftFadeWidth: 28 property int leftFadeWidth: 28
property bool marqueeEnabled: true property bool marqueeEnabled: true
readonly property bool overflowing: metrics.width > root.width readonly property bool overflowing: metrics.width > content.width
property int pauseMs: 1200 property int pauseMs: 1200
property real pixelsPerSecond: 40 property real pixelsPerSecond: 40
property real rightFadeStrength: overflowing ? fadeStrengthMoving : fadeStrengthIdle property real rightFadeStrength: overflowing ? fadeStrengthMoving : fadeStrengthIdle
property int rightFadeWidth: 28 property int rightFadeWidth: 28
property bool sliding: false property bool sliding: false
property alias text: elideText.text property alias text: elideText.text
property bool vertical: false
function durationForDistance(px): int { function durationForDistance(px): int {
return Math.max(1, Math.round(Math.abs(px) / root.pixelsPerSecond * 1000)); return Math.max(1, Math.round(Math.abs(px) / root.pixelsPerSecond * 1000));
@@ -42,7 +43,8 @@ Item {
} }
clip: false clip: false
implicitHeight: elideText.implicitHeight implicitHeight: vertical ? elideText.implicitWidth : elideText.implicitHeight
implicitWidth: vertical ? elideText.implicitHeight : elideText.implicitWidth
Behavior on leftFadeStrength { Behavior on leftFadeStrength {
Anim { Anim {
@@ -53,171 +55,187 @@ Item {
} }
} }
onHeightChanged: resetMarquee()
onTextChanged: resetMarquee() onTextChanged: resetMarquee()
onVerticalChanged: resetMarquee()
onVisibleChanged: if (!visible) onVisibleChanged: if (!visible)
resetMarquee() resetMarquee()
onWidthChanged: resetMarquee() onWidthChanged: resetMarquee()
TextMetrics {
id: metrics
font: elideText.font
text: elideText.text
}
CustomText {
id: elideText
anchors.verticalCenter: parent.verticalCenter
animate: root.animate
animateProp: "scale,opacity"
color: root.color
elide: Text.ElideNone
visible: !root.overflowing
width: root.width
}
Item { Item {
id: marqueeViewport id: content
anchors.fill: parent anchors.centerIn: parent
clip: false height: root.vertical ? root.width : root.height
layer.enabled: true width: root.vertical ? root.height : root.width
visible: root.overflowing
layer.effect: OpacityMask { transform: Rotation {
maskSource: rightFadeMask angle: root.vertical ? -90 : 0
origin.x: content.width / 2
origin.y: content.height / 2
}
TextMetrics {
id: metrics
font: elideText.font
text: elideText.text
}
CustomText {
id: elideText
anchors.verticalCenter: parent.verticalCenter
animate: root.animate
animateProp: "scale,opacity"
color: root.color
elide: Text.ElideNone
visible: !root.overflowing
width: content.width
} }
Item { Item {
id: strip id: marqueeViewport
anchors.verticalCenter: parent.verticalCenter anchors.fill: parent
height: t1.implicitHeight clip: false
width: t1.width + root.gap + t2.width layer.enabled: true
x: 0 visible: root.overflowing
CustomText { layer.effect: OpacityMask {
id: t1 maskSource: rightFadeMask
animate: root.animate
animateProp: "opacity"
color: root.color
font.pointSize: elideText.font.pointSize
text: elideText.text
} }
CustomText { Item {
id: t2 id: strip
animate: root.animate anchors.verticalCenter: parent.verticalCenter
animateProp: "opacity" height: t1.implicitHeight
color: root.color width: t1.width + root.gap + t2.width
font.pointSize: elideText.font.pointSize x: 0
text: t1.text
x: t1.width + root.gap
}
}
SequentialAnimation { CustomText {
id: marqueeAnim id: t1
running: false animate: root.animate
animateProp: "opacity"
color: root.color
font.pointSize: elideText.font.pointSize
text: elideText.text
}
onFinished: pauseTimer.restart() CustomText {
id: t2
ScriptAction { animate: root.animate
script: { animateProp: "opacity"
root.sliding = true; color: root.color
root.leftFadeEnabled = true; font.pointSize: elideText.font.pointSize
text: t1.text
x: t1.width + root.gap
} }
} }
Anim { SequentialAnimation {
duration: root.durationForDistance(t1.width) id: marqueeAnim
easing.bezierCurve: Easing.Linear
easing.type: Easing.Linear
from: 0
property: "x"
target: strip
to: -t1.width
}
ScriptAction { running: false
script: {
root.leftFadeEnabled = false; onFinished: pauseTimer.restart()
ScriptAction {
script: {
root.sliding = true;
root.leftFadeEnabled = true;
}
}
Anim {
duration: root.durationForDistance(t1.width)
easing.bezierCurve: Easing.Linear
easing.type: Easing.Linear
from: 0
property: "x"
target: strip
to: -t1.width
}
ScriptAction {
script: {
root.leftFadeEnabled = false;
}
}
Anim {
duration: root.durationForDistance(root.gap)
easing.bezierCurve: Easing.Linear
easing.type: Easing.Linear
from: -t1.width
property: "x"
target: strip
to: -(t1.width + root.gap)
}
ScriptAction {
script: {
root.sliding = false;
strip.x = 0;
}
} }
} }
Anim { Timer {
duration: root.durationForDistance(root.gap) id: pauseTimer
easing.bezierCurve: Easing.Linear
easing.type: Easing.Linear
from: -t1.width
property: "x"
target: strip
to: -(t1.width + root.gap)
}
ScriptAction { interval: root.pauseMs
script: { repeat: false
root.sliding = false; running: true
strip.x = 0;
onTriggered: {
if (root.marqueeEnabled)
marqueeAnim.start();
} }
} }
} }
Timer { Rectangle {
id: pauseTimer id: rightFadeMask
interval: root.pauseMs readonly property real fadeStartPos: {
repeat: false const w = Math.max(1, width);
running: true return Math.max(0, Math.min(1, (w - root.rightFadeWidth) / w));
onTriggered: {
if (root.marqueeEnabled)
marqueeAnim.start();
} }
} readonly property real leftFadeEndPos: {
} const w = Math.max(1, width);
return Math.max(0, Math.min(1, root.leftFadeWidth / w));
Rectangle {
id: rightFadeMask
readonly property real fadeStartPos: {
const w = Math.max(1, width);
return Math.max(0, Math.min(1, (w - root.rightFadeWidth) / w));
}
readonly property real leftFadeEndPos: {
const w = Math.max(1, width);
return Math.max(0, Math.min(1, root.leftFadeWidth / w));
}
anchors.fill: marqueeViewport
layer.enabled: true
visible: false
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop {
color: Qt.rgba(1, 1, 1, 1.0 - root.leftFadeStrength)
position: 0.0
} }
GradientStop { anchors.fill: marqueeViewport
color: Qt.rgba(1, 1, 1, 1.0) layer.enabled: true
position: rightFadeMask.leftFadeEndPos visible: false
}
GradientStop { gradient: Gradient {
color: Qt.rgba(1, 1, 1, 1.0) orientation: Gradient.Horizontal
position: rightFadeMask.fadeStartPos
}
GradientStop { GradientStop {
color: Qt.rgba(1, 1, 1, 1.0 - root.rightFadeStrength) color: Qt.rgba(1, 1, 1, 1.0 - root.leftFadeStrength)
position: 1.0 position: 0.0
}
GradientStop {
color: Qt.rgba(1, 1, 1, 1.0)
position: rightFadeMask.leftFadeEndPos
}
GradientStop {
color: Qt.rgba(1, 1, 1, 1.0)
position: rightFadeMask.fadeStartPos
}
GradientStop {
color: Qt.rgba(1, 1, 1, 1.0 - root.rightFadeStrength)
position: 1.0
}
} }
} }
} }
+4 -4
View File
@@ -126,7 +126,7 @@ MouseArea {
CustomRect { CustomRect {
id: item id: item
readonly property bool active: modelData === root.active readonly property bool active: modelData === root?.active
required property int index required property int index
required property MenuItem modelData required property MenuItem modelData
@@ -163,19 +163,19 @@ MouseArea {
MaterialIcon { MaterialIcon {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
color: item.active ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurfaceVariant color: item.active ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurfaceVariant
text: item.modelData.icon text: item.modelData?.icon ?? ""
} }
CustomText { CustomText {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true Layout.fillWidth: true
color: item.active ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurface color: item.active ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurface
text: item.modelData.text text: item.modelData?.text ?? ""
} }
Loader { Loader {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
active: item.modelData.trailingIcon.length > 0 active: item.modelData?.trailingIcon.length > 0
asynchronous: true asynchronous: true
visible: active visible: active
-4
View File
@@ -27,10 +27,6 @@ Variants {
} }
Connections { Connections {
function onConfigDashboardPositionChanged(): void {
Qt.callLater(scope.rebuild);
}
function onConfigPositionChanged(): void { function onConfigPositionChanged(): void {
Qt.callLater(scope.rebuild); Qt.callLater(scope.rebuild);
} }
+2 -4
View File
@@ -3,7 +3,7 @@ import QtQuick
import qs.Components import qs.Components
import ZShell.Config import ZShell.Config
import qs.Helpers import qs.Helpers
import qs.Modules as BarPopouts import qs.Modules.Bar.Popouts
Item { Item {
id: root id: root
@@ -15,7 +15,7 @@ Item {
required property EdgeGeometry geometry required property EdgeGeometry geometry
property bool osdShortcutActive property bool osdShortcutActive
required property Panels panels required property Panels panels
required property BarPopouts.Wrapper popouts required property Wrapper popouts
required property ShellScreen screen required property ShellScreen screen
property bool singleGestureTriggered: false property bool singleGestureTriggered: false
property bool utilitiesShortcutActive property bool utilitiesShortcutActive
@@ -302,8 +302,6 @@ Item {
root.visibilities.sidebar = false; root.visibilities.sidebar = false;
root.panels.popouts.hasCurrent = false; root.panels.popouts.hasCurrent = false;
root.visibilities.launcher = false; root.visibilities.launcher = false;
} else {
Config.save();
} }
} }
+6 -7
View File
@@ -1,7 +1,7 @@
import Quickshell import Quickshell
import QtQuick import QtQuick
import qs.Components import qs.Components
import qs.Modules as Modules import qs.Modules.Bar.Popouts as Popouts
import qs.Modules.Notifications as Notifications import qs.Modules.Notifications as Notifications
import qs.Modules.Notifications.Sidebar as Sidebar import qs.Modules.Notifications.Sidebar as Sidebar
import qs.Modules.Notifications.Sidebar.Utils as Utils import qs.Modules.Notifications.Sidebar.Utils as Utils
@@ -53,8 +53,9 @@ Item {
Item { Item {
id: resourcesWrapper id: resourcesWrapper
anchors.bottom: root.geometry.position === "bottom" ? parent.bottom : undefined
anchors.left: parent.left anchors.left: parent.left
anchors.top: parent.top anchors.top: root.geometry.position !== "bottom" ? parent.top : undefined
clip: true clip: true
implicitHeight: root.geometry.horizontal ? resources.implicitHeight * (1 - resources.offsetScale) : resources.implicitHeight implicitHeight: root.geometry.horizontal ? resources.implicitHeight * (1 - resources.offsetScale) : resources.implicitHeight
implicitWidth: root.geometry.horizontal ? resources.implicitWidth : resources.implicitWidth * (1 - resources.offsetScale) implicitWidth: root.geometry.horizontal ? resources.implicitWidth : resources.implicitWidth * (1 - resources.offsetScale)
@@ -62,9 +63,7 @@ Item {
Resources.Wrapper { Resources.Wrapper {
id: resources id: resources
anchors.left: parent.left position: root.geometry.position
anchors.top: parent.top
horizontal: root.geometry.horizontal
visibilities: root.visibilities visibilities: root.visibilities
} }
} }
@@ -100,7 +99,7 @@ Item {
visibilities: root.visibilities visibilities: root.visibilities
} }
Modules.ClipWrapper { Popouts.ClipWrapper {
id: popouts id: popouts
borderThickness: root.borderThickness borderThickness: root.borderThickness
@@ -169,8 +168,8 @@ Item {
Dashboard.Wrapper { Dashboard.Wrapper {
id: dashboard id: dashboard
horizontal: root.geometry.horizontal
offsetScale: dashboardWrapper.offsetScale offsetScale: dashboardWrapper.offsetScale
position: root.geometry.position
visibilities: root.visibilities visibilities: root.visibilities
} }
} }
+1 -1
View File
@@ -221,7 +221,7 @@ CustomWindow {
panel: panels.dashboardWrapper panel: panels.dashboardWrapper
radius: Tokens.rounding.normal radius: Tokens.rounding.normal
x: panels.dashboardWrapper.x + panels.dashboard.x + geometry.insetLeft(root.borderThickness) - (geometry.horizontal ? 0 : panels.dashboard.width * extraExtent) x: panels.dashboardWrapper.x + panels.dashboard.x + geometry.insetLeft(root.borderThickness) - (geometry.horizontal ? 0 : panels.dashboard.width * extraExtent)
y: panels.dashboardWrapper.y + panels.dashboard.y + geometry.insetTop(root.borderThickness) - (geometry.horizontal ? panels.dashboard.height * extraExtent : 0) y: panels.dashboardWrapper.y + panels.dashboard.y + geometry.insetTop(root.borderThickness) - (geometry.horizontal && geometry.position !== "bottom" ? panels.dashboard.height * extraExtent : 0)
} }
PanelBg { PanelBg {
+51 -2
View File
@@ -1,9 +1,8 @@
pragma Singleton pragma Singleton
import ZShell.Config import QtQuick
import Quickshell import Quickshell
import Quickshell.Services.Notifications import Quickshell.Services.Notifications
import QtQuick
Singleton { Singleton {
id: root id: root
@@ -95,6 +94,15 @@ Singleton {
return Quickshell.iconPath(icon); return Quickshell.iconPath(icon);
} }
function getBatteryIcon(percentage: real, charging = false): string {
if (percentage === 1)
return charging ? "battery_charging_full" : "battery_full";
let level = Math.floor(percentage * 7);
if (charging && (level === 4 || level === 1))
level--;
return charging ? `battery_charging_${(level + 3) * 10}` : `battery_${level}_bar`;
}
function getBluetoothIcon(icon: string): string { function getBluetoothIcon(icon: string): string {
if (icon.includes("headset") || icon.includes("headphones")) if (icon.includes("headset") || icon.includes("headphones"))
return "headphones"; return "headphones";
@@ -168,6 +176,32 @@ Singleton {
return "chat"; return "chat";
} }
function getSpecialWsIcon(name: string): string {
name = name.toLowerCase().slice("special:".length);
if (name === "special")
return "star";
if (name === "communication")
return "forum";
if (name === "music")
return "music_cast";
if (name === "todo")
return "checklist";
if (name === "sysmon")
return "monitor_heart";
return name[0].toUpperCase();
}
function getTrayIcon(id: string, icon: string): string {
if (icon.includes("?path=")) {
const [name, path] = icon.split("?path=");
const file = name.slice(name.lastIndexOf("/") + 1);
const themed = Quickshell.iconPath(file, true);
icon = themed ? themed : Qt.resolvedUrl(`${path}/${file}`);
}
return icon;
}
function getVolumeIcon(volume: real, isMuted: bool): string { function getVolumeIcon(volume: real, isMuted: bool): string {
if (isMuted) if (isMuted)
return "no_sound"; return "no_sound";
@@ -183,4 +217,19 @@ Singleton {
return weatherIcons[code]; return weatherIcons[code];
return "air"; return "air";
} }
function matchIconConfig(name: string, iconConfig: var): bool {
if (!iconConfig.icon)
return false;
if (iconConfig.regex) {
const re = new RegExp(iconConfig.regex, iconConfig.flags ?? "");
if (re.test(name))
return true;
} else if (iconConfig.name === name) {
return true;
}
return false;
}
} }
-1
View File
@@ -73,7 +73,6 @@ Singleton {
Config.dock.pinnedApps = pinnedApps; Config.dock.pinnedApps = pinnedApps;
root.unpinnedOrder = visibleUnpinned.concat(root.unpinnedOrder.map(normalizeId).filter(id => !pinnedApps.includes(id) && !visibleUnpinned.includes(id))); root.unpinnedOrder = visibleUnpinned.concat(root.unpinnedOrder.map(normalizeId).filter(id => !pinnedApps.includes(id) && !visibleUnpinned.includes(id)));
Config.saveNoToast();
} }
function isPinned(appId) { function isPinned(appId) {
+2 -4
View File
@@ -3,11 +3,9 @@ pragma ComponentBehavior: Bound
import Quickshell import Quickshell
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import qs.Modules
import ZShell.Config import ZShell.Config
import qs.Modules.SysTray import qs.Modules.Bar.Components
import qs.Modules.Network import qs.Modules.Bar.Popouts
import qs.Modules.Updates
GridLayout { GridLayout {
id: root id: root
+2 -6
View File
@@ -3,18 +3,14 @@ pragma ComponentBehavior: Bound
import Quickshell import Quickshell
import QtQuick import QtQuick
import ZShell.Config import ZShell.Config
import qs.Modules.Bar.Popouts
import qs.Components import qs.Components
import qs.Modules
import qs.Helpers
import qs.Modules.SysTray
import qs.Modules.SysTray.Widgets
import qs.Modules.Network
Item { Item {
id: root id: root
readonly property int clampedExtent: Math.max(Config.bar.border, extent) readonly property int clampedExtent: Math.max(Config.bar.border, extent)
readonly property int contentThickness: Math.max(Config.bar.height, 30) + padding * 2 readonly property int contentThickness: Math.floor(Math.max(Config.bar.height, Tokens.bar.innerSize * (horizontal ? 1 : 1.5))) + padding * 2
readonly property int exclusiveZone: Config.bar.autoHide ? Config.bar.border : contentThickness readonly property int exclusiveZone: Config.bar.autoHide ? Config.bar.border : contentThickness
property real extent: fullscreen ? 0 : Config.bar.border property real extent: fullscreen ? 0 : Config.bar.border
required property bool fullscreen required property bool fullscreen
+157
View File
@@ -0,0 +1,157 @@
pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import QtQuick.Layouts
import ZShell.Config
import qs.Modules.Bar.Popouts
import qs.Modules.Bar.Components.Common
import qs.Helpers
import qs.Components
import qs.Services
WidgetBase {
id: root
readonly property color contentColor: visibilities.dashboard ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
readonly property string effectiveFormat: {
let fmt = Config.general.dateFormat;
if (!root.horizontal)
fmt = fmt.replace("dddd", "ddd");
return fmt;
}
readonly property var formatParts: {
const segments = root.effectiveFormat.split(" - ");
let datePart = "";
let timePart = "";
if (segments.length > 1) {
datePart = segments[0];
timePart = segments[1];
} else if (/[hms]/.test(segments[0])) {
timePart = segments[0];
} else {
datePart = segments[0];
}
const dateTokens = datePart.trim().length ? datePart.trim().split(/\s+/) : [];
const timeTokens = timePart.trim().length ? timePart.trim().split(":") : [];
return {
dateTokens: dateTokens,
timeTokens: timeTokens
};
}
required property GridLayout loader
required property Wrapper popouts
readonly property real size: horizontal ? timeText.contentWidth + Tokens.padding.normal * 2 : verticalColumn.implicitHeight + Tokens.padding.larger * 2
required property PersistentProperties visibilities
color: visibilities.dashboard ? Colors.palette.m3primary : Colors.tPalette.m3surfaceContainer
implicitHeight: horizontal ? baseSize : size
implicitWidth: horizontal ? size : baseSize
radius: Tokens.rounding.full
CustomText {
id: timeText
anchors.centerIn: parent
color: root.contentColor
font: Config.appearance.font.family.mono // qmllint disable incompatible-type
height: implicitHeight
text: Time.dateStr
visible: root.horizontal
Behavior on color {
CAnim {
}
}
}
ColumnLayout {
id: verticalColumn
anchors.centerIn: parent
spacing: Tokens.padding.small ?? 2
visible: !root.horizontal
width: root.baseSize
Repeater {
model: root.formatParts.dateTokens
CustomText {
required property string modelData
Layout.alignment: Qt.AlignHCenter
color: root.contentColor
font: Config.appearance.font.family.mono // qmllint disable incompatible-type
text: Qt.formatDateTime(Time.date, modelData)
Behavior on color {
CAnim {
}
}
}
}
Rectangle {
Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: Tokens.padding.small ?? 2
Layout.topMargin: Tokens.padding.small ?? 2
color: root.contentColor
height: 1
implicitWidth: root.baseSize * 0.5
opacity: 0.4
radius: 1
visible: root.formatParts.dateTokens.length > 0 && root.formatParts.timeTokens.length > 0
}
Repeater {
model: root.formatParts.timeTokens
CustomText {
required property string modelData
Layout.alignment: Qt.AlignHCenter
color: root.contentColor
font: Config.appearance.font.family.mono // qmllint disable incompatible-type
text: {
if (modelData.includes("h"))
return Time.hourStr;
if (modelData.includes("m"))
return Time.minuteStr;
if (modelData.includes("s"))
return Time.secondStr;
return "";
}
Behavior on color {
CAnim {
}
}
}
}
CustomText {
Layout.alignment: Qt.AlignHCenter
color: root.contentColor
font: Config.appearance.font.family.mono // qmllint disable incompatible-type
text: Qt.formatDateTime(Time.date, "AP")
visible: Config.services.useTwelveHourClock && root.formatParts.timeTokens.length > 0
Behavior on color {
CAnim {
}
}
}
}
StateLayer {
acceptedButtons: Qt.LeftButton
color: root.contentColor
onClicked: {
root.visibilities.dashboard = !root.visibilities.dashboard;
}
}
}
@@ -0,0 +1,16 @@
import QtQuick
import ZShell.Config
import qs.Components
import qs.Services
CustomRect {
id: root
readonly property int baseSize: horizontal ? Math.max(Config.bar.height, Tokens.bar.innerSize) : Math.max(Config.bar.height, Math.floor(Tokens.bar.innerSize * 1.5))
required property bool horizontal
color: Colors.tPalette.m3surfaceContainer
implicitHeight: horizontal ? baseSize : width
implicitWidth: horizontal ? height : baseSize
radius: Tokens.rounding.full
}
@@ -1,18 +1,16 @@
import QtQuick import QtQuick
import ZShell.Config import ZShell.Config
import qs.Components import qs.Components
import qs.Modules.Bar.Components.Common
import qs.Helpers import qs.Helpers
import qs.Services import qs.Services
CustomRect { WidgetBase {
id: root id: root
required property bool horizontal
property bool tempEnabled: Hyprsunset.enabled property bool tempEnabled: Hyprsunset.enabled
color: root.tempEnabled ? Colors.palette.m3primary : Colors.tPalette.m3surfaceContainer color: root.tempEnabled ? Colors.palette.m3primary : Colors.tPalette.m3surfaceContainer
implicitHeight: horizontal ? Config.bar.height : width
implicitWidth: horizontal ? height : Config.bar.height
radius: Tokens.rounding.full radius: Tokens.rounding.full
StateLayer { StateLayer {
@@ -27,7 +25,7 @@ CustomRect {
animate: true animate: true
color: root.tempEnabled ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface color: root.tempEnabled ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
fill: root.tempEnabled ? 1 : 0 fill: root.tempEnabled ? 1 : 0
font.pointSize: root.horizontal ? Tokens.font.size.larger : Tokens.font.size.large font.pointSize: Tokens.font.size.larger
text: root.tempEnabled ? "lightbulb" : "light_off" text: root.tempEnabled ? "lightbulb" : "light_off"
Behavior on fill { Behavior on fill {
@@ -1,24 +1,30 @@
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import qs.Components
import qs.Daemons
import ZShell.Config import ZShell.Config
import qs.Components
import qs.Modules.Bar.Components.Common
import qs.Helpers import qs.Helpers
import qs.Services import qs.Services
CustomRect { WidgetBase {
id: root id: root
readonly property string currentMedia: (Players.active?.trackTitle ?? qsTr("No media")) || qsTr("Unknown title") readonly property string currentMedia: (Players.active?.trackTitle ?? qsTr("No media")) || qsTr("Unknown title")
required property bool horizontal
readonly property int textWidth: Math.min(metrics.width, 200) readonly property int textWidth: Math.min(metrics.width, 200)
color: Colors.tPalette.m3surfaceContainer implicitHeight: horizontal ? baseSize : layout.implicitHeight + Tokens.padding.normal * 2
implicitHeight: horizontal ? Config.bar.height : layout.implicitHeight + Tokens.padding.normal * 2 implicitWidth: horizontal ? layout.implicitWidth + Tokens.padding.normal * 2 : baseSize
implicitWidth: horizontal ? layout.implicitWidth + Tokens.padding.normal * 2 : Config.bar.height
radius: Tokens.rounding.full radius: Tokens.rounding.full
Behavior on implicitHeight {
enabled: !root.horizontal
Anim {
}
}
Behavior on implicitWidth { Behavior on implicitWidth {
enabled: root.horizontal
Anim { Anim {
} }
} }
@@ -33,7 +39,11 @@ CustomRect {
GridLayout { GridLayout {
id: layout id: layout
anchors.centerIn: parent anchors.bottomMargin: root.horizontal ? 0 : Tokens.padding.normal
anchors.fill: parent
anchors.leftMargin: root.horizontal ? Tokens.padding.normal : 0
anchors.rightMargin: root.horizontal ? Tokens.padding.normal : 0
anchors.topMargin: root.horizontal ? 0 : Tokens.padding.normal
columns: root.horizontal ? -1 : 1 columns: root.horizontal ? -1 : 1
Behavior on implicitWidth { Behavior on implicitWidth {
@@ -53,39 +63,29 @@ CustomRect {
id: mediatext id: mediatext
Layout.alignment: root.horizontal ? Qt.AlignVCenter : Qt.AlignHCenter Layout.alignment: root.horizontal ? Qt.AlignVCenter : Qt.AlignHCenter
Layout.preferredHeight: root.horizontal ? root.height : root.textWidth
animate: true animate: true
color: Players.active?.isPlaying ? Colors.palette.m3primary : Colors.palette.m3onSurface color: Players.active?.isPlaying ? Colors.palette.m3primary : Colors.palette.m3onSurface
font.pointSize: Tokens.font.size.normal font.pointSize: Tokens.font.size.normal
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
implicitHeight: root.horizontal ? root.implicitHeight : root.textWidth
implicitWidth: root.horizontal ? root.textWidth : root.implicitWidth
marqueeEnabled: false marqueeEnabled: false
pauseMs: 4000 pauseMs: 4000
text: root.currentMedia text: root.currentMedia
width: root.textWidth vertical: !root.horizontal
}
}
transform: [ CustomMouseArea {
Translate { anchors.fill: parent
x: !root.horizontal ? -root.textWidth + mediatext.implicitHeight : 0 hoverEnabled: true
},
Rotation {
angle: root.horizontal ? 0 : 270
origin.x: mediatext.implicitHeight / 2
origin.y: mediatext.implicitHeight / 2
}
]
CustomMouseArea { onContainsMouseChanged: {
anchors.fill: parent if (!containsMouse) {
hoverEnabled: true mediatext.marqueeEnabled = false;
} else {
onContainsMouseChanged: { mediatext.marqueeEnabled = true;
if (!containsMouse) { mediatext.anim.start();
mediatext.marqueeEnabled = false;
} else {
mediatext.marqueeEnabled = true;
mediatext.anim.start();
}
}
} }
} }
} }
@@ -1,20 +1,19 @@
import Quickshell import Quickshell
import QtQuick import QtQuick
import ZShell.Config import ZShell.Config
import qs.Modules.Bar.Components.Common
import qs.Modules.Bar.Popouts
import qs.Daemons import qs.Daemons
import qs.Components import qs.Components
import qs.Services import qs.Services
CustomRect { WidgetBase {
id: root id: root
required property bool horizontal
required property Wrapper popouts required property Wrapper popouts
required property PersistentProperties visibilities required property PersistentProperties visibilities
color: visibilities.sidebar ? Colors.palette.m3primary : Colors.tPalette.m3surfaceContainer color: visibilities.sidebar ? Colors.palette.m3primary : Colors.tPalette.m3surfaceContainer
implicitHeight: horizontal ? Config.bar.height : width
implicitWidth: horizontal ? height : Config.bar.height
radius: Tokens.rounding.full radius: Tokens.rounding.full
MaterialIcon { MaterialIcon {
@@ -23,7 +22,7 @@ CustomRect {
anchors.centerIn: parent anchors.centerIn: parent
color: root.visibilities.sidebar ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface color: root.visibilities.sidebar ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
fill: root.visibilities.sidebar ? 1 : 0 fill: root.visibilities.sidebar ? 1 : 0
font.pointSize: root.horizontal ? Tokens.font.size.larger : Tokens.font.size.large font.pointSize: Tokens.font.size.larger
text: NotifServer.list.length ? "\uf4fe" : "\ue7f4" text: NotifServer.list.length ? "\uf4fe" : "\ue7f4"
Behavior on color { Behavior on color {
@@ -49,7 +49,7 @@ GridLayout {
} }
CustomClippingRect { CustomClippingRect {
Layout.preferredHeight: root.horizontal ? icon.implicitHeight : 4 Layout.preferredHeight: root.horizontal ? icon.implicitHeight - Tokens.padding.small : 4
Layout.preferredWidth: root.horizontal ? 4 : icon.implicitWidth Layout.preferredWidth: root.horizontal ? 4 : icon.implicitWidth
color: Colors.layer(Colors.palette.m3surfaceContainerHigh, 2) color: Colors.layer(Colors.palette.m3surfaceContainerHigh, 2)
radius: Tokens.rounding.full radius: Tokens.rounding.full
@@ -63,7 +63,7 @@ GridLayout {
anchors.top: root.horizontal ? undefined : parent.top anchors.top: root.horizontal ? undefined : parent.top
color: root.mainColor color: root.mainColor
implicitHeight: root.horizontal ? Math.ceil(root.percentage * parent.height) : 0 implicitHeight: root.horizontal ? Math.ceil(root.percentage * parent.height) : 0
implicitWidth: root.horizontal ? 0 : Math.ceil(root.percentage * parent.height) implicitWidth: root.horizontal ? 0 : Math.ceil(root.percentage * parent.width)
// Behavior on implicitHeight { // Behavior on implicitHeight {
// Anim { // Anim {
@@ -6,19 +6,18 @@ import QtQuick.Layouts
import ZShell.Config import ZShell.Config
import ZShell.Services import ZShell.Services
import qs.Services import qs.Services
import qs.Modules import qs.Modules.Bar.Components.Common
import qs.Components import qs.Components
CustomRect { WidgetBase {
id: root id: root
required property bool horizontal
required property PersistentProperties visibilities required property PersistentProperties visibilities
clip: true clip: true
color: visibilities.resources ? Colors.palette.m3primary : Colors.tPalette.m3surfaceContainer color: visibilities.resources ? Colors.palette.m3primary : Colors.tPalette.m3surfaceContainer
implicitHeight: horizontal ? Math.max(Config.bar.height, 24) : gridLayout.implicitHeight + Tokens.padding.normal * 2 implicitHeight: horizontal ? baseSize : gridLayout.implicitHeight + Tokens.padding.normal * 2
implicitWidth: horizontal ? gridLayout.implicitWidth + Tokens.padding.larger * 2 : Config.bar.height implicitWidth: horizontal ? gridLayout.implicitWidth + Tokens.padding.larger * 2 : baseSize
radius: Tokens.rounding.full radius: Tokens.rounding.full
StateLayer { StateLayer {
@@ -3,13 +3,14 @@ pragma ComponentBehavior: Bound
import Quickshell import Quickshell
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import qs.Helpers
import qs.Modules.SysTray.Widgets
import ZShell.Config import ZShell.Config
import qs.Helpers
import qs.Modules.Bar.Components.StatusIcons
import qs.Modules.Bar.Components.Common
import qs.Components import qs.Components
import qs.Services import qs.Services
CustomClippingRect { WidgetBase {
id: root id: root
readonly property int firstPresent: { readonly property int firstPresent: {
@@ -19,7 +20,6 @@ CustomClippingRect {
return i; return i;
return -1; return -1;
} }
required property bool horizontal
// Index of the first/last entry that isn't collapsed, for edge margin gating // Index of the first/last entry that isn't collapsed, for edge margin gating
readonly property alias items: grid readonly property alias items: grid
@@ -30,7 +30,6 @@ CustomClippingRect {
return i; return i;
return -1; return -1;
} }
readonly property real shortSize: Config.bar.height
readonly property real size: horizontal ? grid.implicitWidth + Tokens.padding.small * 2 : grid.implicitHeight + Tokens.padding.small * 2 readonly property real size: horizontal ? grid.implicitWidth + Tokens.padding.small * 2 : grid.implicitHeight + Tokens.padding.small * 2
readonly property int spacing: Tokens.spacing.normal / 2 readonly property int spacing: Tokens.spacing.normal / 2
@@ -43,8 +42,8 @@ CustomClippingRect {
bottomLeftRadius: horizontal ? Tokens.rounding.smallest / 2 : Tokens.rounding.full bottomLeftRadius: horizontal ? Tokens.rounding.smallest / 2 : Tokens.rounding.full
color: Colors.tPalette.m3surfaceContainer color: Colors.tPalette.m3surfaceContainer
implicitHeight: horizontal ? shortSize : size implicitHeight: horizontal ? baseSize : size
implicitWidth: horizontal ? size : shortSize implicitWidth: horizontal ? size : baseSize
radius: Tokens.rounding.full radius: Tokens.rounding.full
topLeftRadius: Tokens.rounding.smallest / 2 topLeftRadius: Tokens.rounding.smallest / 2
topRightRadius: horizontal ? Tokens.rounding.full : Tokens.rounding.smallest / 2 topRightRadius: horizontal ? Tokens.rounding.full : Tokens.rounding.smallest / 2
@@ -13,7 +13,7 @@ MaterialIcon {
animate: true animate: true
color: Audio.muted ? Colors.palette.m3error : Colors.palette.m3onSurface color: Audio.muted ? Colors.palette.m3error : Colors.palette.m3onSurface
fill: 1 fill: 1
font.pointSize: horizontal ? Tokens.font.size.larger : Tokens.font.size.large font.pointSize: Tokens.font.size.larger
text: Audio.muted ? "volume_off" : "volume_up" text: Audio.muted ? "volume_off" : "volume_up"
Behavior on Layout.maximumWidth { Behavior on Layout.maximumWidth {
@@ -13,7 +13,7 @@ MaterialIcon {
animate: true animate: true
color: (Audio.sourceMuted ?? false) ? Colors.palette.m3error : Colors.palette.m3onSurface color: (Audio.sourceMuted ?? false) ? Colors.palette.m3error : Colors.palette.m3onSurface
fill: 1 fill: 1
font.pointSize: horizontal ? Tokens.font.size.larger : Tokens.font.size.large font.pointSize: Tokens.font.size.larger
text: Audio.sourceMuted ? "mic_off" : "mic" text: Audio.sourceMuted ? "mic_off" : "mic"
Behavior on Layout.maximumWidth { Behavior on Layout.maximumWidth {
@@ -125,7 +125,7 @@ Item {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
animate: true animate: true
fill: 1 fill: 1
font.pointSize: root.horizontal ? Tokens.font.size.larger : Tokens.font.size.large font.pointSize: Tokens.font.size.larger
text: { text: {
if (PowerProfiles.profile === PowerProfile.PowerSaver) if (PowerProfiles.profile === PowerProfile.PowerSaver)
return "energy_savings_leaf"; return "energy_savings_leaf";
@@ -1,10 +1,9 @@
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick import QtQuick
import QtQuick.VectorImage
import Quickshell import Quickshell
import Quickshell.Services.SystemTray import Quickshell.Services.SystemTray
import qs.Helpers import qs.Helpers
import qs.Modules import qs.Modules.Bar.Popouts
import qs.Components import qs.Components
import ZShell.Config import ZShell.Config
import qs.Services import qs.Services
@@ -3,28 +3,28 @@ pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell.Services.SystemTray import Quickshell.Services.SystemTray
import qs.Components
import ZShell.Config import ZShell.Config
import qs.Modules import qs.Components
import qs.Modules.Bar.Components.Common
import qs.Modules.Bar.Components.Tray
import qs.Modules.Bar.Popouts
import qs.Services import qs.Services
CustomClippingRect { WidgetBase {
id: root id: root
required property bool horizontal
readonly property alias items: repeater readonly property alias items: repeater
readonly property alias layout: sysGrid readonly property alias layout: sysGrid
required property GridLayout loader required property GridLayout loader
readonly property int padding: Tokens.padding.small readonly property int padding: Tokens.padding.small
required property Wrapper popouts required property Wrapper popouts
readonly property real shortSize: Config.bar.height
readonly property real size: horizontal ? sysGrid.implicitWidth + Tokens.padding.small : sysGrid.implicitHeight + Tokens.padding.small readonly property real size: horizontal ? sysGrid.implicitWidth + Tokens.padding.small : sysGrid.implicitHeight + Tokens.padding.small
bottomLeftRadius: horizontal ? Tokens.rounding.full : Tokens.rounding.smallest / 2 bottomLeftRadius: horizontal ? Tokens.rounding.full : Tokens.rounding.smallest / 2
bottomRightRadius: Tokens.rounding.smallest / 2 bottomRightRadius: Tokens.rounding.smallest / 2
color: Colors.tPalette.m3surfaceContainer color: Colors.tPalette.m3surfaceContainer
implicitHeight: horizontal ? shortSize : size implicitHeight: horizontal ? baseSize : size
implicitWidth: horizontal ? size : shortSize implicitWidth: horizontal ? size : baseSize
radius: Tokens.rounding.full radius: Tokens.rounding.full
topRightRadius: horizontal ? Tokens.rounding.smallest / 2 : Tokens.rounding.full topRightRadius: horizontal ? Tokens.rounding.smallest / 2 : Tokens.rounding.full
@@ -1,21 +1,20 @@
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import qs.Components
import qs.Modules
import qs.Helpers
import ZShell.Config import ZShell.Config
import qs.Components
import qs.Modules.Bar.Components.Common
import qs.Helpers
import qs.Services import qs.Services
CustomRect { WidgetBase {
id: root id: root
property int countUpdates: Updates.availableUpdates property int countUpdates: Updates.availableUpdates
required property bool horizontal
property color textColor: Colors.palette.m3onSurface property color textColor: Colors.palette.m3onSurface
color: Colors.tPalette.m3surfaceContainer color: Colors.tPalette.m3surfaceContainer
implicitHeight: horizontal ? Config.bar.height : content.implicitHeight + Tokens.spacing.small * 2 implicitHeight: horizontal ? baseSize : content.implicitHeight + Tokens.spacing.small
implicitWidth: horizontal ? content.implicitWidth + Tokens.spacing.small * 2 : Config.bar.height implicitWidth: horizontal ? content.implicitWidth + Tokens.spacing.small * 2 : baseSize
radius: Tokens.rounding.full radius: Tokens.rounding.full
GridLayout { GridLayout {
@@ -24,6 +23,7 @@ CustomRect {
anchors.centerIn: parent anchors.centerIn: parent
columnSpacing: Tokens.spacing.small columnSpacing: Tokens.spacing.small
columns: root.horizontal ? -1 : 1 columns: root.horizontal ? -1 : 1
rowSpacing: -Tokens.spacing.extraSmall / 2
MaterialIcon { MaterialIcon {
Layout.alignment: root.horizontal ? Qt.AlignVCenter : Qt.AlignHCenter Layout.alignment: root.horizontal ? Qt.AlignVCenter : Qt.AlignHCenter
@@ -34,7 +34,7 @@ CustomRect {
CustomText { CustomText {
Layout.alignment: root.horizontal ? Qt.AlignVCenter : Qt.AlignHCenter Layout.alignment: root.horizontal ? Qt.AlignVCenter : Qt.AlignHCenter
color: root.textColor color: root.textColor
font.pointSize: root.horizontal ? Tokens.font.size.normal : Tokens.font.size.larger font.pointSize: Tokens.font.size.normal
text: root.countUpdates text: root.countUpdates
} }
} }
@@ -1,8 +1,8 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import qs.Components
import ZShell.Config import ZShell.Config
import qs.Components
import qs.Helpers import qs.Helpers
import qs.Services import qs.Services
@@ -17,13 +17,12 @@ Item {
readonly property int maxSize: { readonly property int maxSize: {
const otherModules = bar.children.filter(c => c.enabled && c.entryId && c.item !== this && c.entryId !== "spacer"); const otherModules = bar.children.filter(c => c.enabled && c.entryId && c.item !== this && c.entryId !== "spacer");
const otherSize = otherModules.reduce((acc, curr) => acc + (horizontal ? (curr.item?.nonAnimWidth ?? curr.width ?? 0) : (curr.item.nonAnimHeight ?? curr.height ?? 0)), 0); const otherSize = otherModules.reduce((acc, curr) => acc + (horizontal ? (curr.item?.nonAnimWidth ?? curr.width ?? 0) : (curr.item.nonAnimHeight ?? curr.height ?? 0)), 0);
return horizontal ? bar.width - otherSize - bar.spacing * (bar.children.length - 1) - bar.vPadding * 2 : bar.height - otherSize - bar.vPadding * (bar.children.length - 1) - bar.vPadding * 4; return horizontal ? (bar.width - otherSize - bar.columnSpacing * (bar.children.length - 1) - bar.vPadding * 2) : (bar.height - otherSize - bar.rowSpacing * (bar.children.length - 1) - bar.vPadding * 4);
} }
anchors.centerIn: parent
clip: true clip: true
implicitHeight: horizontal ? current.implicitHeight : current.implicitWidth + current.anchors.topMargin implicitHeight: horizontal ? current.implicitHeight : current.implicitWidth + current.anchors.topMargin
implicitWidth: horizontal ? Math.min(current.implicitWidth, root.maxSize) : current.implicitHeight + current.anchors.topMargin implicitWidth: horizontal ? current.implicitWidth + current.anchors.leftMargin : current.implicitHeight + current.anchors.topMargin
Behavior on implicitHeight { Behavior on implicitHeight {
enabled: !root.horizontal enabled: !root.horizontal
@@ -36,6 +35,7 @@ Item {
enabled: root.horizontal enabled: root.horizontal
Anim { Anim {
type: Anim.DefaultEffects
} }
} }
@@ -2,10 +2,10 @@ pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Hyprland import Quickshell.Hyprland
import ZShell.Config import ZShell.Config
import qs.Modules.Bar.Components.Workspaces
import qs.Components import qs.Components
import qs.Services import qs.Services
@@ -17,7 +17,7 @@ Item {
required property bool horizontal required property bool horizontal
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.screen) readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.screen)
required property ShellScreen screen required property ShellScreen screen
readonly property real shortSize: Math.max(Config.bar.height, 24) readonly property real shortSize: Math.max(Config.bar.height, Tokens.bar.innerSize)
readonly property real size: (workspaceButtonWidth * workspacesShown) + activeWorkspaceMargin * 2 readonly property real size: (workspaceButtonWidth * workspacesShown) + activeWorkspaceMargin * 2
property int workspaceButtonWidth: (horizontal ? bgRect.implicitHeight : bgRect.implicitWidth) - root.activeWorkspaceMargin * 2 property int workspaceButtonWidth: (horizontal ? bgRect.implicitHeight : bgRect.implicitWidth) - root.activeWorkspaceMargin * 2
property int workspaceIndexInGroup: (effectiveActiveWorkspaceId - 1) % root.workspacesShown property int workspaceIndexInGroup: (effectiveActiveWorkspaceId - 1) % root.workspacesShown
@@ -90,7 +90,7 @@ Item {
color: Colors.palette.m3onSecondaryContainer color: Colors.palette.m3onSecondaryContainer
elide: Text.ElideRight elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: button.modelData.name text: button.modelData?.name ?? ""
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
} }
@@ -5,10 +5,6 @@ import Quickshell.Services.SystemTray
import QtQuick import QtQuick
import ZShell.Config import ZShell.Config
import qs.Components import qs.Components
import qs.Modules.WSOverview
import qs.Modules.Network
import qs.Modules.SysTray.Popouts
import qs.Modules.Updates
Item { Item {
id: root id: root
-61
View File
@@ -1,61 +0,0 @@
import Quickshell
import QtQuick
import QtQuick.Layouts
import ZShell.Config
import qs.Modules
import qs.Helpers
import qs.Components
import qs.Services
CustomRect {
id: root
required property bool horizontal
required property GridLayout loader
required property Wrapper popouts
readonly property real shortSize: Config.bar.height
readonly property real size: timeText.contentWidth + (horizontal ? Tokens.padding.normal : Tokens.padding.larger) * 2
required property PersistentProperties visibilities
color: visibilities.dashboard ? Colors.palette.m3primary : Colors.tPalette.m3surfaceContainer
implicitHeight: horizontal ? shortSize : size
implicitWidth: horizontal ? size : shortSize
radius: Tokens.rounding.full
CustomText {
id: timeText
anchors.centerIn: parent
// anchors.horizontalCenter: root.horizontal ? undefined : parent.horizontalCenter
// anchors.verticalCenter: root.horizontal ? parent.verticalCenter : undefined
color: root.visibilities.dashboard ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
font: Config.appearance.font.family.mono // qmllint disable incompatible-type
height: root.horizontal ? implicitHeight : implicitWidth
text: Time.dateStr
width: root.horizontal ? implicitWidth : implicitHeight
Behavior on color {
CAnim {
}
}
transform: [
Translate {
x: !root.horizontal ? -timeText.implicitWidth + timeText.implicitHeight : 0
},
Rotation {
angle: root.horizontal ? 0 : 270
origin.x: timeText.implicitHeight / 2
origin.y: timeText.implicitHeight / 2
}
]
}
StateLayer {
acceptedButtons: Qt.LeftButton
color: root.visibilities.dashboard ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
onClicked: {
root.visibilities.dashboard = !root.visibilities.dashboard;
}
}
}
+12 -9
View File
@@ -14,16 +14,18 @@ Item {
reloadableId: "dashboardState" reloadableId: "dashboardState"
} }
required property bool horizontal
readonly property real nonAnimHeight: state === "visible" ? (content.item?.nonAnimHeight ?? 0) : 0 readonly property real nonAnimHeight: state === "visible" ? (content.item?.nonAnimHeight ?? 0) : 0
required property real offsetScale required property real offsetScale
required property string position
readonly property bool shouldBeActive: root.visibilities.dashboard && Config.dashboard.enabled readonly property bool shouldBeActive: root.visibilities.dashboard && Config.dashboard.enabled
required property PersistentProperties visibilities required property PersistentProperties visibilities
anchors.left: horizontal ? undefined : parent.left anchors.bottom: position === "bottom" ? parent.bottom : undefined
anchors.leftMargin: horizontal ? 0 : (-implicitWidth - 5) * offsetScale anchors.bottomMargin: position === "bottom" ? (-implicitHeight - 5) * offsetScale : 0
anchors.top: horizontal ? parent.top : undefined anchors.left: position === "left" ? parent.left : undefined
anchors.topMargin: horizontal ? (-implicitHeight - 5) * offsetScale : 0 anchors.leftMargin: position === "left" ? (-implicitWidth - 5) * offsetScale : 0
anchors.top: position === "top" ? parent.top : undefined
anchors.topMargin: position === "top" ? (-implicitHeight - 5) * offsetScale : 0
implicitHeight: content.implicitHeight implicitHeight: content.implicitHeight
implicitWidth: content.implicitWidth || 854 implicitWidth: content.implicitWidth || 854
opacity: 1 - offsetScale opacity: 1 - offsetScale
@@ -33,10 +35,11 @@ Item {
id: content id: content
active: root.shouldBeActive || root.visible active: root.shouldBeActive || root.visible
anchors.bottom: root.horizontal ? parent.bottom : undefined anchors.bottom: root.position === "top" ? parent.bottom : undefined
anchors.horizontalCenter: root.horizontal ? parent.horizontalCenter : undefined anchors.horizontalCenter: root.position !== "left" ? parent.horizontalCenter : undefined
anchors.right: root.horizontal ? undefined : parent.right anchors.right: root.position !== "left" ? undefined : parent.right
anchors.verticalCenter: root.horizontal ? undefined : parent.verticalCenter anchors.top: root.position === "bottom" ? parent.top : undefined
anchors.verticalCenter: root.position === "left" ? undefined : parent.verticalCenter
sourceComponent: Content { sourceComponent: Content {
dashState: root.dashState dashState: root.dashState
@@ -82,7 +82,6 @@ Searcher {
list.visibilities.launcher = false; list.visibilities.launcher = false;
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--scheme", variant]); Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--scheme", variant]);
Config.colors.schemeType = variant; Config.colors.schemeType = variant;
Config.save();
} }
} }
} }
+12 -4
View File
@@ -8,14 +8,18 @@ import ZShell.Config
Item { Item {
id: root id: root
required property bool horizontal
readonly property real nonAnimHeight: content.item?.nonAnimHeight ?? 0 readonly property real nonAnimHeight: content.item?.nonAnimHeight ?? 0
property real offsetScale: shouldBeActive ? 0 : 1 property real offsetScale: shouldBeActive ? 0 : 1
required property string position
readonly property bool shouldBeActive: root.visibilities.resources readonly property bool shouldBeActive: root.visibilities.resources
required property PersistentProperties visibilities required property PersistentProperties visibilities
anchors.leftMargin: horizontal ? 0 : (-implicitWidth - 5) * offsetScale anchors.bottom: position === "bottom" ? parent.bottom : undefined
anchors.topMargin: horizontal ? (-implicitHeight - 5) * offsetScale : 0 anchors.bottomMargin: position === "bottom" ? (-implicitHeight - 5) * offsetScale : 0
anchors.left: parent.left
anchors.leftMargin: position === "left" ? (-implicitWidth - 5) * offsetScale : 0
anchors.top: position !== "bottom" ? parent.top : undefined
anchors.topMargin: position === "top" ? (-implicitHeight - 5) * offsetScale : 0
clip: true clip: true
implicitHeight: content.implicitHeight implicitHeight: content.implicitHeight
implicitWidth: content.implicitWidth || 854 // Hard coded fallback for first open implicitWidth: content.implicitWidth || 854 // Hard coded fallback for first open
@@ -33,7 +37,11 @@ Item {
id: content id: content
active: root.shouldBeActive || root.visible active: root.shouldBeActive || root.visible
anchors.centerIn: parent anchors.bottom: root.position === "top" ? parent.bottom : undefined
anchors.horizontalCenter: root.position !== "left" ? parent.horizontalCenter : undefined
anchors.right: root.position !== "left" ? undefined : parent.right
anchors.top: root.position === "bottom" ? parent.top : undefined
anchors.verticalCenter: root.position === "left" ? undefined : parent.verticalCenter
sourceComponent: Content { sourceComponent: Content {
wrapper: root wrapper: root
+3 -6
View File
@@ -37,8 +37,8 @@ PageBase {
active: Config.bar.position === "top" ? menuItems[0] : Config.bar.position === "left" ? menuItems[1] : menuItems[2] active: Config.bar.position === "top" ? menuItems[0] : Config.bar.position === "left" ? menuItems[1] : menuItems[2]
last: true last: true
settingAnchor: "bar-position" settingAnchor: "bar-position"
subtext: qsTr("Automatic or manual effect values") subtext: qsTr("Change which edge the bar appears on")
text: qsTr("Effects mode") text: qsTr("Position")
menuItems: [ menuItems: [
MenuItem { MenuItem {
@@ -58,10 +58,7 @@ PageBase {
} }
] ]
onSelected: item => { onSelected: item => Config.bar.position = item.value
Config.bar.position = item.value;
Config.save();
}
} }
// Components // Components
+1 -4
View File
@@ -70,10 +70,7 @@ PageBase {
} }
] ]
onSelected: item => { onSelected: item => Config.screenshot.mode = item.value
Config.screenshot.mode = item.value;
Config.save();
}
} }
SectionHeader { SectionHeader {
-2
View File
@@ -104,7 +104,6 @@ PageBase {
onApplySettings: (start, end) => { onApplySettings: (start, end) => {
Config.general.color.scheduleDarkStart = start; Config.general.color.scheduleDarkStart = start;
Config.general.color.scheduleDarkEnd = end; Config.general.color.scheduleDarkEnd = end;
Config.save();
ModeScheduler.checkStartup(); ModeScheduler.checkStartup();
PopupManager.requestClose(); PopupManager.requestClose();
} }
@@ -146,7 +145,6 @@ PageBase {
onApplySettings: (start, end) => { onApplySettings: (start, end) => {
Config.general.color.scheduleHyprsunsetStart = start; Config.general.color.scheduleHyprsunsetStart = start;
Config.general.color.scheduleHyprsunsetEnd = end; Config.general.color.scheduleHyprsunsetEnd = end;
Config.save();
Hyprsunset.checkStartup(); Hyprsunset.checkStartup();
PopupManager.requestClose(); PopupManager.requestClose();
} }
-159
View File
@@ -1,159 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell.Services.SystemTray
import qs.Components
import ZShell.Config
import qs.Modules.SysTray.Widgets
import qs.Modules
import qs.Services
GridLayout {
id: root
required property bool horizontal
readonly property alias items: repeater
required property GridLayout loader
required property Wrapper popouts
readonly property real shortSize: Config.bar.height + Tokens.padding.smallest * 2
readonly property real size: horizontal ? sysTray.implicitWidth + sysTrayMod.implicitWidth + Tokens.padding.small : sysTray.implicitHeight + sysTrayMod.implicitHeight + Tokens.padding.small
function closestRowChild(row, x) {
let child = row.childAt(x, row.height / 2);
if (child)
return child;
let closest = null;
let closestDistance = Infinity;
for (let i = 0; i < row.children.length; ++i) {
let c = row.children[i];
if (!c.visible || c.width <= 0)
continue;
let centerX = c.x + c.width / 2;
let dist = Math.abs(x - centerX);
if (dist < closestDistance) {
closestDistance = dist;
closest = c;
}
}
return closest;
}
function getHoveredSubItem(localX, localY) {
let modPos = mapToItem(sysTrayMod, localX, localY);
if (sysTrayMod.contains(Qt.point(modPos.x, modPos.y))) {
let modRowPos = sysTrayMod.mapToItem(sysIcons, modPos.x, modPos.y);
let child = closestRowChild(sysIcons, modRowPos.x);
if (child) {
if (child.objectName === "audio" && Config.bar.popouts.audio)
return {
id: "audio",
item: child
};
if (child.objectName === "microphone" && Config.bar.popouts.audio)
return {
id: "audio",
item: child
};
if (child.objectName === "upower" && Config.bar.popouts.upower)
return {
id: "upower",
item: child
};
}
}
let trayPos = mapToItem(sysTray, localX, localY);
if (Config.bar.tray.showOnHover && sysTray.contains(Qt.point(trayPos.x, trayPos.y))) {
let trayRowPos = sysTray.mapToItem(sysRow, trayPos.x, trayPos.y);
let child = sysRow.childAt(trayRowPos.x, trayRowPos.y);
if (child && child.hasOwnProperty("ind")) {
return {
id: `traymenu${child.ind}`,
item: child
};
}
}
return null;
}
columnSpacing: Tokens.padding.small
columns: horizontal ? -1 : 1
height: horizontal ? shortSize : size
rowSpacing: Tokens.padding.small
width: horizontal ? size : shortSize
CustomClippingRect {
id: sysTray
Layout.fillHeight: true
bottomRightRadius: Tokens.rounding.smallest / 2
color: Colors.tPalette.m3surfaceContainer
implicitWidth: sysRow.width + Tokens.padding.small * 2
radius: Tokens.rounding.full
topRightRadius: Tokens.rounding.smallest / 2
GridLayout {
id: sysRow
anchors.bottom: root.horizontal ? undefined : parent.bottom
anchors.centerIn: parent
anchors.horizontalCenter: root.horizontal ? parent.horizontalCenter : undefined
anchors.left: root.horizontal ? undefined : parent.left
anchors.right: root.horizontal ? undefined : parent.right
anchors.verticalCenter: root.horizontal ? parent.verticalCenter : undefined
columnSpacing: Tokens.spacing.small / 2
columns: root.horizontal ? -1 : 1
rowSpacing: Tokens.spacing.small / 2
Repeater {
id: repeater
model: SystemTray.items
TrayItem {
id: trayItem
required property int index
required property SystemTrayItem modelData
implicitHeight: 34
implicitWidth: 34
ind: index
item: modelData
loader: root.loader
popouts: root.popouts
}
}
}
}
CustomClippingRect {
id: sysTrayMod
Layout.fillHeight: root.horizontal
Layout.fillWidth: !root.horizontal
bottomLeftRadius: Tokens.rounding.smallest / 2
color: Colors.tPalette.m3surfaceContainer
implicitHeight: root.horizontal ? sysIcons.implicitHeight : sysIcons.implicitHeight + Tokens.padding.smaller + Tokens.padding.normal
implicitWidth: root.horizontal ? sysIcons.implicitWidth + Tokens.padding.smaller + Tokens.padding.normal : sysIcons.implicitWidth
radius: Tokens.rounding.full
topLeftRadius: Tokens.rounding.smallest / 2
StatusIcons {
id: sysIcons
anchors.fill: parent
anchors.leftMargin: Tokens.padding.smaller
anchors.rightMargin: Tokens.padding.normal
horizontal: root.horizontal
}
}
}
-86
View File
@@ -1,86 +0,0 @@
import Quickshell
import Quickshell.Hyprland
import Quickshell.Wayland
import QtQuick
import QtQuick.Layouts
import ZShell.Config
import qs.Components
import qs.Modules
import qs.Helpers
import qs.Services
Item {
id: root
required property ShellScreen screen
required property Item wrapper
implicitHeight: layout.implicitHeight + 16
implicitWidth: layout.implicitWidth + 16
GridLayout {
id: layout
anchors.centerIn: parent
columnSpacing: 8
rowSpacing: 8
Repeater {
model: Hypr.workspaces
CustomRect {
id: workspacePreview
required property HyprlandWorkspace modelData
Layout.preferredHeight: 180 + 10
Layout.preferredWidth: 320 + 10
border.color: "white"
border.width: 1
radius: Tokens.rounding.smallest
Repeater {
model: workspacePreview.modelData.toplevels
Item {
id: preview
property rect appPosition: {
let {
at: [cx, cy],
size: [cw, ch]
} = modelData.lastIpcObject;
cx -= modelData.monitor.x;
cy -= modelData.monitor.y;
return Qt.rect((cx / 8), (cy / 8), (cw / 8), (ch / 8));
}
required property HyprlandToplevel modelData
anchors.fill: parent
anchors.margins: 5
CustomRect {
border.color: Colors.tPalette.m3outline
border.width: 1
implicitHeight: preview.appPosition.height
implicitWidth: preview.appPosition.width
radius: Tokens.rounding.smallest / 2
x: preview.appPosition.x
y: preview.appPosition.y - 3.4
ScreencopyView {
id: previewCopy
anchors.fill: parent
captureSource: preview.modelData.wayland
live: true
}
}
}
}
}
}
}
}
+2 -1
View File
@@ -3,6 +3,7 @@
#include "configlist.hpp" #include "configlist.hpp"
#include "configobject.hpp" #include "configobject.hpp"
#include <qcontainerfwd.h> #include <qcontainerfwd.h>
#include <qhashfunctions.h>
#include <qqmlregistration.h> #include <qqmlregistration.h>
namespace ZShell::config { namespace ZShell::config {
@@ -62,7 +63,7 @@ class Bar : public ConfigObject {
CFG_PROPERTY(int, revealDelay, 100) CFG_PROPERTY(int, revealDelay, 100)
CFG_PROPERTY(int, rounding, 14) CFG_PROPERTY(int, rounding, 14)
CFG_PROPERTY(int, smoothing, 32) CFG_PROPERTY(int, smoothing, 32)
CFG_PROPERTY(QString, position, "top") CFG_PROPERTY(QString, position, QStringLiteral("top"))
CONFIG_SUBOBJECT(Tray, tray) CONFIG_SUBOBJECT(Tray, tray)
CONFIG_LIST( CONFIG_LIST(
EntryList, EntryList,
+40 -19
View File
@@ -61,6 +61,8 @@ Config::Config(QObject* parent)
m_reloadTimer.setInterval(50); m_reloadTimer.setInterval(50);
connect(&m_reloadTimer, &QTimer::timeout, this, &Config::reloadAsync); connect(&m_reloadTimer, &QTimer::timeout, this, &Config::reloadAsync);
connectAutoSave(this);
connect( connect(
&m_watcher, &m_watcher,
&QFileSystemWatcher::directoryChanged, &QFileSystemWatcher::directoryChanged,
@@ -93,15 +95,31 @@ QString Config::filePath() const {
void Config::loadSync() { void Config::loadSync() {
m_loading = true; m_loading = true;
QFile f(filePath()); QFile f(filePath());
QJsonObject before;
bool existed = false;
if (f.open(QIODevice::ReadOnly)) { if (f.open(QIODevice::ReadOnly)) {
const auto doc = QJsonDocument::fromJson(f.readAll()); const auto doc = QJsonDocument::fromJson(f.readAll());
if (doc.isObject()) loadFromJson(QJsonValue(doc.object())); if (doc.isObject()) {
before = doc.object();
existed = true;
} else {
qInfo() << "Config: existing config at" << filePath()
<< "is empty or not a valid JSON object - using defaults";
}
} else { } else {
qInfo() << "Config: no existing config at" << filePath() qInfo() << "Config: no existing config at" << filePath()
<< "- using defaults"; << "- using defaults";
} }
loadFromJson(QJsonValue(before));
m_loading = false; m_loading = false;
const auto after = toJson().toObject();
if (!existed || after != before) saveNow();
} }
void Config::updateWatch() { void Config::updateWatch() {
@@ -146,27 +164,21 @@ void Config::loadAsync() {
if (f.open(QIODevice::ReadOnly)) { if (f.open(QIODevice::ReadOnly)) {
const auto doc = QJsonDocument::fromJson(f.readAll()); const auto doc = QJsonDocument::fromJson(f.readAll());
const bool valid = doc.isObject();
const QJsonObject before = valid ? doc.object() : QJsonObject();
if (doc.isObject()) { QMetaObject::invokeMethod(
QMetaObject::invokeMethod( this,
this, [this, valid, before]() {
[this, doc]() { loadFromJson(QJsonValue(before));
loadFromJson(QJsonValue(doc.object())); m_loading = false;
m_loading = false;
if (m_reloadPending) m_reloadTimer.start(); const auto after = toJson().toObject();
}, if (!valid || after != before) saveNow();
Qt::QueuedConnection);
} else {
QMetaObject::invokeMethod(
this,
[this]() {
m_loading = false;
if (m_reloadPending) m_reloadTimer.start(); if (m_reloadPending) m_reloadTimer.start();
}, },
Qt::QueuedConnection); Qt::QueuedConnection);
}
} else { } else {
qInfo() << "Config: failed to reload from" << filePath() qInfo() << "Config: failed to reload from" << filePath()
<< "- using in-memory values"; << "- using in-memory values";
@@ -232,6 +244,15 @@ void Config::saveNow() {
flushAsync(); flushAsync();
} }
void Config::connectAutoSave(ConfigNode* node) {
connect(node, &ConfigNode::propertiesChanged, this, [this] {
scheduleSave();
});
for (auto* child : node->childNodes())
connectAutoSave(child);
}
bool Config::writeAtomically(const QByteArray& data) { bool Config::writeAtomically(const QByteArray& data) {
QSaveFile f(filePath()); QSaveFile f(filePath());
+1
View File
@@ -90,6 +90,7 @@ class Config : public ConfigObject {
void updateWatch(); void updateWatch();
void loadSync(); void loadSync();
void loadAsync(); void loadAsync();
void connectAutoSave(ConfigNode* node);
QTimer m_saveTimer; QTimer m_saveTimer;
QTimer m_reloadTimer; QTimer m_reloadTimer;
+14 -5
View File
@@ -79,13 +79,14 @@ void ConfigList::loadFromJson(const QJsonValue& json) {
if (!json.isArray()) { if (!json.isArray()) {
qCWarning( qCWarning(
lcConfig, lcConfig,
"Option '%s' must be a list, ignoring", "Option '%s' must be a list, resetting to default",
qUtf8Printable(propertyPath())); qUtf8Printable(propertyPath()));
m_rejectedJson = json;
resetToDefaults();
materializeDefaults();
return; return;
} }
m_rejectedJson = QJsonValue::Undefined;
populate(json.toArray()); populate(json.toArray());
m_loaded = true; m_loaded = true;
} }
@@ -93,12 +94,11 @@ void ConfigList::loadFromJson(const QJsonValue& json) {
QJsonValue ConfigList::toJson() const { QJsonValue ConfigList::toJson() const {
if (m_loaded) return elementsToJson(); if (m_loaded) return elementsToJson();
return m_rejectedJson; return QJsonValue::Undefined;
} }
void ConfigList::clearLoadedKeys() { void ConfigList::clearLoadedKeys() {
m_loaded = false; m_loaded = false;
m_rejectedJson = QJsonValue::Undefined;
} }
QStringList ConfigList::unknownKeys() const { QStringList ConfigList::unknownKeys() const {
@@ -113,6 +113,15 @@ QStringList ConfigList::unknownKeys() const {
return keys; return keys;
} }
void ConfigList::materializeDefaults() {
for (auto* const item : m_items)
item->materializeDefaults();
if (m_global) return;
m_loaded = true;
}
void ConfigList::resyncFromGlobal() { void ConfigList::resyncFromGlobal() {
syncValuesFromGlobal(); syncValuesFromGlobal();
} }
+3 -5
View File
@@ -33,6 +33,7 @@ class ConfigList : public ConfigNode {
[[nodiscard]] QJsonValue toJson() const override; [[nodiscard]] QJsonValue toJson() const override;
void clearLoadedKeys() override; void clearLoadedKeys() override;
[[nodiscard]] QStringList unknownKeys() const override; [[nodiscard]] QStringList unknownKeys() const override;
void materializeDefaults() override;
void resyncFromGlobal() override; void resyncFromGlobal() override;
signals: signals:
@@ -71,7 +72,6 @@ class ConfigList : public ConfigNode {
QJsonArray m_defaults; QJsonArray m_defaults;
QList<ConfigObject*> m_items; QList<ConfigObject*> m_items;
bool m_loaded = false; bool m_loaded = false;
QJsonValue m_rejectedJson = QJsonValue::Undefined;
}; };
} // namespace ZShell::config } // namespace ZShell::config
@@ -103,7 +103,6 @@ class ConfigList : public ConfigNode {
} \ } \
}; };
#define CONFIG_LIST(Type, name, ...) \ #define CONFIG_LIST(Type, name, ...) \
Q_PROPERTY(ZShell::config::Type* name READ name CONSTANT) \ Q_PROPERTY(ZShell::config::Type* name READ name CONSTANT) \
\ \
@@ -115,6 +114,8 @@ class ConfigList : public ConfigNode {
private: \ private: \
Type* m_##name = new Type(this __VA_OPT__(, __VA_ARGS__)); Type* m_##name = new Type(this __VA_OPT__(, __VA_ARGS__));
#define LIST_ENTRY(id, enabled) \
vmap({{"id", QString::fromUtf8(#id)}, {"enabled", enabled}})
namespace ZShell::config { namespace ZShell::config {
@@ -136,6 +137,3 @@ class ListEntry : public ConfigObject {
CONFIG_LIST_TYPE(ListEntry, EntryList) CONFIG_LIST_TYPE(ListEntry, EntryList)
} // namespace ZShell::config } // namespace ZShell::config
#define LIST_ENTRY(id, enabled) \
vmap({{"id", QString::fromUtf8(#id)}, {"enabled", enabled}})
+2
View File
@@ -26,6 +26,8 @@ class ConfigNode : public QObject {
[[nodiscard]] virtual QStringList unknownKeys() const = 0; [[nodiscard]] virtual QStringList unknownKeys() const = 0;
[[nodiscard]] virtual QList<ConfigNode*> childNodes() const; [[nodiscard]] virtual QList<ConfigNode*> childNodes() const;
virtual void materializeDefaults() = 0;
void syncFromGlobal(ConfigNode* global); void syncFromGlobal(ConfigNode* global);
virtual void resyncFromGlobal() = 0; virtual void resyncFromGlobal() = 0;
+78 -4
View File
@@ -7,6 +7,51 @@
namespace ZShell::config { namespace ZShell::config {
namespace {
bool isStringArray(const QJsonArray& arr) {
for (const auto& v : arr) {
if (!v.isString()) return false;
}
return true;
}
bool jsonValueMatchesType(const QJsonValue& val, QMetaType type) {
switch (val.type()) {
case QJsonValue::Bool:
return type.id() == QMetaType::Bool;
case QJsonValue::Double:
switch (type.id()) {
case QMetaType::Int:
case QMetaType::UInt:
case QMetaType::LongLong:
case QMetaType::ULongLong:
case QMetaType::Double:
case QMetaType::Float:
return true;
default:
return false;
}
case QJsonValue::String:
return type.id() == QMetaType::QString;
case QJsonValue::Array:
if (type.id() == QMetaType::QStringList)
return isStringArray(val.toArray());
return type.id() == QMetaType::QVariantList;
case QJsonValue::Object:
case QJsonValue::Null:
case QJsonValue::Undefined:
default:
return false;
}
}
} // namespace
ConfigObject::ConfigObject(QObject* parent) : ConfigNode(parent) {} ConfigObject::ConfigObject(QObject* parent) : ConfigNode(parent) {}
void ConfigObject::loadFromJson(const QJsonValue& json) { void ConfigObject::loadFromJson(const QJsonValue& json) {
@@ -14,6 +59,7 @@ void ConfigObject::loadFromJson(const QJsonValue& json) {
const auto* meta = metaObject(); const auto* meta = metaObject();
QSet<QString> known; QSet<QString> known;
QSet<QString> invalid;
for (int i = basePropertyOffset(); i < meta->propertyCount(); ++i) { for (int i = basePropertyOffset(); i < meta->propertyCount(); ++i) {
auto prop = meta->property(i); auto prop = meta->property(i);
@@ -34,6 +80,15 @@ void ConfigObject::loadFromJson(const QJsonValue& json) {
if (!prop.isWritable()) continue; if (!prop.isWritable()) continue;
if (!jsonValueMatchesType(jsonVal, prop.metaType())) {
qWarning() << "Config: type mismatch for" << key << "in"
<< meta->className() << "- expected"
<< prop.metaType().name() << "got value" << jsonVal
<< "- resetting to default";
invalid.insert(key);
continue;
}
if (prop.metaType().id() == QMetaType::QStringList) { if (prop.metaType().id() == QMetaType::QStringList) {
QStringList list; QStringList list;
const auto jsonArr = jsonVal.toArray(); const auto jsonArr = jsonVal.toArray();
@@ -56,10 +111,12 @@ void ConfigObject::loadFromJson(const QJsonValue& json) {
m_extras = {}; m_extras = {};
for (auto it = obj.begin(); it != obj.end(); ++it) { for (auto it = obj.begin(); it != obj.end(); ++it) {
if (!known.contains(it.key())) { if (!known.contains(it.key()) || invalid.contains(it.key())) {
m_extras.insert(it.key(), it.value()); m_extras.insert(it.key(), it.value());
} }
} }
materializeDefaults();
} }
QJsonValue ConfigObject::toJson() const { QJsonValue ConfigObject::toJson() const {
@@ -104,9 +161,6 @@ QJsonValue ConfigObject::toJson() const {
obj.insert(key, QJsonValue::fromVariant(value)); obj.insert(key, QJsonValue::fromVariant(value));
} }
for (auto it = m_extras.begin(); it != m_extras.end(); ++it)
obj.insert(it.key(), it.value());
if (obj.isEmpty()) return QJsonValue::Undefined; if (obj.isEmpty()) return QJsonValue::Undefined;
return obj; return obj;
@@ -159,6 +213,26 @@ QList<ConfigNode*> ConfigObject::childNodes() const {
return nodes; return nodes;
} }
void ConfigObject::materializeDefaults() {
const auto* meta = metaObject();
for (int i = basePropertyOffset(); i < meta->propertyCount(); ++i) {
const auto prop = meta->property(i);
const auto key = QString::fromUtf8(prop.name());
if (auto* const node = prop.read(this).value<ConfigNode*>()) {
node->materializeDefaults();
continue;
}
if (!prop.isWritable()) continue;
if (m_global) continue;
m_loadedKeys.insert(key);
}
}
void ConfigObject::syncValuesFromGlobal() { void ConfigObject::syncValuesFromGlobal() {
const auto* meta = metaObject(); const auto* meta = metaObject();
+1
View File
@@ -62,6 +62,7 @@ class ConfigObject : public ConfigNode {
void clearLoadedKeys() override; void clearLoadedKeys() override;
[[nodiscard]] QStringList unknownKeys() const override; [[nodiscard]] QStringList unknownKeys() const override;
[[nodiscard]] QList<ConfigNode*> childNodes() const override; [[nodiscard]] QList<ConfigNode*> childNodes() const override;
void materializeDefaults() override;
void resyncFromGlobal() override; void resyncFromGlobal() override;
[[nodiscard]] virtual QStringList identityKeys() const; [[nodiscard]] virtual QStringList identityKeys() const;
+15 -1
View File
@@ -3,7 +3,9 @@
#include "configobject.hpp" #include "configobject.hpp"
#include <QVariantList> #include <QVariantList>
#include <qobject.h>
#include <qqmlengine.h> #include <qqmlengine.h>
#include <qqmlintegration.h>
#include <qqmlregistration.h> #include <qqmlregistration.h>
namespace ZShell::config { namespace ZShell::config {
@@ -195,6 +197,16 @@ class AnimTokens : public ConfigObject {
, m_durations(new AnimDurations(this)) {} , m_durations(new AnimDurations(this)) {}
}; };
class BarTokens : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
CFG_PROPERTY(int, innerSize, 28)
public:
explicit BarTokens(QObject* parent = nullptr) : ConfigObject(parent) {}
};
class Tokens : public ConfigObject { class Tokens : public ConfigObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
@@ -205,6 +217,7 @@ class Tokens : public ConfigObject {
CONFIG_SUBOBJECT(AppearanceSpacing, spacing) CONFIG_SUBOBJECT(AppearanceSpacing, spacing)
CONFIG_SUBOBJECT(FontTokens, font) CONFIG_SUBOBJECT(FontTokens, font)
CONFIG_SUBOBJECT(AnimTokens, anim) CONFIG_SUBOBJECT(AnimTokens, anim)
CONFIG_SUBOBJECT(BarTokens, bar)
public: public:
explicit Tokens(QObject* parent = nullptr) explicit Tokens(QObject* parent = nullptr)
@@ -213,7 +226,8 @@ class Tokens : public ConfigObject {
, m_padding(new AppearancePadding(this)) , m_padding(new AppearancePadding(this))
, m_spacing(new AppearanceSpacing(this)) , m_spacing(new AppearanceSpacing(this))
, m_font(new FontTokens(this)) , m_font(new FontTokens(this))
, m_anim(new AnimTokens(this)) {} , m_anim(new AnimTokens(this))
, m_bar(new BarTokens(this)) {}
static Tokens* create(QQmlEngine*, QJSEngine*) { return new Tokens(); } static Tokens* create(QQmlEngine*, QJSEngine*) { return new Tokens(); }
}; };
-1
View File
@@ -113,7 +113,6 @@ Singleton {
function setMode(mode: string): void { function setMode(mode: string): void {
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--mode", mode]); Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--mode", mode]);
Config.general.color.mode = mode; Config.general.color.mode = mode;
Config.save();
} }
function swapRG(c: color): color { function swapRG(c: color): color {
+17 -7
View File
@@ -30,14 +30,24 @@ RUN pacman -S --noconfirm \
glib2 glib2
# AUR: libcava (provides development headers for cava) # AUR: libcava (provides development headers for cava)
RUN pacman -S --needed --noconfirm sudo && \ RUN pacman -Syu --noconfirm && \
pacman -S --needed --noconfirm \
sudo \
meson \
autoconf-archive \
python-tqdm && \
useradd -m builder && \ useradd -m builder && \
echo "builder ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers && \ echo "builder ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
su builder -c " \
git clone https://aur.archlinux.org/libcava.git /tmp/libcava && \ RUN for i in 1 2 3 4 5; do \
cd /tmp/libcava && \ rm -rf /tmp/libcava && \
makepkg -si --noconfirm \ git clone --depth 1 https://aur.archlinux.org/libcava.git /tmp/libcava && \
" && \ break || sleep 5; \
done && \
test -d /tmp/libcava/.git && \
chown -R builder /tmp/libcava
RUN su builder -c "cd /tmp/libcava && makepkg -si --noconfirm" && \
rm -rf /tmp/libcava && \ rm -rf /tmp/libcava && \
userdel -r builder && \ userdel -r builder && \
sed -i '/^builder/d' /etc/sudoers sed -i '/^builder/d' /etc/sudoers
+3
View File
@@ -13,6 +13,9 @@ dependencies = [
"materialyoucolor" "materialyoucolor"
] ]
[project.optional-dependencies]
dev = ["pytest"]
[project.scripts] [project.scripts]
zshell-cli = "zshell:main" zshell-cli = "zshell:main"