From bd576e17dc7ee7f9b7992412553374c4a09c7d2c Mon Sep 17 00:00:00 2001 From: zach Date: Wed, 27 May 2026 09:28:35 +0200 Subject: [PATCH 01/18] Revert to OpenGL --- shell.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell.qml b/shell.qml index 599b41b..5e756cb 100644 --- a/shell.qml +++ b/shell.qml @@ -1,6 +1,6 @@ //@ pragma UseQApplication //@ pragma Env QSG_RENDER_LOOP=threaded -//@ pragma Env QSG_RHI_BACKEND=vulkan +// @ pragma Env QSG_RHI_BACKEND=vulkan //@ pragma Env QSG_NO_VSYNC=1 //@ pragma Env QS_NO_RELOAD_POPUP=1 //@ pragma Env QT_SCALE_FACTOR_ROUNDING_POLICY=Round From 3963a48a9d9186c205f4516343a39c53d04d0135 Mon Sep 17 00:00:00 2001 From: zach Date: Wed, 27 May 2026 11:43:42 +0200 Subject: [PATCH 02/18] load higher resolution tray icons for high-dpi screens --- Drawers/Windows.qml | 42 +++++++++++++++++++++++------------- Modules/Drawing/Wrapper.qml | 7 +++--- Modules/SysTray/TrayItem.qml | 6 +++++- 3 files changed, 35 insertions(+), 20 deletions(-) diff --git a/Drawers/Windows.qml b/Drawers/Windows.qml index a1c9df4..eac6f5f 100644 --- a/Drawers/Windows.qml +++ b/Drawers/Windows.qml @@ -305,22 +305,34 @@ Variants { } } - Drawing { - id: drawing + Loader { + id: drawingLoader - anchors.fill: parent - z: 2 + active: visibilities.isDrawing + + sourceComponent: Drawing { + id: drawing + + anchors.fill: parent + z: 2 + } } - DrawingInput { - id: input + Loader { + id: inputLoader - bar: bar - drawing: drawing - panels: panels - popout: panels.drawing - visibilities: visibilities - z: 2 + active: visibilities.isDrawing + + sourceComponent: DrawingInput { + id: input + + bar: bar + drawing: drawingLoader.item + panels: panels + popout: panels.drawing + visibilities: visibilities + z: 2 + } } Interactions { @@ -328,8 +340,8 @@ Variants { anchors.fill: parent bar: bar - drawing: drawing - input: input + drawing: drawingLoader.item + input: inputLoader.item panels: panels popouts: panels.popouts screen: scope.modelData @@ -340,7 +352,7 @@ Variants { id: panels bar: bar - drawingItem: drawing + drawingItem: drawingLoader.item screen: scope.modelData visibilities: visibilities diff --git a/Modules/Drawing/Wrapper.qml b/Modules/Drawing/Wrapper.qml index 62a1705..6ff4da4 100644 --- a/Modules/Drawing/Wrapper.qml +++ b/Modules/Drawing/Wrapper.qml @@ -44,11 +44,11 @@ Item { Loader { id: icon - active: Qt.binding(() => root.shouldBeActive || root.visible) + active: root.shouldBeActive || root.visible anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter asynchronous: true - height: content.contentItem.height + height: content.item.height opacity: root.expanded ? 0 : 1 Behavior on opacity { @@ -64,6 +64,7 @@ Item { Loader { id: content + active: root.shouldBeActive || root.visible anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter asynchronous: true @@ -77,7 +78,5 @@ Item { drawing: root.drawing visibilities: root.visibilities } - - Component.onCompleted: active = Qt.binding(() => root.shouldBeActive || root.visible) } } diff --git a/Modules/SysTray/TrayItem.qml b/Modules/SysTray/TrayItem.qml index cd73d96..ff11cd5 100644 --- a/Modules/SysTray/TrayItem.qml +++ b/Modules/SysTray/TrayItem.qml @@ -3,6 +3,7 @@ import QtQuick import QtQuick.VectorImage import Quickshell import Quickshell.Services.SystemTray +import qs.Helpers import qs.Modules import qs.Components import qs.Config @@ -11,6 +12,7 @@ Item { id: root property bool current: popouts.currentName.startsWith(`traymenu${ind}`) && popouts.hasCurrent + readonly property real dpr: Hypr.monitorFor(loader.screen).scale property bool hasLoaded: false required property int ind required property SystemTrayItem item @@ -48,9 +50,11 @@ Item { id: icon anchors.centerIn: parent + antialiasing: true color: root.current ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface - implicitSize: 22 + implicitSize: 24 * root.dpr layer.enabled: Config.general.color.smart || Config.general.color.scheduleDark + scale: 1 / root.dpr source: root.item.icon } } From d92e5b4cd70d89ed6de333926a6bdc019ff8129b Mon Sep 17 00:00:00 2001 From: zach Date: Wed, 27 May 2026 12:03:06 +0200 Subject: [PATCH 03/18] fix drawing input anchoring incorrectly when using loader --- Drawers/DrawingInput.qml | 4 ++-- Drawers/Interactions.qml | 2 +- Drawers/Windows.qml | 8 ++++---- Modules/Drawing/Wrapper.qml | 1 - 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Drawers/DrawingInput.qml b/Drawers/DrawingInput.qml index 0851a66..a8bec26 100644 --- a/Drawers/DrawingInput.qml +++ b/Drawers/DrawingInput.qml @@ -22,20 +22,20 @@ CustomMouseArea { } acceptedButtons: Qt.LeftButton | Qt.RightButton - anchors.fill: root.visibilities.isDrawing ? parent : undefined + enabled: z > 0 hoverEnabled: true visible: root.visibilities.isDrawing onPositionChanged: event => { const x = event.x; const y = event.y; - if (root.visibilities.isDrawing && (event.buttons & Qt.LeftButton)) { root.drawing.points.push(Qt.point(x, y)); root.drawing.requestPaint(); } if (root.inLeftPanel(root.popout, x, y)) { + console.log("set -2 z"); root.z = -2; root.panels.drawing.expanded = true; } diff --git a/Drawers/Interactions.qml b/Drawers/Interactions.qml index f7fcdf0..7ba6670 100644 --- a/Drawers/Interactions.qml +++ b/Drawers/Interactions.qml @@ -78,7 +78,7 @@ CustomMouseArea { const dragY = y - dragStart.y; if (root.visibilities.isDrawing && !root.inLeftPanel(root.panels.drawing, x, y)) { - // root.input.z = 2; + root.input.z = 2; root.panels.drawing.expanded = false; } diff --git a/Drawers/Windows.qml b/Drawers/Windows.qml index eac6f5f..d45b2cb 100644 --- a/Drawers/Windows.qml +++ b/Drawers/Windows.qml @@ -309,12 +309,11 @@ Variants { id: drawingLoader active: visibilities.isDrawing + anchors.fill: parent + z: 2 sourceComponent: Drawing { id: drawing - - anchors.fill: parent - z: 2 } } @@ -322,6 +321,8 @@ Variants { id: inputLoader active: visibilities.isDrawing + anchors.fill: parent + z: 2 sourceComponent: DrawingInput { id: input @@ -331,7 +332,6 @@ Variants { panels: panels popout: panels.drawing visibilities: visibilities - z: 2 } } diff --git a/Modules/Drawing/Wrapper.qml b/Modules/Drawing/Wrapper.qml index 6ff4da4..6376d30 100644 --- a/Modules/Drawing/Wrapper.qml +++ b/Modules/Drawing/Wrapper.qml @@ -48,7 +48,6 @@ Item { anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter asynchronous: true - height: content.item.height opacity: root.expanded ? 0 : 1 Behavior on opacity { From 41c9d9e9b4c1fa42f3628a5b65191e77cf367641 Mon Sep 17 00:00:00 2001 From: zach Date: Wed, 27 May 2026 13:05:45 +0200 Subject: [PATCH 04/18] avoid unnecessary JS bindings by using narrower conditions, remove logging --- Drawers/Drawing.qml | 1 + Drawers/DrawingInput.qml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Drawers/Drawing.qml b/Drawers/Drawing.qml index 9d7bf63..399b11f 100644 --- a/Drawers/Drawing.qml +++ b/Drawers/Drawing.qml @@ -23,6 +23,7 @@ Canvas { ctx.save(); ctx.lineWidth = root.penWidth; ctx.strokeStyle = root.penColor; + ctx.lineJoin = "round"; ctx.lineCap = "round"; ctx.beginPath(); ctx.moveTo(points[0].x, points[0].y); diff --git a/Drawers/DrawingInput.qml b/Drawers/DrawingInput.qml index a8bec26..ffa37b2 100644 --- a/Drawers/DrawingInput.qml +++ b/Drawers/DrawingInput.qml @@ -32,10 +32,10 @@ CustomMouseArea { if (root.visibilities.isDrawing && (event.buttons & Qt.LeftButton)) { root.drawing.points.push(Qt.point(x, y)); root.drawing.requestPaint(); + return; } - if (root.inLeftPanel(root.popout, x, y)) { - console.log("set -2 z"); + if (!(event.buttons & Qt.LeftButton) && root.inLeftPanel(root.popout, x, y)) { root.z = -2; root.panels.drawing.expanded = true; } From afa3b0e3c402f7f5c7d27cb1bf48ddf1c54f422a Mon Sep 17 00:00:00 2001 From: zach Date: Wed, 27 May 2026 13:46:15 +0200 Subject: [PATCH 05/18] cache icons based on pixel content instead of image string --- Daemons/NotifServer.qml | 35 +---- Plugins/ZShell/writefile.cpp | 296 +++++++++++++++++------------------ Plugins/ZShell/writefile.hpp | 15 +- 3 files changed, 154 insertions(+), 192 deletions(-) diff --git a/Daemons/NotifServer.qml b/Daemons/NotifServer.qml index e5a560f..608ac03 100644 --- a/Daemons/NotifServer.qml +++ b/Daemons/NotifServer.qml @@ -218,7 +218,7 @@ Singleton { function onImageChanged(): void { notif.imageSource = notif.notification.image || ""; notif.image = notif.imageSource; - notif.cacheImageIfNeeded(); + notif.cacheImageIfNeeded(notif.imageSource); } function onResidentChanged(): void { @@ -283,29 +283,18 @@ Singleton { } property int urgency: NotificationUrgency.Normal - function cacheImageIfNeeded(): void { - const source = imageSource; - + function cacheImageIfNeeded(source: string): void { if (!source || cachingImage) return; if (cachedImageSource === source) return; - if (source.startsWith("file:")) { - cachedImageSource = source; - image = source; - return; - } - - const hash = hashForString(source); - const cache = `${Paths.notifimagecache}/${hash}.png`; - const cacheUrl = Qt.resolvedUrl(cache); - cachingImage = true; - ZShellIo.saveImage(source, cacheUrl, () => { + + ZShellIo.cacheImage(Qt.resolvedUrl(source), Paths.notifimagecache, (path, url) => { cachedImageSource = source; - image = cache; + image = path; cachingImage = false; }, () => { cachingImage = false; @@ -321,20 +310,6 @@ Singleton { } } - function hashForString(s: string): string { - let h1 = 0xdeadbeef, h2 = 0x41c6ce57, ch; - for (let i = 0; i < s.length; i++) { - ch = s.charCodeAt(i); - h1 = Math.imul(h1 ^ ch, 2654435761); - h2 = Math.imul(h2 ^ ch, 1597334677); - } - h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507); - h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909); - h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507); - h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909); - return (h2 >>> 0).toString(16).padStart(8, "0") + (h1 >>> 0).toString(16).padStart(8, "0"); - } - function lock(item: Item): void { locks.add(item); } diff --git a/Plugins/ZShell/writefile.cpp b/Plugins/ZShell/writefile.cpp index 195edd4..504ba86 100644 --- a/Plugins/ZShell/writefile.cpp +++ b/Plugins/ZShell/writefile.cpp @@ -1,17 +1,22 @@ #include "writefile.hpp" #include +#include +#include +#include #include #include #include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ZShell { @@ -84,54 +89,51 @@ void ZShellIo::saveItem( &QQuickItemGrabResult::ready, this, [grabResult, scaledRect, path, onSaved, onFailed, this]() { + const auto future = QtConcurrent::run([grabResult, scaledRect, path]() { + QImage image = grabResult->image(); - QImage image = grabResult->image(); - - if (scaledRect.isValid()) { - image = image.copy(scaledRect); - } - - const auto future = QtConcurrent::run([image, path]() { + if (scaledRect.isValid()) { + image = image.copy(scaledRect); + } const QString file = path.toLocalFile(); const QString parent = QFileInfo(file).absolutePath(); - return QDir().mkpath(parent) && image.save(file); + QDir().mkpath(parent); + + QSaveFile out(file); + if (!out.open(QIODevice::WriteOnly)) { + return false; + } + + if (!image.save(&out, "PNG")) { + return false; + } + + return out.commit(); }); auto* watcher = new QFutureWatcher(this); auto* engine = qmlEngine(this); - QObject::connect( - watcher, - &QFutureWatcher::finished, - this, - [=]() { - + QObject::connect(watcher, &QFutureWatcher::finished, this, [=]() { if (watcher->result()) { - - if (onSaved.isCallable()) { + if (onSaved.isCallable() && engine) { onSaved.call({ - QJSValue(path.toLocalFile()), - engine->toScriptValue(QVariant::fromValue(path)) + engine->toScriptValue(path.toLocalFile()), + engine->toScriptValue(path) }); } - } else { - - qWarning() << "ZShellIo::saveItem: failed to save" - << path; - - if (onFailed.isCallable()) { + qWarning() << "ZShellIo::saveItem: failed to save" << path; + if (onFailed.isCallable() && engine) { onFailed.call({ - engine->toScriptValue(QVariant::fromValue(path)) + engine->toScriptValue(path) }); } } - watcher->deleteLater(); - } - ); + }); watcher->setFuture(future); } @@ -139,106 +141,130 @@ void ZShellIo::saveItem( } // ============================================================ -// saveImage +// cacheImage // ============================================================ -void ZShellIo::saveImage(const QUrl& source, const QUrl& target) { - this->saveImage(source, target, QJSValue(), QJSValue()); +void ZShellIo::cacheImage(const QUrl& source, const QString& cacheDir) { + this->cacheImage(source, cacheDir, QJSValue(), QJSValue()); } -void ZShellIo::saveImage(const QUrl& source, const QUrl& target, QJSValue onSaved) { - this->saveImage(source, target, onSaved, QJSValue()); +void ZShellIo::cacheImage(const QUrl& source, const QString& cacheDir, QJSValue onSaved) { + this->cacheImage(source, cacheDir, onSaved, QJSValue()); } -void ZShellIo::saveImage( +void ZShellIo::cacheImage( const QUrl& source, - const QUrl& target, + const QString& cacheDir, QJSValue onSaved, QJSValue onFailed ) { - auto* engine = qmlEngine(this); + if (cacheDir.isEmpty()) { + qWarning() << "ZShellIo::cacheImage: cacheDir is empty"; + return; + } - const auto future = QtConcurrent::run([this, source, target]() { - return this->saveImageInternal(source, target); + QImage image; + if (!loadSourceImage(source, image)) { + qWarning() << "ZShellIo::cacheImage: failed to load source image" << source; + auto* engine = qmlEngine(this); + if (onFailed.isCallable() && engine) { + onFailed.call({ + engine->toScriptValue(source), + engine->toScriptValue(cacheDir) + }); + } + return; + } + + const auto future = QtConcurrent::run([image, cacheDir]() -> QString { + if (image.isNull()) { + return QString(); + } + + const QImage normalized = image.convertToFormat(QImage::Format_RGBA8888); + + const QByteArray bytes( + reinterpret_cast(normalized.constBits()), + qsizetype(normalized.sizeInBytes()) + ); + + const QByteArray digest = + QCryptographicHash::hash(bytes, QCryptographicHash::Sha256).toHex(); + + QDir dir(cacheDir); + if (!dir.exists() && !QDir().mkpath(cacheDir)) { + return QString(); + } + + const QString finalPath = dir.filePath(QString::fromLatin1(digest) + ".png"); + + if (QFile::exists(finalPath)) { + return finalPath; + } + + QSaveFile out(finalPath); + if (!out.open(QIODevice::WriteOnly)) { + return QString(); + } + + if (!normalized.save(&out, "PNG")) { + return QString(); + } + + if (!out.commit()) { + return QString(); + } + + return finalPath; }); - auto* watcher = new QFutureWatcher(this); + auto* watcher = new QFutureWatcher(this); + auto* engine = qmlEngine(this); - QObject::connect( - watcher, - &QFutureWatcher::finished, - this, - [=]() { + QObject::connect(watcher, &QFutureWatcher::finished, this, [=]() { + const QString finalPath = watcher->result(); - if (watcher->result()) { - - if (onSaved.isCallable()) { + if (!finalPath.isEmpty()) { + if (onSaved.isCallable() && engine) { onSaved.call({ - QJSValue(target.toLocalFile()), - engine->toScriptValue(QVariant::fromValue(target)) + engine->toScriptValue(finalPath), + engine->toScriptValue(QUrl::fromLocalFile(finalPath)) }); } - } else { - - qWarning() << "ZShellIo::saveImage: failed to save" - << source - << "to" - << target; - - if (onFailed.isCallable()) { + qWarning() << "ZShellIo::cacheImage: failed to cache" << source; + if (onFailed.isCallable() && engine) { onFailed.call({ - engine->toScriptValue(QVariant::fromValue(target)) + engine->toScriptValue(source), + engine->toScriptValue(cacheDir) }); } } watcher->deleteLater(); - } - ); + }); watcher->setFuture(future); } -bool ZShellIo::saveImageInternal(const QUrl& source, const QUrl& target) const { +// ============================================================ +// loadSourceImage +// ============================================================ - if (!target.isLocalFile()) { - qWarning() << "ZShellIo::saveImage: target" - << target - << "is not a local file"; - return false; - } - - const QString targetFile = target.toLocalFile(); - - if (!QDir().mkpath(QFileInfo(targetFile).absolutePath())) { - return false; - } - - // ======================================================== - // Local file path - // ======================================================== +bool ZShellIo::loadSourceImage(const QUrl& source, QImage& image) const { + image = QImage(); if (source.isLocalFile()) { - - QFile::remove(targetFile); - - return QFile::copy( - source.toLocalFile(), - targetFile - ); + QImageReader reader(source.toLocalFile()); + reader.setAutoTransform(true); + image = reader.read(); + return !image.isNull(); } - // ======================================================== - // image:// provider path - // ======================================================== - if (source.scheme() == "image") { - auto* engine = qmlEngine(const_cast(this)); - if (!engine) { - qWarning() << "ZShellIo::saveImage: no QQmlEngine"; + qWarning() << "ZShellIo::loadSourceImage: no QQmlEngine"; return false; } @@ -246,70 +272,44 @@ bool ZShellIo::saveImageInternal(const QUrl& source, const QUrl& target) const { const QString imageId = source.path().startsWith('/') - ? source.path().mid(1) - : source.path(); - - auto* providerBase = - engine->imageProvider(providerId); + ? source.path().mid(1) + : source.path(); + auto* providerBase = engine->imageProvider(providerId); if (!providerBase) { - qWarning() << "ZShellIo::saveImage: provider not found" + qWarning() << "ZShellIo::loadSourceImage: provider not found" << providerId; return false; } - auto* provider = - dynamic_cast(providerBase); - + auto* provider = dynamic_cast(providerBase); if (!provider) { - qWarning() << "ZShellIo::saveImage: provider is not a QQuickImageProvider" - << providerId; - return false; - } - - if (!provider) { - qWarning() << "ZShellIo::saveImage: provider not found" + qWarning() << "ZShellIo::loadSourceImage: provider is not a QQuickImageProvider" << providerId; return false; } QSize size; - QImage image; switch (provider->imageType()) { - case QQuickImageProvider::Image: - image = provider->requestImage( - imageId, - &size, - QSize() - ); + image = provider->requestImage(imageId, &size, QSize()); break; case QQuickImageProvider::Pixmap: - image = provider->requestPixmap( - imageId, - &size, - QSize() - ).toImage(); + image = provider->requestPixmap(imageId, &size, QSize()).toImage(); break; default: - qWarning() << "ZShellIo::saveImage: unsupported provider type"; + qWarning() << "ZShellIo::loadSourceImage: unsupported provider type" + << providerId; return false; } - if (image.isNull()) { - qWarning() << "ZShellIo::saveImage: provider returned null image"; - return false; - } - - return image.save(targetFile); + return !image.isNull(); } - qWarning() << "ZShellIo::saveImage: unsupported source" - << source; - + qWarning() << "ZShellIo::loadSourceImage: unsupported source" << source; return false; } @@ -318,18 +318,12 @@ bool ZShellIo::saveImageInternal(const QUrl& source, const QUrl& target) const { // ============================================================ bool ZShellIo::copyFile(const QUrl& source, const QUrl& target, bool overwrite) const { - if (!source.isLocalFile()) { - qWarning() << "ZShellIo::copyFile: source" - << source - << "is not a local file"; + qWarning() << "ZShellIo::copyFile: source" << source << "is not a local file"; return false; } - if (!target.isLocalFile()) { - qWarning() << "ZShellIo::copyFile: target" - << target - << "is not a local file"; + qWarning() << "ZShellIo::copyFile: target" << target << "is not a local file"; return false; } @@ -337,18 +331,12 @@ bool ZShellIo::copyFile(const QUrl& source, const QUrl& target, bool overwrite) QFile::remove(target.toLocalFile()); } - return QFile::copy( - source.toLocalFile(), - target.toLocalFile() - ); + return QFile::copy(source.toLocalFile(), target.toLocalFile()); } bool ZShellIo::deleteFile(const QUrl& path) const { - if (!path.isLocalFile()) { - qWarning() << "ZShellIo::deleteFile: path" - << path - << "is not a local file"; + qWarning() << "ZShellIo::deleteFile: path" << path << "is not a local file"; return false; } @@ -356,10 +344,8 @@ bool ZShellIo::deleteFile(const QUrl& path) const { } QString ZShellIo::toLocalFile(const QUrl& url) const { - if (!url.isLocalFile()) { - qWarning() << "ZShellIo::toLocalFile: given url is not a local file" - << url; + qWarning() << "ZShellIo::toLocalFile: given url is not a local file" << url; return QString(); } diff --git a/Plugins/ZShell/writefile.hpp b/Plugins/ZShell/writefile.hpp index f518da9..a74d897 100644 --- a/Plugins/ZShell/writefile.hpp +++ b/Plugins/ZShell/writefile.hpp @@ -1,10 +1,11 @@ #pragma once #include -#include -#include +#include +#include +#include #include -#include +#include namespace ZShell { @@ -23,9 +24,9 @@ Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved); Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved, QJSValue onFailed); -Q_INVOKABLE void saveImage(const QUrl& source, const QUrl& target); -Q_INVOKABLE void saveImage(const QUrl& source, const QUrl& target, QJSValue onSaved); -Q_INVOKABLE void saveImage(const QUrl& source, const QUrl& target, QJSValue onSaved, QJSValue onFailed); +Q_INVOKABLE void cacheImage(const QUrl& source, const QString& cacheDir); +Q_INVOKABLE void cacheImage(const QUrl& source, const QString& cacheDir, QJSValue onSaved); +Q_INVOKABLE void cacheImage(const QUrl& source, const QString& cacheDir, QJSValue onSaved, QJSValue onFailed); // clang-format on Q_INVOKABLE bool copyFile(const QUrl& source, const QUrl& target, bool overwrite = true) const; @@ -33,7 +34,7 @@ Q_INVOKABLE bool deleteFile(const QUrl& path) const; Q_INVOKABLE QString toLocalFile(const QUrl& url) const; private: -bool saveImageInternal(const QUrl& source, const QUrl& target) const; +bool loadSourceImage(const QUrl& source, QImage& image) const; }; } // namespace ZShell From 90e0987f2267b7275e668efd0801de39bc52f73a Mon Sep 17 00:00:00 2001 From: zach Date: Wed, 27 May 2026 14:19:13 +0200 Subject: [PATCH 06/18] load icon-theme versions of tray icons for some apps --- Modules/SysTray/TrayItem.qml | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Modules/SysTray/TrayItem.qml b/Modules/SysTray/TrayItem.qml index ff11cd5..ff0faa2 100644 --- a/Modules/SysTray/TrayItem.qml +++ b/Modules/SysTray/TrayItem.qml @@ -19,6 +19,21 @@ Item { required property RowLayout loader required property Wrapper popouts + function resolveIcon(app: string, icon: string): string { + if (app === "chrome_status_icon_1") { + return Quickshell.iconPath("discord-tray"); + } else if (app === "AyuGramDesktop") { + if (icon === Quickshell.iconPath("com.ayugram.desktop-attention-symbolic")) + return Quickshell.iconPath("telegram-attention-panel"); + else if (icon === Quickshell.iconPath("com.ayugram.desktop-mute-symbolic")) + return Quickshell.iconPath("telegram-mute-panel"); + else if (icon === Quickshell.iconPath("com.ayugram.desktop-symbolic")) + return Quickshell.iconPath("telegram-panel"); + } + + return root.item.icon; + } + CustomRect { anchors.fill: parent anchors.margins: 3 @@ -32,6 +47,7 @@ Item { onClicked: { if (mouse.button === Qt.LeftButton) { root.item.activate(); + console.log(icon.source + "\n" + root.item.id); } else if (mouse.button === Qt.RightButton) { root.popouts.currentName = `traymenu${root.ind}`; root.popouts.currentCenter = Qt.binding(() => root.mapToItem(root.loader, root.implicitWidth / 2, 0).x); @@ -55,6 +71,6 @@ Item { implicitSize: 24 * root.dpr layer.enabled: Config.general.color.smart || Config.general.color.scheduleDark scale: 1 / root.dpr - source: root.item.icon + source: root.resolveIcon(root.item.id, root.item.icon) } } From 1a72757e4171ab75d058863da2a0c27d6505c4f7 Mon Sep 17 00:00:00 2001 From: zach Date: Wed, 27 May 2026 22:51:03 +0200 Subject: [PATCH 07/18] more tray icon replacements --- .gitignore | 2 ++ Config/General.qml | 2 +- Modules/SysTray/TrayItem.qml | 7 +++++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 72ab8cb..39a44e6 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ uv.lock .qtcreator/ dist/ **/target/ +**/test-plugins/ +**/Charts/ diff --git a/Config/General.qml b/Config/General.qml index 7948941..45d3513 100644 --- a/Config/General.qml +++ b/Config/General.qml @@ -27,7 +27,7 @@ JsonObject { { perc: 20, name: qsTr("Low battery"), - message: qsTr("Battery at %1%").arg(Battery.currentPerc * 100), + message: qsTr("Battery is low"), icon: "battery_android_frame_2" }, ] diff --git a/Modules/SysTray/TrayItem.qml b/Modules/SysTray/TrayItem.qml index ff0faa2..5b0880f 100644 --- a/Modules/SysTray/TrayItem.qml +++ b/Modules/SysTray/TrayItem.qml @@ -29,6 +29,13 @@ Item { return Quickshell.iconPath("telegram-mute-panel"); else if (icon === Quickshell.iconPath("com.ayugram.desktop-symbolic")) return Quickshell.iconPath("telegram-panel"); + } else if (app === "TelegramDesktop") { + if (icon === Quickshell.iconPath("org.telegram.desktop-symbolic")) + return Quickshell.iconPath("telegram-panel"); + else if (icon === Quickshell.iconPath("org.telegram.desktop-attention-symbolic")) + return Quickshell.iconPath("telegram-attention-panel"); + else if (icon === Quickshell.iconPath("org.telegram.desktop-mute-symbolic")) + return Quickshell.iconPath("telegram-mute-panel"); } return root.item.icon; From 36fb9254951f1513157f0bda103a81000b0af256 Mon Sep 17 00:00:00 2001 From: zach Date: Wed, 27 May 2026 23:14:02 +0200 Subject: [PATCH 08/18] fix gpu name in resource popout --- Helpers/SystemUsage.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/Helpers/SystemUsage.qml b/Helpers/SystemUsage.qml index 02c035a..da9ab18 100644 --- a/Helpers/SystemUsage.qml +++ b/Helpers/SystemUsage.qml @@ -17,6 +17,7 @@ Singleton { property var disks: [] property real gpuMemTotal: 0 property real gpuMemUsed + property string gpuName property real gpuPerc property real gpuTemp readonly property string gpuType: Config.services.gpuType.toUpperCase() || autoGpuType From 8e14993633752825a87efc704affc59f0aca493c Mon Sep 17 00:00:00 2001 From: zach Date: Wed, 27 May 2026 23:22:09 +0200 Subject: [PATCH 09/18] debug logging of battery percent props --- Daemons/Battery.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/Daemons/Battery.qml b/Daemons/Battery.qml index ba346bd..e3dbde1 100644 --- a/Daemons/Battery.qml +++ b/Daemons/Battery.qml @@ -36,6 +36,7 @@ Scope { for (const perc of root.popupThresholds) { if (p <= perc.perc && !perc.warned) { perc.warned = true; + console.log(perc.warned + "\n" + [...Config.general.battery.popupThresholds][0].warned); Toaster.toast(perc.title ?? qsTr("Battery warning"), perc.message ?? qsTr("Battery perc is low"), perc.icon ?? "battery_android_alert", perc.critical ? Toast.Error : Toast.Warning); } } From 62092647445b75ea54fa72cf6adfbb1b448d7b97 Mon Sep 17 00:00:00 2001 From: zach Date: Thu, 28 May 2026 00:53:49 +0200 Subject: [PATCH 10/18] use loader for updates popout --- Modules/Updates/UpdatesPopout.qml | 233 ++++++++++++++++-------------- Modules/Wrapper.qml | 55 +------ 2 files changed, 128 insertions(+), 160 deletions(-) diff --git a/Modules/Updates/UpdatesPopout.qml b/Modules/Updates/UpdatesPopout.qml index 5f0d161..1665777 100644 --- a/Modules/Updates/UpdatesPopout.qml +++ b/Modules/Updates/UpdatesPopout.qml @@ -11,150 +11,161 @@ import qs.Helpers CustomClippingRect { id: root + readonly property bool hasUpdates: Object.keys(Updates.updates)?.length > 0 readonly property int itemHeight: 50 + Appearance.padding.smaller * 2 required property var wrapper color: DynamicColors.tPalette.m3surfaceContainer - implicitHeight: updatesList.visible ? updatesList.implicitHeight + Appearance.padding.small * 2 : noUpdates.height - implicitWidth: updatesList.visible ? updatesList.contentWidth + Appearance.padding.small * 2 : noUpdates.width + implicitHeight: hasUpdates ? updatesListLoader.item?.implicitHeight + Appearance.padding.small * 2 : noUpdatesLoader.item.height + implicitWidth: hasUpdates ? updatesListLoader.item?.contentWidth + Appearance.padding.small * 2 : noUpdatesLoader.item.width radius: Appearance.rounding.small - Item { - id: noUpdates + Loader { + id: noUpdatesLoader + active: !root.hasUpdates anchors.centerIn: parent - height: 200 - visible: script.values.length === 0 - width: 600 - MaterialIcon { - id: noUpdatesIcon + sourceComponent: Item { + id: noUpdates - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: parent.top - color: DynamicColors.tPalette.m3onSurfaceVariant - font.pointSize: Appearance.font.size.extraLarge * 3 - horizontalAlignment: Text.AlignHCenter - text: "check" - } + height: 200 + width: 300 - CustomText { - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: noUpdatesIcon.bottom - color: DynamicColors.tPalette.m3onSurfaceVariant - horizontalAlignment: Text.AlignHCenter - text: qsTr("No updates available") - verticalAlignment: Text.AlignVCenter + MaterialIcon { + id: noUpdatesIcon + + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + color: DynamicColors.tPalette.m3onSurfaceVariant + font.pointSize: Appearance.font.size.extraLarge * 3 + horizontalAlignment: Text.AlignHCenter + text: "check" + } + + CustomText { + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: noUpdatesIcon.bottom + color: DynamicColors.tPalette.m3onSurfaceVariant + horizontalAlignment: Text.AlignHCenter + text: qsTr("No updates available") + verticalAlignment: Text.AlignVCenter + } } } - CustomListView { - id: updatesList + Loader { + id: updatesListLoader + active: root.hasUpdates anchors.centerIn: parent - contentHeight: childrenRect.height - contentWidth: 600 - displayMarginBeginning: root.itemHeight - displayMarginEnd: root.itemHeight - implicitHeight: Math.min(contentHeight, (root.itemHeight + spacing) * 5 - spacing) - implicitWidth: contentWidth - spacing: Appearance.spacing.normal - visible: script.values.length > 0 - delegate: CustomRect { - id: update + sourceComponent: CustomListView { + id: updatesList - required property var modelData - readonly property list sections: modelData.update.split(" ") + contentHeight: childrenRect.height + contentWidth: 600 + displayMarginBeginning: root.itemHeight + displayMarginEnd: root.itemHeight + implicitHeight: Math.min(contentHeight, (root.itemHeight + spacing) * 5 - spacing) + implicitWidth: contentWidth + spacing: Appearance.spacing.normal - // anchors.left: parent.left - // anchors.right: parent.right - color: DynamicColors.tPalette.m3surfaceContainer - implicitHeight: root.itemHeight - implicitWidth: 600 - radius: Appearance.rounding.small - Appearance.padding.small + delegate: CustomRect { + id: update - RowLayout { - anchors.fill: parent - anchors.leftMargin: Appearance.padding.smaller - anchors.rightMargin: Appearance.padding.smaller + required property var modelData + readonly property list sections: modelData.update.split(" ") - MaterialIcon { - font.pointSize: Appearance.font.size.large * 2 - text: "package_2" - } - - ColumnLayout { - Layout.fillWidth: true - - CustomText { - Layout.fillWidth: true - Layout.preferredHeight: 25 - elide: Text.ElideRight - font.pointSize: Appearance.font.size.large - text: update.sections[0] - } - - CustomText { - Layout.fillWidth: true - color: DynamicColors.palette.m3onSurfaceVariant - text: Updates.formatUpdateTime(update.modelData.timestamp) - } - } + // anchors.left: parent.left + // anchors.right: parent.right + color: DynamicColors.tPalette.m3surfaceContainer + implicitHeight: root.itemHeight + implicitWidth: 600 + radius: Appearance.rounding.small - Appearance.padding.small RowLayout { - Layout.fillHeight: true - Layout.preferredWidth: 300 - - MarqueeText { - id: versionFrom - - Layout.fillHeight: true - Layout.preferredWidth: 125 - animate: true - color: DynamicColors.palette.m3tertiary - font.pointSize: Appearance.font.size.large - horizontalAlignment: Text.AlignHCenter - marqueeEnabled: true - pauseMs: 4000 - text: update.sections[1] - width: 125 - } + anchors.fill: parent + anchors.leftMargin: Appearance.padding.smaller + anchors.rightMargin: Appearance.padding.smaller MaterialIcon { - Layout.fillHeight: true - color: DynamicColors.palette.m3secondary - font.pointSize: Appearance.font.size.extraLarge - horizontalAlignment: Text.AlignHCenter - text: "arrow_right_alt" - verticalAlignment: Text.AlignVCenter + font.pointSize: Appearance.font.size.large * 2 + text: "package_2" } - MarqueeText { - id: versionTo + ColumnLayout { + Layout.fillWidth: true + CustomText { + Layout.fillWidth: true + Layout.preferredHeight: 25 + elide: Text.ElideRight + font.pointSize: Appearance.font.size.large + text: update.sections[0] + } + + CustomText { + Layout.fillWidth: true + color: DynamicColors.palette.m3onSurfaceVariant + text: Updates.formatUpdateTime(update.modelData.timestamp) + } + } + + RowLayout { Layout.fillHeight: true - Layout.preferredWidth: 120 - animate: true - color: DynamicColors.palette.m3primary - font.pointSize: Appearance.font.size.large - horizontalAlignment: Text.AlignHCenter - marqueeEnabled: true - pauseMs: 4000 - text: update.sections[3] - width: 125 + Layout.preferredWidth: 300 + + MarqueeText { + id: versionFrom + + Layout.fillHeight: true + Layout.preferredWidth: 125 + animate: true + color: DynamicColors.palette.m3tertiary + font.pointSize: Appearance.font.size.large + horizontalAlignment: Text.AlignHCenter + marqueeEnabled: true + pauseMs: 4000 + text: update.sections[1] + width: 125 + } + + MaterialIcon { + Layout.fillHeight: true + color: DynamicColors.palette.m3secondary + font.pointSize: Appearance.font.size.extraLarge + horizontalAlignment: Text.AlignHCenter + text: "arrow_right_alt" + verticalAlignment: Text.AlignVCenter + } + + MarqueeText { + id: versionTo + + Layout.fillHeight: true + Layout.preferredWidth: 120 + animate: true + color: DynamicColors.palette.m3primary + font.pointSize: Appearance.font.size.large + horizontalAlignment: Text.AlignHCenter + marqueeEnabled: true + pauseMs: 4000 + text: update.sections[3] + width: 125 + } } } } - } - model: ScriptModel { - id: script + model: ScriptModel { + id: script - objectProp: "update" - values: Object.entries(Updates.updates).sort((a, b) => b[1] - a[1]).map(([update, timestamp]) => ({ - update, - timestamp - })) + objectProp: "update" + values: Object.entries(Updates.updates).sort((a, b) => b[1] - a[1]).map(([update, timestamp]) => ({ + update, + timestamp + })) + } } } } diff --git a/Modules/Wrapper.qml b/Modules/Wrapper.qml index 8bf0ab7..77f9c9e 100644 --- a/Modules/Wrapper.qml +++ b/Modules/Wrapper.qml @@ -15,9 +15,8 @@ Item { property real currentCenter property alias currentName: popoutState.currentName property string detachedMode - readonly property bool isDetached: detachedMode.length > 0 property alias hasCurrent: popoutState.hasCurrent - readonly property real nonAnimHeight: children.find(c => c.shouldBeActive)?.implicitHeight ?? content.implicitHeight + readonly property real nonAnimHeight: content.implicitHeight || 150 readonly property real nonAnimWidth: children.find(c => c.shouldBeActive)?.implicitWidth ?? content.implicitWidth required property real offsetScale property string queuedMode @@ -28,29 +27,13 @@ Item { detachedMode = ""; } - function detach(mode: string): void { - setAnims(true); - if (mode === "winfo") { - detachedMode = mode; - } else { - queuedMode = mode; - detachedMode = "any"; - } - setAnims(false); - focus = true; - } - - function setAnims(detach: bool): void { - const type = `expressive${detach ? "Slow" : "Default"}Spatial`; - animLength = Appearance.anim.durations[type]; - animCurve = Appearance.anim.curves[type]; - } - focus: hasCurrent implicitHeight: nonAnimHeight implicitWidth: nonAnimWidth Behavior on implicitHeight { + enabled: root.offsetScale < 1 + Anim { duration: root.animLength easing.bezierCurve: root.animCurve @@ -72,41 +55,15 @@ Item { Comp { id: content - // anchors.horizontalCenter: parent.horizontalCenter - // anchors.top: parent.top anchors.centerIn: parent - shouldBeActive: root.hasCurrent && !root.detachedMode + shouldBeActive: root.hasCurrent sourceComponent: Content { popouts: popoutState } - } - // Comp { - // id: winfo - // - // anchors.centerIn: parent - // shouldBeActive: root.detachedMode === "winfo" - // - // sourceComponent: WindowInfo { - // client: Hypr.activeToplevel - // screen: root.screen - // } - // } - // - // Comp { - // id: controlCenter - // - // anchors.centerIn: parent - // shouldBeActive: root.detachedMode === "any" - // - // sourceComponent: ControlCenter { - // active: root.queuedMode - // screen: root.screen - // - // onClose: root.close() - // } - // } + onActiveChanged: console.log("active:", content.implicitHeight) + } component Comp: Loader { id: comp From fa87789fcd25046771b750f7562fe1f6e63f81a5 Mon Sep 17 00:00:00 2001 From: zach Date: Thu, 28 May 2026 00:54:39 +0200 Subject: [PATCH 11/18] remove logging --- Modules/Wrapper.qml | 2 -- 1 file changed, 2 deletions(-) diff --git a/Modules/Wrapper.qml b/Modules/Wrapper.qml index 77f9c9e..b45c609 100644 --- a/Modules/Wrapper.qml +++ b/Modules/Wrapper.qml @@ -61,8 +61,6 @@ Item { sourceComponent: Content { popouts: popoutState } - - onActiveChanged: console.log("active:", content.implicitHeight) } component Comp: Loader { From 8323bc31a05d02cae348343c49b570d5177192ea Mon Sep 17 00:00:00 2001 From: zach Date: Thu, 28 May 2026 01:10:00 +0200 Subject: [PATCH 12/18] properly handle disabling popouts --- Modules/SysTray/TrayItem.qml | 2 +- Modules/SysTray/TrayWidget.qml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/SysTray/TrayItem.qml b/Modules/SysTray/TrayItem.qml index 5b0880f..e29e797 100644 --- a/Modules/SysTray/TrayItem.qml +++ b/Modules/SysTray/TrayItem.qml @@ -55,7 +55,7 @@ Item { if (mouse.button === Qt.LeftButton) { root.item.activate(); console.log(icon.source + "\n" + root.item.id); - } else if (mouse.button === Qt.RightButton) { + } else if (mouse.button === Qt.RightButton && Config.barConfig.popouts.tray) { root.popouts.currentName = `traymenu${root.ind}`; root.popouts.currentCenter = Qt.binding(() => root.mapToItem(root.loader, root.implicitWidth / 2, 0).x); root.popouts.hasCurrent = true; diff --git a/Modules/SysTray/TrayWidget.qml b/Modules/SysTray/TrayWidget.qml index c368a26..2d3a768 100644 --- a/Modules/SysTray/TrayWidget.qml +++ b/Modules/SysTray/TrayWidget.qml @@ -23,12 +23,12 @@ RowLayout { let modRowPos = sysTrayMod.mapToItem(sysModRow, modPos.x, modPos.y); let child = sysModRow.childAt(modRowPos.x, modRowPos.y); if (child) { - if (child.objectName === "audioWidget") + if (child.objectName === "audioWidget" && Config.barConfig.popouts.audio) return { id: "audio", item: child }; - if (child.objectName === "upowerWidget") + if (child.objectName === "upowerWidget" && Config.barConfig.popouts.upower) return { id: "upower", item: child From f22c08991c269ce0605dec159c494ad2e94a2b41 Mon Sep 17 00:00:00 2001 From: zach Date: Thu, 28 May 2026 02:03:39 +0200 Subject: [PATCH 13/18] revert notification icon oopsie --- Daemons/NotifServer.qml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Daemons/NotifServer.qml b/Daemons/NotifServer.qml index 608ac03..00fa168 100644 --- a/Daemons/NotifServer.qml +++ b/Daemons/NotifServer.qml @@ -218,7 +218,7 @@ Singleton { function onImageChanged(): void { notif.imageSource = notif.notification.image || ""; notif.image = notif.imageSource; - notif.cacheImageIfNeeded(notif.imageSource); + notif.cacheImageIfNeeded(); } function onResidentChanged(): void { @@ -283,7 +283,9 @@ Singleton { } property int urgency: NotificationUrgency.Normal - function cacheImageIfNeeded(source: string): void { + function cacheImageIfNeeded(): void { + const source = imageSource; + if (!source || cachingImage) return; From ba67e56fdabfa9fce610cc0b4785550318d0cd99 Mon Sep 17 00:00:00 2001 From: zach Date: Thu, 28 May 2026 02:12:11 +0200 Subject: [PATCH 14/18] properly load/unload settings --- Modules/Settings/Wrapper.qml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Modules/Settings/Wrapper.qml b/Modules/Settings/Wrapper.qml index 8795427..a734fd4 100644 --- a/Modules/Settings/Wrapper.qml +++ b/Modules/Settings/Wrapper.qml @@ -32,10 +32,9 @@ Item { Loader { id: content - active: true + active: root.shouldBeActive || root.visible anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter - visible: true sourceComponent: Content { screen: root.screen From ef1bcf6c731b7a529b6a4bc677a5b29ed199c565 Mon Sep 17 00:00:00 2001 From: zach Date: Thu, 28 May 2026 11:48:23 +0200 Subject: [PATCH 15/18] new config option to set tray icon base size --- Config/BarConfig.qml | 5 +++++ Config/Config.qml | 3 +++ Modules/Settings/Categories/Bar.qml | 15 +++++++++++++++ Modules/SysTray/TrayItem.qml | 5 +++-- scripts/SettingsIndex.mjs | 8 ++++++++ 5 files changed, 34 insertions(+), 2 deletions(-) diff --git a/Config/BarConfig.qml b/Config/BarConfig.qml index a9d2be1..0f489ef 100644 --- a/Config/BarConfig.qml +++ b/Config/BarConfig.qml @@ -59,6 +59,8 @@ JsonObject { } property int rounding: 8 property int smoothing: 32 + property Tray tray: Tray { + } component Popouts: JsonObject { property bool activeWindow: true @@ -69,4 +71,7 @@ JsonObject { property bool tray: true property bool upower: true } + component Tray: JsonObject { + property int trayIconSize: 24 + } } diff --git a/Config/Config.qml b/Config/Config.qml index 7bb6b0a..009e67a 100644 --- a/Config/Config.qml +++ b/Config/Config.qml @@ -100,6 +100,9 @@ Singleton { border: barConfig.border, smoothing: barConfig.smoothing, height: barConfig.height, + tray: { + trayIconSize: barConfig.tray.trayIconSize + }, popouts: { tray: barConfig.popouts.tray, audio: barConfig.popouts.audio, diff --git a/Modules/Settings/Categories/Bar.qml b/Modules/Settings/Categories/Bar.qml index b7afaab..f139ef8 100644 --- a/Modules/Settings/Categories/Bar.qml +++ b/Modules/Settings/Categories/Bar.qml @@ -56,6 +56,21 @@ SettingsPage { } } + SettingsSection { + sectionId: "Tray" + + SettingsHeader { + name: "System tray" + } + + SettingSpinBox { + min: 16 + name: "Tray icon size" + object: Config.barConfig.tray + setting: "trayIconSize" + } + } + SettingsSection { sectionId: "Popouts" diff --git a/Modules/SysTray/TrayItem.qml b/Modules/SysTray/TrayItem.qml index e29e797..5f3b246 100644 --- a/Modules/SysTray/TrayItem.qml +++ b/Modules/SysTray/TrayItem.qml @@ -13,7 +13,6 @@ Item { property bool current: popouts.currentName.startsWith(`traymenu${ind}`) && popouts.hasCurrent readonly property real dpr: Hypr.monitorFor(loader.screen).scale - property bool hasLoaded: false required property int ind required property SystemTrayItem item required property RowLayout loader @@ -36,6 +35,8 @@ Item { return Quickshell.iconPath("telegram-attention-panel"); else if (icon === Quickshell.iconPath("org.telegram.desktop-mute-symbolic")) return Quickshell.iconPath("telegram-mute-panel"); + } else if (app === "steam") { + return Quickshell.iconPath("steam_tray_mono"); } return root.item.icon; @@ -75,7 +76,7 @@ Item { anchors.centerIn: parent antialiasing: true color: root.current ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface - implicitSize: 24 * root.dpr + implicitSize: Config.barConfig.tray.trayIconSize * root.dpr layer.enabled: Config.general.color.smart || Config.general.color.scheduleDark scale: 1 / root.dpr source: root.resolveIcon(root.item.id, root.item.icon) diff --git a/scripts/SettingsIndex.mjs b/scripts/SettingsIndex.mjs index 12c74e1..d5aec75 100644 --- a/scripts/SettingsIndex.mjs +++ b/scripts/SettingsIndex.mjs @@ -180,6 +180,14 @@ export const settingsIndex = [ section: "Bar", keywords: ["smoothing", "rounding"], }, + // System tray section + { + name: "Tray icon size", + category: "bar", + categoryName: "Bar", + section: "Tray", + keywords: ["tray", "icon", "size"], + }, // Popouts section { name: "Tray", From 4005e197eb5bde428bca087e7db382131366130f Mon Sep 17 00:00:00 2001 From: zach Date: Thu, 28 May 2026 14:40:47 +0200 Subject: [PATCH 16/18] better wallpaper cropping on load, cache images to disk and fix image aspect ratio from creating black bars --- Helpers/Wallpapers.qml | 4 +- .../Settings/Controls/WallpaperCropper.qml | 152 +++++++------ Modules/Wallpaper/WallBackground-old.qml | 80 ------- Modules/Wallpaper/WallBackground.qml | 67 +++--- Plugins/ZShell/Internal/CMakeLists.txt | 1 + Plugins/ZShell/Internal/arcgauge.cpp | 4 +- Plugins/ZShell/Internal/arcgauge.hpp | 4 +- Plugins/ZShell/Internal/wallpaperimage.cpp | 199 ++++++++++++++++++ Plugins/ZShell/Internal/wallpaperimage.hpp | 95 +++++++++ 9 files changed, 429 insertions(+), 177 deletions(-) delete mode 100644 Modules/Wallpaper/WallBackground-old.qml create mode 100644 Plugins/ZShell/Internal/wallpaperimage.cpp create mode 100644 Plugins/ZShell/Internal/wallpaperimage.hpp diff --git a/Helpers/Wallpapers.qml b/Helpers/Wallpapers.qml index 10c8f22..c2609ad 100644 --- a/Helpers/Wallpapers.qml +++ b/Helpers/Wallpapers.qml @@ -53,14 +53,12 @@ Searcher { }; root.crops = updated; - monitorCrops.writeAdapter(); - monitorCrops.reload(); } function setWallpaper(path: string): void { actualCurrent = path; WallpaperPath.currentWallpaperPath = path; - Quickshell.screens.forEach(n => setCrop(n.name, Qt.rect(0, 0, 0, 0), Qt.rect(0, 0, 0, 0), 1.0)); + Quickshell.screens.forEach(n => setCrop(n.name, Qt.rect(0, 0, 1, 1), Qt.rect(0, 0, 0, 0), 1.0)); Quickshell.execDetached(["zshell-cli", "wallpaper", "lockscreen", "--input-image", `${root.actualCurrent}`, "--output-path", `${Paths.state}/lockscreen_bg.png`, "--blur-amount", `${Config.lock.blurAmount}`]); if (Config.general.color.schemeGeneration) Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--image-path", `${root.actualCurrent}`, "--scheme", `${Config.colors.schemeType}`, "--mode", `${Config.general.color.mode}`]); diff --git a/Modules/Settings/Controls/WallpaperCropper.qml b/Modules/Settings/Controls/WallpaperCropper.qml index d8a9e12..efe7cc6 100644 --- a/Modules/Settings/Controls/WallpaperCropper.qml +++ b/Modules/Settings/Controls/WallpaperCropper.qml @@ -80,12 +80,26 @@ Item { required property ShellScreen modelData function applyCrop(): void { - const croprect = cropRect.mapToItem(scaledImg, 0, 0, cropRect.width, cropRect.height); - const upscaledRect = Qt.rect((croprect.x - cropRect.imageX) / scaledImg.paintedWidth, (croprect.y - cropRect.imageY) / scaledImg.paintedHeight, croprect.width / scaledImg.paintedWidth, croprect.height / scaledImg.paintedHeight); - Wallpapers.setCrop(delegate.modelData.name, upscaledRect, croprect, cropRect.zoom); + if (!cropRectLoader.item) return; + const cropRect = cropRectLoader.item; + + // We need to calculate the exact percentage coordinates that map perfectly + // to our C++ backend, regardless of current display scaling + const cropXPercent = (cropRect.x - cropRect.imageX) / scaledImg.paintedWidth; + const cropYPercent = (cropRect.y - cropRect.imageY) / scaledImg.paintedHeight; + const cropWidthPercent = cropRect.width / scaledImg.paintedWidth; + const cropHeightPercent = cropRect.height / scaledImg.paintedHeight; + + const finalRect = Qt.rect(cropXPercent, cropYPercent, cropWidthPercent, cropHeightPercent); + + // We just pass the percentages directly to the backend + Wallpapers.setCrop(delegate.modelData.name, finalRect, finalRect, cropRect.zoom); } function zoomClipRect(zoom: real): void { + if (!cropRectLoader.item) return; + const cropRect = cropRectLoader.item; + let oldCenterX = cropRect.x + cropRect.width * 0.5; let oldCenterY = cropRect.y + cropRect.height * 0.5; @@ -128,7 +142,7 @@ Item { Layout.preferredHeight: 10 from: 1.0 to: 5.0 - value: cropRect.zoom + value: cropRectLoader.item ? cropRectLoader.item.zoom : 1.0 onMoved: { delegate.zoomClipRect(value); @@ -156,15 +170,20 @@ Item { sourceSize.width: parent.width onPaintedWidthChanged: { - if (paintedWidth > 0) { - scaledImg.displayData = Wallpapers.getCrop(delegate.modelData.name); - cropRect.zoom = Wallpapers.getCrop(delegate.modelData.name).zoom; - cropRect.restoreFromData(); + if (paintedWidth > 0 && cropRectLoader.item) { + cropRectLoader.item.restoreFromData(); + } + } + onSourceChanged: { + if (cropRectLoader.item) { + cropRectLoader.item.restoreFromData(); + } + } + onStatusChanged: { + if (scaledImg.status == Image.Ready && cropRectLoader.item) { + cropRectLoader.item.restoreFromData(); } } - onSourceChanged: cropRect.clampToBounds() - onStatusChanged: if (scaledImg.status == Image.Ready) - cropRect.clampToBounds() CustomText { id: monitorId @@ -177,72 +196,85 @@ Item { text: delegate.modelData.name } - CustomRect { - id: cropRect + Loader { + id: cropRectLoader + active: scaledImg.paintedWidth > 0 && scaledImg.status == Image.Ready + + sourceComponent: Component { + CustomRect { + id: cropRect - property real aspectRatio: delegate.modelData.width / delegate.modelData.height - readonly property real baseHeight: baseWidth / aspectRatio - readonly property real baseWidth: { - let fittedHeight = scaledImg.paintedHeight; - let fittedWidth = fittedHeight * aspectRatio; + property real aspectRatio: delegate.modelData.width / delegate.modelData.height + readonly property real baseHeight: baseWidth / aspectRatio + readonly property real baseWidth: { + let fittedHeight = scaledImg.paintedHeight; + let fittedWidth = fittedHeight * aspectRatio; - if (fittedWidth > scaledImg.paintedWidth) { - fittedWidth = scaledImg.paintedWidth; - fittedHeight = fittedWidth / aspectRatio; - } + if (fittedWidth > scaledImg.paintedWidth) { + fittedWidth = scaledImg.paintedWidth; + fittedHeight = fittedWidth / aspectRatio; + } - return fittedWidth; - } - readonly property real imageX: (scaledImg.width - scaledImg.paintedWidth) / 2 - readonly property real imageY: (scaledImg.height - scaledImg.paintedHeight) / 2 - property real imgAspectRatio: scaledImg.paintedWidth / scaledImg.paintedHeight - property real zoom: scaledImg.displayData.zoom + return fittedWidth; + } + readonly property real imageX: (scaledImg.width - scaledImg.paintedWidth) / 2 + readonly property real imageY: (scaledImg.height - scaledImg.paintedHeight) / 2 + property real imgAspectRatio: scaledImg.paintedWidth / scaledImg.paintedHeight + property real zoom: 1.0 - function centerInImage() { - x = imageX + (scaledImg.paintedWidth - width) / 2; - y = imageY + (scaledImg.paintedHeight - height) / 2; - } + function centerInImage() { + x = imageX + (scaledImg.paintedWidth - width) / 2; + y = imageY + (scaledImg.paintedHeight - height) / 2; + } - function clampToBounds() { - x = Math.max(imageX, Math.min(x, imageX + scaledImg.paintedWidth - width)); + function clampToBounds() { + x = Math.max(imageX, Math.min(x, imageX + scaledImg.paintedWidth - width)); - y = Math.max(imageY, Math.min(y, imageY + scaledImg.paintedHeight - height)); - } + y = Math.max(imageY, Math.min(y, imageY + scaledImg.paintedHeight - height)); + } - function restoreFromData() { - let data = scaledImg.displayData; + function restoreFromData() { + let data = Wallpapers.getCrop(delegate.modelData.name); - if (data && data.scaledX !== 0 || data.scaledY !== 0 || data.scaledWidth !== 0 || data.scaledHeight !== 0) { - x = data.scaledX; - y = data.scaledY; + if (data && (Math.abs(data.x) > 0.001 || Math.abs(data.y) > 0.001 || Math.abs(data.width - 1.0) > 0.001 || Math.abs(data.height - 1.0) > 0.001)) { + zoom = data.zoom > 0 ? data.zoom : 1.0; + x = imageX + (data.x * scaledImg.paintedWidth); + y = imageY + (data.y * scaledImg.paintedHeight); + + clampToBounds(); + } else { + zoom = 1.0; + centerInImage(); + } + } - clampToBounds(); - } else { - zoom = 1.0; - centerInImage(); + border.color: DynamicColors.palette.m3primary + border.width: 2 + height: baseHeight / zoom + opacity: 1 + width: baseWidth / zoom + + Behavior on opacity { + Anim { + } + } + + Component.onCompleted: { + restoreFromData(); + } + onHeightChanged: clampToBounds() + onWidthChanged: clampToBounds() } } - - border.color: DynamicColors.palette.m3primary - border.width: 2 - height: baseHeight / zoom - opacity: 1 - width: baseWidth / zoom - - Behavior on opacity { - Anim { - } - } - - Component.onCompleted: clampToBounds() - onHeightChanged: clampToBounds() - onWidthChanged: clampToBounds() } MouseArea { id: mouse function updateCrop(mouseX, mouseY) { + if (!cropRectLoader.item) return; + const cropRect = cropRectLoader.item; + let nx = mouseX - cropRect.width * 0.5; let ny = mouseY - cropRect.height * 0.5; diff --git a/Modules/Wallpaper/WallBackground-old.qml b/Modules/Wallpaper/WallBackground-old.qml deleted file mode 100644 index b8bb649..0000000 --- a/Modules/Wallpaper/WallBackground-old.qml +++ /dev/null @@ -1,80 +0,0 @@ -pragma ComponentBehavior: Bound - -import QtQuick -import qs.Components -import qs.Helpers -import qs.Config - -Item { - id: root - - property Image current: one - property string source: Wallpapers.current - - anchors.fill: parent - - Component.onCompleted: { - if (source) - Qt.callLater(() => one.update()); - } - onSourceChanged: { - if (!source) { - current = null; - } else if (current === one) { - two.update(); - } else { - one.update(); - } - } - - Img { - id: one - } - - Img { - id: two - } - - component Img: Image { - id: img - - function update(): void { - if (source === root.source) { - root.current = this; - } else { - source = root.source; - } - } - - anchors.fill: parent - asynchronous: true - fillMode: Image.PreserveAspectCrop - opacity: 0 - retainWhileLoading: true - scale: Wallpapers.showPreview ? 1 : 0.8 - sourceClipRect: Qt.rect(Config.background.sourceClipX, Config.background.sourceClipY, Config.background.sourceClipW, Config.background.sourceClipH) - - states: State { - name: "visible" - when: root.current === img - - PropertyChanges { - img.opacity: 1 - img.scale: 1 - } - } - transitions: Transition { - Anim { - duration: Config.background.wallFadeDuration - properties: "opacity,scale" - target: img - } - } - - onStatusChanged: { - if (status === Image.Ready) { - root.current = this; - } - } - } -} diff --git a/Modules/Wallpaper/WallBackground.qml b/Modules/Wallpaper/WallBackground.qml index e6a4675..1c8656a 100644 --- a/Modules/Wallpaper/WallBackground.qml +++ b/Modules/Wallpaper/WallBackground.qml @@ -6,6 +6,7 @@ import QtQuick import qs.Components import qs.Helpers import qs.Config +import ZShell.Internal Item { id: root @@ -15,58 +16,64 @@ Item { function refreshData(): void { Hyprland.refreshMonitors(); - const scale = Hyprland.monitorFor(root.screen).scale; - if (scale > 0 && img.resScale !== scale) { - img.resScale = scale; - img.sourceSize.width = root.screen.width * scale; + let scale = Hyprland.monitorFor(root.screen).scale; + if (scale <= 0) + scale = 1.0; // Fallback to avoid zeroes on initialization + + if (root.screen.width > 0 && root.screen.height > 0) { + img.screenResolution = Qt.size(root.screen.width * scale, root.screen.height * scale); } + const displayData = Wallpapers.getCrop(root.screen.name); - const displayRect = Qt.rect(img.sourceSize.width * displayData.x, img.implicitHeight * displayData.y, img.sourceSize.width * displayData.width, img.implicitHeight * displayData.height); - img.anchors.fill = null; - img.zoom = displayData.zoom; - img.x = -(displayRect.x * displayData.zoom / img.resScale); - img.y = -(displayRect.y * displayData.zoom / img.resScale); + + if (displayData) { + img.cropX = displayData.x !== undefined ? displayData.x : 0.0; + img.cropY = displayData.y !== undefined ? displayData.y : 0.0; + img.cropWidth = (displayData.width !== undefined && displayData.width > 0) ? displayData.width : 1.0; + img.cropHeight = (displayData.height !== undefined && displayData.height > 0) ? displayData.height : 1.0; + } } anchors.fill: parent - Image { + Component.onCompleted: root.refreshData() + + Connections { + function onHeightChanged() { + root.refreshData(); + } + + function onWidthChanged() { + root.refreshData(); + } + + target: root.screen + } + + WallpaperImage { id: img - property int displayH - property int displayW - property real resScale - property real zoom: 1.0 - - asynchronous: true - fillMode: Image.PreserveAspectCrop - height: implicitHeight * zoom / resScale - opacity: 1 - retainWhileLoading: true + anchors.fill: parent source: root.source - sourceSize.width: root.screen.width * resScale - width: implicitWidth * zoom / resScale - Behavior on height { + Behavior on cropHeight { Anim { } } - Behavior on width { + Behavior on cropWidth { Anim { } } - Behavior on x { + Behavior on cropX { Anim { } } - Behavior on y { + Behavior on cropY { Anim { } } - - onStatusChanged: { - if (img.status == Image.Ready) { - root.refreshData(); + Behavior on zoom { + Anim { } } diff --git a/Plugins/ZShell/Internal/CMakeLists.txt b/Plugins/ZShell/Internal/CMakeLists.txt index d49d62f..dd353a3 100644 --- a/Plugins/ZShell/Internal/CMakeLists.txt +++ b/Plugins/ZShell/Internal/CMakeLists.txt @@ -8,6 +8,7 @@ qml_module(ZShell-internal circularbuffer.hpp circularbuffer.cpp sparklineitem.hpp sparklineitem.cpp arcgauge.hpp arcgauge.cpp + wallpaperimage.hpp wallpaperimage.cpp LIBRARIES Qt::Gui Qt::Quick diff --git a/Plugins/ZShell/Internal/arcgauge.cpp b/Plugins/ZShell/Internal/arcgauge.cpp index 1ef0fb7..4f77b50 100644 --- a/Plugins/ZShell/Internal/arcgauge.cpp +++ b/Plugins/ZShell/Internal/arcgauge.cpp @@ -4,7 +4,7 @@ #include #include -namespace caelestia::internal { +namespace ZShell::internal { ArcGauge::ArcGauge(QQuickItem* parent) : QQuickPaintedItem(parent) { @@ -116,4 +116,4 @@ void ArcGauge::setLineWidth(qreal width) { update(); } -} // namespace caelestia::internal +} // namespace ZShell::internal diff --git a/Plugins/ZShell/Internal/arcgauge.hpp b/Plugins/ZShell/Internal/arcgauge.hpp index efa6800..453fd59 100644 --- a/Plugins/ZShell/Internal/arcgauge.hpp +++ b/Plugins/ZShell/Internal/arcgauge.hpp @@ -5,7 +5,7 @@ #include #include -namespace caelestia::internal { +namespace ZShell::internal { class ArcGauge : public QQuickPaintedItem { Q_OBJECT @@ -58,4 +58,4 @@ qreal m_sweepAngle = 1.5 * M_PI; qreal m_lineWidth = 10.0; }; -} // namespace caelestia::internal +} // namespace ZShell::internal diff --git a/Plugins/ZShell/Internal/wallpaperimage.cpp b/Plugins/ZShell/Internal/wallpaperimage.cpp new file mode 100644 index 0000000..ebe27e2 --- /dev/null +++ b/Plugins/ZShell/Internal/wallpaperimage.cpp @@ -0,0 +1,199 @@ +#include "wallpaperimage.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace ZShell::internal { + +WallpaperImage::WallpaperImage(QQuickItem *parent) + : QQuickItem(parent) +{ + setFlag(ItemHasContents, true); + connect(&m_imageWatcher, &QFutureWatcher::finished, this, &WallpaperImage::handleImageLoaded); +} + +WallpaperImage::~WallpaperImage() { + if (m_texture) delete m_texture; +} + +void WallpaperImage::setSource(const QUrl &source) { + if (m_source == source) return; + m_source = source; + emit sourceChanged(); + loadImage(); +} + +void WallpaperImage::setScreenResolution(const QSize &screenResolution) { + if (m_screenResolution == screenResolution) return; + m_screenResolution = screenResolution; + emit screenResolutionChanged(); + loadImage(); +} + +void WallpaperImage::setZoom(qreal zoom) { + if (qFuzzyCompare(m_zoom, zoom)) return; + m_zoom = zoom; + emit zoomChanged(); + update(); +} + +void WallpaperImage::setCropX(qreal x) { + if (qFuzzyCompare(m_cropX, x)) return; + m_cropX = x; + emit cropXChanged(); + update(); +} + +void WallpaperImage::setCropY(qreal y) { + if (qFuzzyCompare(m_cropY, y)) return; + m_cropY = y; + emit cropYChanged(); + update(); +} + +void WallpaperImage::setCropWidth(qreal w) { + if (w <= 0.0) w = 1.0; + if (qFuzzyCompare(m_cropWidth, w)) return; + m_cropWidth = w; + emit cropWidthChanged(); + update(); +} + +void WallpaperImage::setCropHeight(qreal h) { + if (h <= 0.0) h = 1.0; + if (qFuzzyCompare(m_cropHeight, h)) return; + m_cropHeight = h; + emit cropHeightChanged(); + update(); +} + +QString WallpaperImage::getCacheFilePath() const { + if (m_source.isEmpty() || m_screenResolution.isEmpty()) return QString(); + + QString cachePath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + "/zshell/imagecache"; + QDir().mkpath(cachePath); + + // Hash the source URL + resolution + QString id = m_source.toString() + "_" + QString::number(m_screenResolution.width()) + "x" + QString::number(m_screenResolution.height()); + QByteArray hash = QCryptographicHash::hash(id.toUtf8(), QCryptographicHash::Md5).toHex(); + + return cachePath + "/" + hash + ".png"; +} + +void WallpaperImage::loadImage() { + if (m_source.isEmpty()) return; + + QString cacheFile = getCacheFilePath(); + QString sourceFile = m_source.isLocalFile() ? m_source.toLocalFile() : m_source.toString(); + + // Qt resource path correction if passed as a standard URL string + if (sourceFile.startsWith("qrc:/")) { + sourceFile = sourceFile.mid(3); // Converts "qrc:/" to ":/" + } + + QSize targetRes = m_screenResolution; + + // Run off the main thread to avoid blocking the UI + QFuture future = QtConcurrent::run([sourceFile, cacheFile, targetRes]() -> QImage { + if (!targetRes.isEmpty() && !cacheFile.isEmpty() && QFileInfo::exists(cacheFile)) { + QImage cached(cacheFile); + if (!cached.isNull()) return cached; + } + + QImage original(sourceFile); + if (original.isNull()) return QImage(); + + if (targetRes.isEmpty()) { + // Screen resolution not set yet by QML, return the unscaled original for now to prevent a black screen + return original; + } + + // Check if original is strictly larger than screen resolution + if (original.width() > targetRes.width() || original.height() > targetRes.height()) { + QImage scaled = original.scaled(targetRes, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); + if (!cacheFile.isEmpty()) scaled.save(cacheFile, "PNG"); + return scaled; + } + + // Otherwise just cache and return the original + if (!cacheFile.isEmpty()) original.save(cacheFile, "PNG"); + return original; + }); + + m_imageWatcher.setFuture(future); +} + +void WallpaperImage::handleImageLoaded() { + m_image = m_imageWatcher.result(); + m_textureDirty = true; + update(); // Request redraw +} + +QSGNode *WallpaperImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) { + auto *node = static_cast(oldNode); + + if (m_image.isNull()) { + delete node; + return nullptr; + } + + if (!node) { + node = window()->createImageNode(); + } + + if (m_textureDirty) { + if (m_texture) delete m_texture; + m_texture = window()->createTextureFromImage(m_image, QQuickWindow::TextureHasAlphaChannel); + m_textureDirty = false; + } + + if (m_texture) { + node->setTexture(m_texture); + node->setRect(boundingRect()); + node->setFiltering(QSGTexture::Linear); + + qreal cW = m_cropWidth / m_zoom; + qreal cH = m_cropHeight / m_zoom; + + QRectF reqRect( + m_cropX * m_texture->textureSize().width(), + m_cropY * m_texture->textureSize().height(), + cW * m_texture->textureSize().width(), + cH * m_texture->textureSize().height() + ); + + QRectF bounds = boundingRect(); + if (bounds.isEmpty() || reqRect.isEmpty()) return node; + + qreal targetRatio = bounds.width() / bounds.height(); + qreal reqRatio = reqRect.width() / reqRect.height(); + + QRectF sourceRect = reqRect; + + // Force 'PreserveAspectCrop' behavior on the requested region + if (reqRatio > targetRatio) { + // Requested region is too wide, center-crop the sides + qreal newWidth = reqRect.height() * targetRatio; + qreal xOffset = (reqRect.width() - newWidth) / 2.0; + sourceRect.setX(reqRect.x() + xOffset); + sourceRect.setWidth(newWidth); + } else if (reqRatio < targetRatio) { + // Requested region is too tall, center-crop the top/bottom + qreal newHeight = reqRect.width() / targetRatio; + qreal yOffset = (reqRect.height() - newHeight) / 2.0; + sourceRect.setY(reqRect.y() + yOffset); + sourceRect.setHeight(newHeight); + } + + node->setSourceRect(sourceRect); + } + + return node; +} + +} // namespace ZShell::internal diff --git a/Plugins/ZShell/Internal/wallpaperimage.hpp b/Plugins/ZShell/Internal/wallpaperimage.hpp new file mode 100644 index 0000000..04cb4ed --- /dev/null +++ b/Plugins/ZShell/Internal/wallpaperimage.hpp @@ -0,0 +1,95 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace ZShell::internal { + +class WallpaperImage : public QQuickItem { +Q_OBJECT +QML_NAMED_ELEMENT(WallpaperImage) +Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged) +Q_PROPERTY(QSize screenResolution READ screenResolution WRITE setScreenResolution NOTIFY screenResolutionChanged) +Q_PROPERTY(qreal zoom READ zoom WRITE setZoom NOTIFY zoomChanged) + +Q_PROPERTY(qreal cropX READ cropX WRITE setCropX NOTIFY cropXChanged) +Q_PROPERTY(qreal cropY READ cropY WRITE setCropY NOTIFY cropYChanged) +Q_PROPERTY(qreal cropWidth READ cropWidth WRITE setCropWidth NOTIFY cropWidthChanged) +Q_PROPERTY(qreal cropHeight READ cropHeight WRITE setCropHeight NOTIFY cropHeightChanged) + +public: +explicit WallpaperImage(QQuickItem *parent = nullptr); +~WallpaperImage() override; + +QUrl source() const { + return m_source; +} +void setSource(const QUrl &source); + +QSize screenResolution() const { + return m_screenResolution; +} +void setScreenResolution(const QSize &screenResolution); + +qreal zoom() const { + return m_zoom; +} +void setZoom(qreal zoom); + +qreal cropX() const { + return m_cropX; +} +void setCropX(qreal x); + +qreal cropY() const { + return m_cropY; +} +void setCropY(qreal y); + +qreal cropWidth() const { + return m_cropWidth; +} +void setCropWidth(qreal w); + +qreal cropHeight() const { + return m_cropHeight; +} +void setCropHeight(qreal h); + +protected: +QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData) override; + +signals: +void sourceChanged(); +void screenResolutionChanged(); +void zoomChanged(); +void cropXChanged(); +void cropYChanged(); +void cropWidthChanged(); +void cropHeightChanged(); + +private: +void loadImage(); +void handleImageLoaded(); +QString getCacheFilePath() const; + +QUrl m_source; +QSize m_screenResolution; +qreal m_zoom = 1.0; + +qreal m_cropX = 0.0; +qreal m_cropY = 0.0; +qreal m_cropWidth = 1.0; +qreal m_cropHeight = 1.0; + +QImage m_image; +QSGTexture *m_texture = nullptr; +bool m_textureDirty = false; +QFutureWatcher m_imageWatcher; +}; + +} // namespace ZShell::internal From 6d0813089a0f5206229de7090f4aad24347f9362 Mon Sep 17 00:00:00 2001 From: zach Date: Thu, 28 May 2026 16:47:19 +0200 Subject: [PATCH 17/18] fix launcher height calculations --- Drawers/Interactions.qml | 7 +++++++ Modules/Launcher/Wrapper.qml | 9 ++++----- Modules/Resources/Wrapper.qml | 5 ++--- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/Drawers/Interactions.qml b/Drawers/Interactions.qml index 7ba6670..553f52b 100644 --- a/Drawers/Interactions.qml +++ b/Drawers/Interactions.qml @@ -132,6 +132,8 @@ CustomMouseArea { if (!inDashboardArea) { root.dashboardShortcutActive = true; } + if (root.panels.launcher.x + root.panels.launcher.width > root.panels.dashboardWrapper.x) + root.visibilities.launcher = false; root.visibilities.settings = false; root.visibilities.sidebar = false; @@ -161,6 +163,11 @@ CustomMouseArea { } if (root.visibilities.launcher) { + if (root.panels.dashboardWrapper.x < root.panels.launcher.x + root.panels.launcher.width) { + console.log("true"); + root.visibilities.dashboard = false; + } + root.visibilities.dock = false; root.visibilities.settings = false; } diff --git a/Modules/Launcher/Wrapper.qml b/Modules/Launcher/Wrapper.qml index 1e8b6d6..84d18ab 100644 --- a/Modules/Launcher/Wrapper.qml +++ b/Modules/Launcher/Wrapper.qml @@ -12,12 +12,11 @@ Item { property int contentHeight readonly property real maxHeight: { let max = screen.height - Appearance.spacing.large * 2; - if (visibilities.resources) + if (visibilities.resources && panels.resourcesWrapper.x + panels.resourcesWrapper.width > root.x) max -= panels.resources.nonAnimHeight; - if (visibilities.dashboard && panels.dashboard.x < root.x + root.implicitWidth) - max -= panels.dashboard.nonAnimHeight; - if (panels.popouts.currentName.startsWith("updates")) - max -= panels.popouts.nonAnimHeight; + if (panels.popouts.hasCurrent) + if (panels.popouts.current.x + panels.popouts.current.width > root.x && panels.popouts.current.x < root.x + root.width) + max -= panels.popouts.nonAnimHeight; return max; } property real offsetScale: shouldBeActive ? 0 : 1 diff --git a/Modules/Resources/Wrapper.qml b/Modules/Resources/Wrapper.qml index a60f898..7a277f5 100644 --- a/Modules/Resources/Wrapper.qml +++ b/Modules/Resources/Wrapper.qml @@ -8,7 +8,7 @@ import qs.Config Item { id: root - readonly property real nonAnimHeight: state === "visible" ? (content.item?.nonAnimHeight ?? 0) : 0 + readonly property real nonAnimHeight: content.item?.nonAnimHeight ?? 0 property real offsetScale: shouldBeActive ? 0 : 1 readonly property bool shouldBeActive: root.visibilities.resources required property PersistentProperties visibilities @@ -31,8 +31,7 @@ Item { id: content active: root.shouldBeActive || root.visible - anchors.bottom: parent.bottom - anchors.horizontalCenter: parent.horizontalCenter + anchors.centerIn: parent sourceComponent: Content { padding: Appearance.padding.normal From d4a53b06e0fe4162c8601a2275b36de37c59d9f5 Mon Sep 17 00:00:00 2001 From: zach Date: Thu, 28 May 2026 16:53:27 +0200 Subject: [PATCH 18/18] remove obsolete background files --- Drawers/Backgrounds.qml | 107 ------------------- Modules/Background.qml | 68 ------------ Modules/Dashboard/Background.qml | 65 ----------- Modules/Dock/Background.qml | 66 ------------ Modules/Drawing/Background.qml | 66 ------------ Modules/Launcher/Background.qml | 66 ------------ Modules/Notifications/Background.qml | 59 ---------- Modules/Notifications/Sidebar/Background.qml | 54 ---------- Modules/Osd/Background.qml | 66 ------------ Modules/Resources/Background.qml | 65 ----------- Modules/Settings/Background.qml | 66 ------------ 11 files changed, 748 deletions(-) delete mode 100644 Drawers/Backgrounds.qml delete mode 100644 Modules/Background.qml delete mode 100644 Modules/Dashboard/Background.qml delete mode 100644 Modules/Dock/Background.qml delete mode 100644 Modules/Drawing/Background.qml delete mode 100644 Modules/Launcher/Background.qml delete mode 100644 Modules/Notifications/Background.qml delete mode 100644 Modules/Notifications/Sidebar/Background.qml delete mode 100644 Modules/Osd/Background.qml delete mode 100644 Modules/Resources/Background.qml delete mode 100644 Modules/Settings/Background.qml diff --git a/Drawers/Backgrounds.qml b/Drawers/Backgrounds.qml deleted file mode 100644 index 6593cbe..0000000 --- a/Drawers/Backgrounds.qml +++ /dev/null @@ -1,107 +0,0 @@ -import Quickshell -import QtQuick -import QtQuick.Shapes -import qs.Components -import qs.Config -import qs.Modules as Modules -import qs.Modules.Notifications as Notifications -import qs.Modules.Notifications.Sidebar as Sidebar -import qs.Modules.Notifications.Sidebar.Utils as Utils -import qs.Modules.Dashboard as Dashboard -import qs.Modules.Osd as Osd -import qs.Modules.Launcher as Launcher -import qs.Modules.Resources as Resources -import qs.Modules.Drawing as Drawing -import qs.Modules.Settings as Settings -import qs.Modules.Dock as Dock - -Shape { - id: root - - required property Item bar - required property Panels panels - required property PersistentProperties visibilities - - anchors.fill: parent - anchors.margins: Config.barConfig.border - anchors.topMargin: bar.implicitHeight - asynchronous: true - preferredRendererType: Shape.CurveRenderer - - Drawing.Background { - startX: 0 - startY: wrapper.y - rounding - wrapper: root.panels.drawing - } - - Resources.Background { - startX: 0 - rounding - startY: 0 - wrapper: root.panels.resources - } - - Osd.Background { - startX: root.width - root.panels.sidebar.width - startY: (root.height - wrapper.height) / 2 - rounding - wrapper: root.panels.osd - } - - Modules.Background { - invertBottomRounding: wrapper.x <= 0 - rounding: root.panels.popouts.currentName.startsWith("updates") || root.panels.popouts.currentName.startsWith("audio") ? Appearance.rounding.normal : Appearance.rounding.smallest - startX: wrapper.x - rounding - startY: wrapper.y - wrapper: root.panels.popouts - } - - Notifications.Background { - sidebar: sidebar - startX: root.width - startY: 0 - wrapper: root.panels.notifications - } - - Launcher.Background { - startX: (root.width - wrapper.width) / 2 - rounding - startY: root.height - wrapper: root.panels.launcher - } - - Dashboard.Background { - startX: root.width - root.panels.dashboard.width - rounding - startY: 0 - wrapper: root.panels.dashboard - } - - Utils.Background { - sidebar: sidebar - startX: root.width - startY: root.height - wrapper: root.panels.utilities - } - - Sidebar.Background { - id: sidebar - - panels: root.panels - startX: root.width - startY: root.panels.notifications.height - wrapper: root.panels.sidebar - } - - Settings.Background { - id: settings - - startX: (root.width - wrapper.width) / 2 - rounding - startY: 0 - wrapper: root.panels.settings - } - - Dock.Background { - id: dock - - startX: (root.width - wrapper.width) / 2 - rounding - startY: root.height - wrapper: root.panels.dock - } -} diff --git a/Modules/Background.qml b/Modules/Background.qml deleted file mode 100644 index a726d17..0000000 --- a/Modules/Background.qml +++ /dev/null @@ -1,68 +0,0 @@ -import QtQuick -import QtQuick.Shapes -import qs.Components -import qs.Config - -ShapePath { - id: root - - readonly property bool flatten: wrapper.height < rounding * 2 - property real ibr: invertBottomRounding ? -1 : 1 - required property bool invertBottomRounding - property real rounding: Appearance.rounding.smallest - readonly property real roundingY: flatten ? wrapper.height / 2 : rounding - required property Wrapper wrapper - - fillColor: DynamicColors.palette.m3surface - strokeWidth: -1 - - Behavior on fillColor { - CAnim { - } - } - - PathArc { - radiusX: root.rounding - radiusY: Math.min(root.rounding, root.wrapper.height) - relativeX: root.rounding - relativeY: root.roundingY - } - - PathLine { - relativeX: 0 - relativeY: root.wrapper.height - root.roundingY - root.roundingY * root.ibr - } - - PathArc { - direction: root.invertBottomRounding ? PathArc.Clockwise : PathArc.Counterclockwise - radiusX: root.rounding - radiusY: Math.min(root.rounding, root.wrapper.height) - relativeX: root.rounding - relativeY: root.roundingY * root.ibr - } - - PathLine { - relativeX: root.wrapper.width - root.rounding * 2 - relativeY: 0 - } - - PathArc { - direction: PathArc.Counterclockwise - radiusX: root.rounding - radiusY: Math.min(root.rounding, root.wrapper.height) - relativeX: root.rounding - relativeY: -root.roundingY - } - - PathLine { - relativeX: 0 - relativeY: -(root.wrapper.height - root.roundingY * 2) - } - - PathArc { - radiusX: root.rounding - radiusY: Math.min(root.rounding, root.wrapper.height) - relativeX: root.rounding - relativeY: -root.roundingY - } -} diff --git a/Modules/Dashboard/Background.qml b/Modules/Dashboard/Background.qml deleted file mode 100644 index cf42103..0000000 --- a/Modules/Dashboard/Background.qml +++ /dev/null @@ -1,65 +0,0 @@ -import qs.Components -import qs.Config -import QtQuick -import QtQuick.Shapes - -ShapePath { - id: root - - readonly property bool flatten: wrapper.height < rounding * 2 - readonly property real rounding: Appearance.rounding.normal - readonly property real roundingY: flatten ? wrapper.height / 2 : rounding - required property Wrapper wrapper - - fillColor: DynamicColors.palette.m3surface - strokeWidth: -1 - - Behavior on fillColor { - CAnim { - } - } - - PathArc { - radiusX: root.rounding - radiusY: Math.min(root.rounding, root.wrapper.height) - relativeX: root.rounding - relativeY: root.roundingY - } - - PathLine { - relativeX: 0 - relativeY: root.wrapper.height - root.roundingY * 2 - } - - PathArc { - direction: PathArc.Counterclockwise - radiusX: root.rounding - radiusY: Math.min(root.rounding, root.wrapper.height) - relativeX: root.rounding - relativeY: root.roundingY - } - - PathLine { - relativeX: root.wrapper.width - root.rounding * 2 - relativeY: 0 - } - - PathArc { - radiusX: root.rounding - radiusY: Math.min(root.rounding, root.wrapper.height) - relativeX: root.rounding - relativeY: root.roundingY - } - - PathLine { - relativeX: 0 - relativeY: -(root.wrapper.height) - } - - PathArc { - radiusX: root.rounding - radiusY: Math.min(root.rounding, root.wrapper.height) - relativeX: root.rounding - relativeY: -root.roundingY - } -} diff --git a/Modules/Dock/Background.qml b/Modules/Dock/Background.qml deleted file mode 100644 index 37f8c08..0000000 --- a/Modules/Dock/Background.qml +++ /dev/null @@ -1,66 +0,0 @@ -import QtQuick -import QtQuick.Shapes -import qs.Components -import qs.Config - -ShapePath { - id: root - - readonly property bool flatten: wrapper.height < rounding * 2 - readonly property real rounding: Appearance.rounding.normal - readonly property real roundingY: flatten ? wrapper.height / 2 : rounding - required property Wrapper wrapper - - fillColor: DynamicColors.palette.m3surface - strokeWidth: -1 - - Behavior on fillColor { - CAnim { - } - } - - PathArc { - direction: PathArc.Counterclockwise - radiusX: root.rounding - radiusY: Math.min(root.rounding, root.wrapper.height) - relativeX: root.rounding - relativeY: -root.roundingY - } - - PathLine { - relativeX: 0 - relativeY: -(root.wrapper.height - root.roundingY * 2) - } - - PathArc { - radiusX: root.rounding - radiusY: Math.min(root.rounding, root.wrapper.height) - relativeX: root.rounding - relativeY: -root.roundingY - } - - PathLine { - relativeX: root.wrapper.width - root.rounding * 2 - relativeY: 0 - } - - PathArc { - radiusX: root.rounding - radiusY: Math.min(root.rounding, root.wrapper.height) - relativeX: root.rounding - relativeY: root.roundingY - } - - PathLine { - relativeX: 0 - relativeY: root.wrapper.height - root.roundingY * 2 - } - - PathArc { - direction: PathArc.Counterclockwise - radiusX: root.rounding - radiusY: Math.min(root.rounding, root.wrapper.height) - relativeX: root.rounding - relativeY: root.roundingY - } -} diff --git a/Modules/Drawing/Background.qml b/Modules/Drawing/Background.qml deleted file mode 100644 index f19ba9a..0000000 --- a/Modules/Drawing/Background.qml +++ /dev/null @@ -1,66 +0,0 @@ -import QtQuick -import QtQuick.Shapes -import qs.Components -import qs.Config - -ShapePath { - id: root - - readonly property bool flatten: wrapper.width < rounding * 2 - readonly property real rounding: Appearance.rounding.normal - readonly property real roundingX: flatten ? wrapper.width / 2 : rounding - required property Wrapper wrapper - - fillColor: DynamicColors.palette.m3surface - strokeWidth: -1 - - Behavior on fillColor { - CAnim { - } - } - - PathArc { - direction: PathArc.Counterclockwise - radiusX: Math.min(root.rounding, root.wrapper.width) - radiusY: root.rounding - relativeX: root.roundingX - relativeY: root.rounding - } - - PathLine { - relativeX: root.wrapper.width - root.roundingX * 2 - relativeY: 0 - } - - PathArc { - radiusX: Math.min(root.rounding, root.wrapper.width) - radiusY: root.rounding - relativeX: root.roundingX - relativeY: root.rounding - } - - PathLine { - relativeX: 0 - relativeY: root.wrapper.height - root.rounding * 2 - } - - PathArc { - radiusX: Math.min(root.rounding, root.wrapper.width) - radiusY: root.rounding - relativeX: -root.roundingX - relativeY: root.rounding - } - - PathLine { - relativeX: -(root.wrapper.width - root.roundingX * 2) - relativeY: 0 - } - - PathArc { - direction: PathArc.Counterclockwise - radiusX: Math.min(root.rounding, root.wrapper.width) - radiusY: root.rounding - relativeX: -root.roundingX - relativeY: root.rounding - } -} diff --git a/Modules/Launcher/Background.qml b/Modules/Launcher/Background.qml deleted file mode 100644 index cb795d4..0000000 --- a/Modules/Launcher/Background.qml +++ /dev/null @@ -1,66 +0,0 @@ -import QtQuick -import QtQuick.Shapes -import qs.Components -import qs.Config - -ShapePath { - id: root - - readonly property bool flatten: wrapper.height < rounding * 2 - readonly property real rounding: Appearance.rounding.smallest + 5 - readonly property real roundingY: flatten ? wrapper.height / 2 : rounding - required property Wrapper wrapper - - fillColor: DynamicColors.palette.m3surface - strokeWidth: -1 - - Behavior on fillColor { - CAnim { - } - } - - PathArc { - direction: PathArc.Counterclockwise - radiusX: root.rounding - radiusY: Math.min(root.rounding, root.wrapper.height) - relativeX: root.rounding - relativeY: -root.roundingY - } - - PathLine { - relativeX: 0 - relativeY: -(root.wrapper.height - root.roundingY * 2) - } - - PathArc { - radiusX: root.rounding - radiusY: Math.min(root.rounding, root.wrapper.height) - relativeX: root.rounding - relativeY: -root.roundingY - } - - PathLine { - relativeX: root.wrapper.width - root.rounding * 2 - relativeY: 0 - } - - PathArc { - radiusX: root.rounding - radiusY: Math.min(root.rounding, root.wrapper.height) - relativeX: root.rounding - relativeY: root.roundingY - } - - PathLine { - relativeX: 0 - relativeY: root.wrapper.height - root.roundingY * 2 - } - - PathArc { - direction: PathArc.Counterclockwise - radiusX: root.rounding - radiusY: Math.min(root.rounding, root.wrapper.height) - relativeX: root.rounding - relativeY: root.roundingY - } -} diff --git a/Modules/Notifications/Background.qml b/Modules/Notifications/Background.qml deleted file mode 100644 index 9e68e20..0000000 --- a/Modules/Notifications/Background.qml +++ /dev/null @@ -1,59 +0,0 @@ -import qs.Components -import qs.Config -import QtQuick -import QtQuick.Shapes - -ShapePath { - id: root - - readonly property bool flatten: wrapper.height < rounding * 2 - readonly property real rounding: 8 - readonly property real roundingY: flatten ? wrapper.height / 2 : rounding - required property var sidebar - required property Wrapper wrapper - - fillColor: DynamicColors.palette.m3surface - strokeWidth: -1 - - Behavior on fillColor { - CAnim { - } - } - - PathLine { - relativeX: -(root.wrapper.width + root.rounding) - relativeY: 0 - } - - PathArc { - radiusX: root.rounding - radiusY: Math.min(root.rounding, root.wrapper.height) - relativeX: root.rounding - relativeY: root.roundingY - } - - PathLine { - relativeX: 0 - relativeY: root.wrapper.height - root.roundingY * 2 - } - - PathArc { - direction: PathArc.Counterclockwise - radiusX: root.sidebar.notifsRoundingX - radiusY: Math.min(root.rounding, root.wrapper.height) - relativeX: root.sidebar.notifsRoundingX - relativeY: root.roundingY - } - - PathLine { - relativeX: root.wrapper.height > 0 ? root.wrapper.width - root.rounding - root.sidebar.notifsRoundingX : root.wrapper.width - relativeY: 0 - } - - PathArc { - radiusX: root.rounding - radiusY: root.rounding - relativeX: root.rounding - relativeY: root.rounding - } -} diff --git a/Modules/Notifications/Sidebar/Background.qml b/Modules/Notifications/Sidebar/Background.qml deleted file mode 100644 index f31c43d..0000000 --- a/Modules/Notifications/Sidebar/Background.qml +++ /dev/null @@ -1,54 +0,0 @@ -import QtQuick -import QtQuick.Shapes -import qs.Components -import qs.Config - -ShapePath { - id: root - - readonly property bool flatten: wrapper.width < rounding * 2 - readonly property real notifsRoundingX: panels.notifications.height > 0 && notifsWidthDiff < rounding * 2 ? notifsWidthDiff / 2 : rounding - readonly property real notifsWidthDiff: panels.notifications.width - wrapper.width - required property var panels - readonly property real rounding: Config.barConfig.rounding - readonly property real utilsRoundingX: utilsWidthDiff < rounding * 2 ? utilsWidthDiff / 2 : rounding - readonly property real utilsWidthDiff: panels.utilities.width - wrapper.width - required property Wrapper wrapper - - fillColor: DynamicColors.palette.m3surface - strokeWidth: -1 - - Behavior on fillColor { - CAnim { - } - } - - PathLine { - relativeX: -root.wrapper.width - root.notifsRoundingX - relativeY: 0 - } - - PathArc { - radiusX: root.notifsRoundingX - radiusY: root.rounding - relativeX: root.notifsRoundingX - relativeY: root.rounding - } - - PathLine { - relativeX: 0 - relativeY: root.wrapper.height - root.rounding * 2 - } - - PathArc { - radiusX: root.utilsRoundingX - radiusY: root.rounding - relativeX: -root.utilsRoundingX - relativeY: root.rounding - } - - PathLine { - relativeX: root.wrapper.width + root.utilsRoundingX - relativeY: 0 - } -} diff --git a/Modules/Osd/Background.qml b/Modules/Osd/Background.qml deleted file mode 100644 index 441dad3..0000000 --- a/Modules/Osd/Background.qml +++ /dev/null @@ -1,66 +0,0 @@ -import QtQuick -import QtQuick.Shapes -import qs.Components -import qs.Config - -ShapePath { - id: root - - readonly property bool flatten: wrapper.width < rounding * 2 - readonly property real rounding: 10 - readonly property real roundingX: flatten ? wrapper.width / 2 : rounding - required property Wrapper wrapper - - fillColor: DynamicColors.palette.m3surface - strokeWidth: -1 - - Behavior on fillColor { - CAnim { - } - } - - PathArc { - radiusX: Math.min(root.rounding, root.wrapper.width) - radiusY: root.rounding - relativeX: -root.roundingX - relativeY: root.rounding - } - - PathLine { - relativeX: -(root.wrapper.width - root.roundingX * 3) - relativeY: 0 - } - - PathArc { - direction: PathArc.Counterclockwise - radiusX: Math.min(root.rounding * 2, root.wrapper.width) - radiusY: root.rounding * 2 - relativeX: -root.roundingX * 2 - relativeY: root.rounding * 2 - } - - PathLine { - relativeX: 0 - relativeY: root.wrapper.height - root.rounding * 4 - } - - PathArc { - direction: PathArc.Counterclockwise - radiusX: Math.min(root.rounding * 2, root.wrapper.width) - radiusY: root.rounding * 2 - relativeX: root.roundingX * 2 - relativeY: root.rounding * 2 - } - - PathLine { - relativeX: root.wrapper.width - root.roundingX * 3 - relativeY: 0 - } - - PathArc { - radiusX: Math.min(root.rounding, root.wrapper.width) - radiusY: root.rounding - relativeX: root.roundingX - relativeY: root.rounding - } -} diff --git a/Modules/Resources/Background.qml b/Modules/Resources/Background.qml deleted file mode 100644 index 48a7eb8..0000000 --- a/Modules/Resources/Background.qml +++ /dev/null @@ -1,65 +0,0 @@ -import qs.Components -import qs.Config -import QtQuick -import QtQuick.Shapes - -ShapePath { - id: root - - readonly property bool flatten: wrapper.height < rounding * 2 - readonly property real rounding: Appearance.rounding.normal - readonly property real roundingY: flatten ? wrapper.height / 2 : rounding - required property Wrapper wrapper - - fillColor: DynamicColors.palette.m3surface - strokeWidth: -1 - - Behavior on fillColor { - CAnim { - } - } - - PathArc { - radiusX: root.rounding - radiusY: Math.min(root.rounding, root.wrapper.height) - relativeX: root.rounding - relativeY: root.roundingY - } - - PathLine { - relativeX: 0 - relativeY: root.wrapper.height - } - - PathArc { - radiusX: root.rounding - radiusY: Math.min(root.rounding, root.wrapper.height) - relativeX: root.rounding - relativeY: -root.roundingY - } - - PathLine { - relativeX: root.wrapper.width - root.rounding * 2 - relativeY: 0 - } - - PathArc { - direction: PathArc.Counterclockwise - radiusX: root.rounding - radiusY: Math.min(root.rounding, root.wrapper.height) - relativeX: root.rounding - relativeY: -root.roundingY - } - - PathLine { - relativeX: 0 - relativeY: -(root.wrapper.height - root.roundingY * 2) - } - - PathArc { - radiusX: root.rounding - radiusY: Math.min(root.rounding, root.wrapper.height) - relativeX: root.rounding - relativeY: -root.roundingY - } -} diff --git a/Modules/Settings/Background.qml b/Modules/Settings/Background.qml deleted file mode 100644 index 3004b3d..0000000 --- a/Modules/Settings/Background.qml +++ /dev/null @@ -1,66 +0,0 @@ -import QtQuick -import QtQuick.Shapes -import qs.Components -import qs.Config - -ShapePath { - id: root - - readonly property bool flatten: wrapper.height < rounding * 2 - readonly property real rounding: Appearance.rounding.large - readonly property real roundingY: flatten ? wrapper.height / 2 : rounding - required property Wrapper wrapper - - fillColor: DynamicColors.palette.m3surface - strokeWidth: -1 - - Behavior on fillColor { - CAnim { - } - } - - PathArc { - radiusX: root.rounding - radiusY: Math.min(root.roundingY, root.wrapper.height) - relativeX: root.rounding - relativeY: root.roundingY - } - - PathLine { - relativeX: 0 - relativeY: root.wrapper.height - root.roundingY * 2 - } - - PathArc { - direction: PathArc.Counterclockwise - radiusX: root.rounding - radiusY: Math.min(root.rounding, root.wrapper.height) - relativeX: root.rounding - relativeY: root.roundingY - } - - PathLine { - relativeX: root.wrapper.width - root.rounding * 2 - relativeY: 0 - } - - PathArc { - direction: PathArc.Counterclockwise - radiusX: root.rounding - radiusY: Math.min(root.rounding, root.wrapper.height) - relativeX: root.rounding - relativeY: -root.roundingY - } - - PathLine { - relativeX: 0 - relativeY: -(root.wrapper.height - root.roundingY * 2) - } - - PathArc { - radiusX: root.rounding - radiusY: Math.min(root.rounding, root.wrapper.height) - relativeX: root.rounding - relativeY: -root.roundingY - } -}