From f89236f51e9b9517de09d1ace6e03a4619d092fc Mon Sep 17 00:00:00 2001 From: zach Date: Tue, 19 May 2026 03:43:08 +0200 Subject: [PATCH 01/20] update blobs --- Plugins/ZShell/Blobs/CMakeLists.txt | 1 + Plugins/ZShell/Blobs/blobmaterial.cpp | 8 ++++- Plugins/ZShell/Blobs/blobmaterial.hpp | 3 ++ Plugins/ZShell/Blobs/blobshape.cpp | 24 +++++++++++++++ Plugins/ZShell/Blobs/shaders/blob.frag | 41 ++++++++++++++++++++++---- 5 files changed, 71 insertions(+), 6 deletions(-) diff --git a/Plugins/ZShell/Blobs/CMakeLists.txt b/Plugins/ZShell/Blobs/CMakeLists.txt index bf12181..05ec0d9 100644 --- a/Plugins/ZShell/Blobs/CMakeLists.txt +++ b/Plugins/ZShell/Blobs/CMakeLists.txt @@ -12,6 +12,7 @@ qml_module(ZShell-blobs qt_add_shaders(ZShell-blobs "blob_shaders" BATCHABLE OPTIMIZED NOHLSL NOMSL + GLSL "300es,330" PREFIX "/" FILES shaders/blob.frag diff --git a/Plugins/ZShell/Blobs/blobmaterial.cpp b/Plugins/ZShell/Blobs/blobmaterial.cpp index 688315a..8bb85b3 100644 --- a/Plugins/ZShell/Blobs/blobmaterial.cpp +++ b/Plugins/ZShell/Blobs/blobmaterial.cpp @@ -2,6 +2,9 @@ #include +static_assert(sizeof(decltype(BlobRectData::excludeMask)) == sizeof(float), + "BlobMaterial packs excludeMask into a float slot via memcpy"); + QSGMaterialType* BlobMaterial::type() const { static QSGMaterialType s_type; return &s_type; @@ -82,8 +85,11 @@ bool BlobMaterialShader::updateUniformData(RenderState& state, QSGMaterial* newM for (int i = 0; i < count; ++i) { const auto& r = mat->m_rects[i]; const int base = 160 + i * 80; + // Pack excludeMask into props.x via bit-cast (read in shader with floatBitsToInt) + float maskAsFloat; + memcpy(&maskAsFloat, &r.excludeMask, sizeof(float)); const float d0[4] = { r.cx, r.cy, r.hw, r.hh }; - const float d1[4] = { 0.0f, r.offsetX, r.offsetY, r.minEig }; + const float d1[4] = { maskAsFloat, r.offsetX, r.offsetY, r.minEig }; const float d3[4] = { r.screenHalfX, r.screenHalfY, 0.0f, 0.0f }; memcpy(buf->data() + base, d0, 16); memcpy(buf->data() + base + 16, d1, 16); diff --git a/Plugins/ZShell/Blobs/blobmaterial.hpp b/Plugins/ZShell/Blobs/blobmaterial.hpp index 9e3518b..0a11f10 100644 --- a/Plugins/ZShell/Blobs/blobmaterial.hpp +++ b/Plugins/ZShell/Blobs/blobmaterial.hpp @@ -14,6 +14,9 @@ struct BlobRectData { float screenHalfX = 0, screenHalfY = 0; // Effective per-corner radii (tr, br, bl, tl), pre-computed on CPU float radius[4] = { 0, 0, 0, 0 }; + // Bitmask of indices in this rect's m_cachedRects that mutually exclude (or are excluded by) this rect. + // Used by the shader to skip smin between excluded pairs. + int excludeMask = 0; }; class BlobMaterial : public QSGMaterial { diff --git a/Plugins/ZShell/Blobs/blobshape.cpp b/Plugins/ZShell/Blobs/blobshape.cpp index ce05808..85bebf2 100644 --- a/Plugins/ZShell/Blobs/blobshape.cpp +++ b/Plugins/ZShell/Blobs/blobshape.cpp @@ -149,6 +149,10 @@ void BlobShape::updatePolish() { const QRectF myPadded(static_cast(m_cachedPaddedX), static_cast(m_cachedPaddedY), static_cast(m_cachedPaddedW), static_cast(m_cachedPaddedH)); + // Track shape pointers parallel to m_cachedRects for pairwise exclusion lookups + QVector rectShapes; + rectShapes.reserve(m_group->shapes().size()); + for (BlobShape* other : m_group->shapes()) { if (other->isInvertedRect()) continue; @@ -210,12 +214,29 @@ void BlobShape::updatePolish() { r.screenHalfY = std::abs(b) * r.hw + std::abs(d) * r.hh; m_cachedRects.append(r); + rectShapes.append(other); } } if (isInvertedRect()) m_cachedMyIndex = -1; + // Compute pairwise exclude masks. Bit j in entry i is set iff rect i excludes rect j + // or rect j excludes rect i. The shader uses this to avoid smin between excluded pairs. + const auto cachedCount = m_cachedRects.size(); + for (qsizetype i = 0; i < cachedCount; ++i) { + int mask = 0; + BlobShape* si = rectShapes[i]; + for (qsizetype j = 0; j < cachedCount; ++j) { + if (j == i) + continue; + BlobShape* sj = rectShapes[j]; + if (si->isExcluded(sj) || sj->isExcluded(si)) + mask |= (1 << j); + } + m_cachedRects[i].excludeMask = mask; + } + // Cache inverted rect data m_cachedHasInverted = false; m_cachedInvertedRadius = 0; @@ -270,6 +291,7 @@ void BlobShape::updatePolish() { const auto rectCount = m_cachedRects.size(); for (qsizetype i = 0; i < rectCount; ++i) { auto& ri = m_cachedRects[i]; + const int riExcludeMask = ri.excludeMask; float fTr = 1.0f, fBr = 1.0f, fBl = 1.0f, fTl = 1.0f; const float cTrX = ri.cx + ri.hw, cTrY = ri.cy - ri.hh; @@ -280,6 +302,8 @@ void BlobShape::updatePolish() { for (qsizetype j = 0; j < rectCount; ++j) { if (j == i) continue; + if (riExcludeMask & (1 << j)) + continue; const auto& rj = m_cachedRects[j]; fTr = std::min(fTr, cpuSmoothstep(0.0f, smoothFactor, cpuSdBox(cTrX, cTrY, rj.cx, rj.cy, rj.hw, rj.hh))); fBr = std::min(fBr, cpuSmoothstep(0.0f, smoothFactor, cpuSdBox(cBrX, cBrY, rj.cx, rj.cy, rj.hw, rj.hh))); diff --git a/Plugins/ZShell/Blobs/shaders/blob.frag b/Plugins/ZShell/Blobs/shaders/blob.frag index 4f3bd45..ddee3f7 100644 --- a/Plugins/ZShell/Blobs/shaders/blob.frag +++ b/Plugins/ZShell/Blobs/shaders/blob.frag @@ -63,13 +63,17 @@ float smaxSharpA(float a, float b, float k) { void main() { vec2 pixel = vec2(paddedX, paddedY) + qt_TexCoord0 * vec2(paddedW, paddedH); - float mergedSdf = 1e10; + // Phase 1: compute per-rect SDF, track owner. We can't smin yet because + // excluded pairs need to skip the smooth blend, which requires pairwise pass + // below. + float dArr[16]; int owner = -2; float minDist = 1e10; for (int i = 0; i < rectCount; i++) { - vec4 rect = rectData[i * 5]; // cx, cy, hw, hh - vec4 props = rectData[i * 5 + 1]; // radius, offsetX, offsetY, minEig + vec4 rect = rectData[i * 5]; // cx, cy, hw, hh + vec4 props = + rectData[i * 5 + 1]; // excludeMask(int bits), offsetX, offsetY, minEig vec4 invDm = rectData[i * 5 + 2]; // inverse deform matrix vec4 sh = rectData[i * 5 + 3]; // screenHalfX, screenHalfY, 0, 0 vec4 radii = @@ -81,8 +85,10 @@ void main() { // AABB early-out: skip rects far from this pixel vec2 extent = sh.xy + vec2(smoothFactor * 1.5); if (abs(pixel.x - center.x) > extent.x || - abs(pixel.y - center.y) > extent.y) + abs(pixel.y - center.y) > extent.y) { + dArr[i] = 1e10; continue; + } // Apply pre-computed inverse deformation to the evaluation point mat2 invDeform = mat2(invDm.xy, invDm.zw); @@ -138,13 +144,38 @@ void main() { d *= scale; } - mergedSdf = smin(mergedSdf, d, smoothFactor); + dArr[i] = d; if (d < smoothFactor && d < minDist) { minDist = d; owner = i; } } + // Phase 2: hard-min baseline over all rects. + float mergedSdf = 1e10; + for (int i = 0; i < rectCount; i++) { + mergedSdf = min(mergedSdf, dArr[i]); + } + + // Phase 3: pair-wise smin contributions, skipping excluded pairs. Pair smin + // <= min, so taking the min over all non-excluded pair smins gives the + // smoothly-merged SDF. + for (int i = 0; i < rectCount; i++) { + if (dArr[i] >= 1e9) + continue; + int excludeMask = floatBitsToInt(rectData[i * 5 + 1].x); + for (int j = i + 1; j < rectCount; j++) { + if (dArr[j] >= 1e9) + continue; + if ((excludeMask & (1 << j)) != 0) + continue; + // smin only deviates from min within smoothFactor + if (abs(dArr[i] - dArr[j]) >= smoothFactor) + continue; + mergedSdf = min(mergedSdf, smin(dArr[i], dArr[j], smoothFactor)); + } + } + if (hasInverted != 0) { float dOuter = sdBox(pixel, invertedOuter.xy, invertedOuter.zw) - 1.0; float dInner = From 0a84c822d53cda902f339af8d2fb2a0e40b15b30 Mon Sep 17 00:00:00 2001 From: zach Date: Tue, 19 May 2026 03:50:24 +0200 Subject: [PATCH 02/20] update blobs --- Plugins/ZShell/Blobs/blobgroup.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Plugins/ZShell/Blobs/blobgroup.cpp b/Plugins/ZShell/Blobs/blobgroup.cpp index e77cf05..a24a750 100644 --- a/Plugins/ZShell/Blobs/blobgroup.cpp +++ b/Plugins/ZShell/Blobs/blobgroup.cpp @@ -73,7 +73,9 @@ void BlobGroup::markShapeDirty(BlobShape* source) { source->polish(); source->update(); - // Use cached padded rects to find spatial neighbors + // Use actual current geometry (not cached rects) to find spatial neighbors. + // Cached rects can be stale when multiple shapes move simultaneously, + // causing the blob background to desync from panels that changed size. const float pad = static_cast(m_smoothing) * 2.0f; const QRectF srcRect(static_cast(source->m_cachedPaddedX - pad), static_cast(source->m_cachedPaddedY - pad), static_cast(source->m_cachedPaddedW + pad * 2.0f), @@ -82,8 +84,9 @@ void BlobGroup::markShapeDirty(BlobShape* source) { for (auto* shape : std::as_const(m_shapes)) { if (shape == source) continue; - const QRectF otherRect(static_cast(shape->m_cachedPaddedX), static_cast(shape->m_cachedPaddedY), - static_cast(shape->m_cachedPaddedW), static_cast(shape->m_cachedPaddedH)); + const QPointF shapeScene = shape->mapToScene(QPointF(0, 0)); + const QRectF otherRect(shapeScene.x(), shapeScene.y(), + static_cast(shape->width()), static_cast(shape->height())); if (srcRect.intersects(otherRect)) { shape->polish(); shape->update(); From 5eb32fc30c42d3bfd252349d702cc2cafaee36bb Mon Sep 17 00:00:00 2001 From: zach Date: Tue, 19 May 2026 03:53:47 +0200 Subject: [PATCH 03/20] update blobs --- Plugins/ZShell/Blobs/blobgroup.cpp | 39 +++++++++++++++++++++++------- Plugins/ZShell/Blobs/blobgroup.hpp | 2 +- Plugins/ZShell/Blobs/blobshape.cpp | 2 +- 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/Plugins/ZShell/Blobs/blobgroup.cpp b/Plugins/ZShell/Blobs/blobgroup.cpp index a24a750..1ccd85f 100644 --- a/Plugins/ZShell/Blobs/blobgroup.cpp +++ b/Plugins/ZShell/Blobs/blobgroup.cpp @@ -67,27 +67,48 @@ void BlobGroup::markDirty() { } } -void BlobGroup::markShapeDirty(BlobShape* source) { +void BlobGroup::markShapeDirty(BlobShape* source, const QRectF& oldGeometry) { m_physicsUpdated = false; source->polish(); source->update(); - // Use actual current geometry (not cached rects) to find spatial neighbors. - // Cached rects can be stale when multiple shapes move simultaneously, - // causing the blob background to desync from panels that changed size. const float pad = static_cast(m_smoothing) * 2.0f; - const QRectF srcRect(static_cast(source->m_cachedPaddedX - pad), - static_cast(source->m_cachedPaddedY - pad), static_cast(source->m_cachedPaddedW + pad * 2.0f), - static_cast(source->m_cachedPaddedH + pad * 2.0f)); + // Compute the source's dirty rect in scene space using its old position + const QPointF oldScene(oldGeometry.x(), oldGeometry.y()); + const QRectF oldDirtyRect(oldScene.x() - static_cast(pad), + oldScene.y() - static_cast(pad), + static_cast(oldGeometry.width() + pad * 2.0), + static_cast(oldGeometry.height() + pad * 2.0)); + + // Compute the source's dirty rect using its new position (fresh from polish) + const QRectF newDirtyRect(static_cast(source->m_cachedPaddedX - pad), + static_cast(source->m_cachedPaddedY - pad), + static_cast(source->m_cachedPaddedW + pad * 2.0), + static_cast(source->m_cachedPaddedH + pad * 2.0)); + + // Mark shapes near the old position as dirty (prevents ghost blobs after panel moves) for (auto* shape : std::as_const(m_shapes)) { if (shape == source) continue; const QPointF shapeScene = shape->mapToScene(QPointF(0, 0)); - const QRectF otherRect(shapeScene.x(), shapeScene.y(), + const QRectF shapeRect(shapeScene.x(), shapeScene.y(), static_cast(shape->width()), static_cast(shape->height())); - if (srcRect.intersects(otherRect)) { + if (oldDirtyRect.intersects(shapeRect)) { + shape->polish(); + shape->update(); + } + } + + // Mark shapes near the new position as dirty + for (auto* shape : std::as_const(m_shapes)) { + if (shape == source) + continue; + const QPointF shapeScene = shape->mapToScene(QPointF(0, 0)); + const QRectF shapeRect(shapeScene.x(), shapeScene.y(), + static_cast(shape->width()), static_cast(shape->height())); + if (newDirtyRect.intersects(shapeRect)) { shape->polish(); shape->update(); } diff --git a/Plugins/ZShell/Blobs/blobgroup.hpp b/Plugins/ZShell/Blobs/blobgroup.hpp index ce5d933..44d5f87 100644 --- a/Plugins/ZShell/Blobs/blobgroup.hpp +++ b/Plugins/ZShell/Blobs/blobgroup.hpp @@ -45,7 +45,7 @@ BlobInvertedRect* invertedRect() const { } void markDirty(); -void markShapeDirty(BlobShape* source); +void markShapeDirty(BlobShape* source, const QRectF& oldGeometry); void ensurePhysicsUpdated(); signals: diff --git a/Plugins/ZShell/Blobs/blobshape.cpp b/Plugins/ZShell/Blobs/blobshape.cpp index 85bebf2..306a666 100644 --- a/Plugins/ZShell/Blobs/blobshape.cpp +++ b/Plugins/ZShell/Blobs/blobshape.cpp @@ -77,7 +77,7 @@ void BlobShape::geometryChange(const QRectF& newGeometry, const QRectF& oldGeome if (std::abs(m_pendingDx) > 0.5f || std::abs(m_pendingDy) > 0.5f || dw > 0.5 || dh > 0.5) { m_pendingDx = 0; m_pendingDy = 0; - m_group->markShapeDirty(this); + m_group->markShapeDirty(this, oldGeometry); } } } From cb1df5078b282c95aad7fa23d8ab1c96e38d58c7 Mon Sep 17 00:00:00 2001 From: zach Date: Tue, 19 May 2026 03:55:48 +0200 Subject: [PATCH 04/20] revert blobs --- Plugins/ZShell/Blobs/blobgroup.cpp | 40 ++++++------------------------ Plugins/ZShell/Blobs/blobgroup.hpp | 2 +- Plugins/ZShell/Blobs/blobshape.cpp | 2 +- 3 files changed, 10 insertions(+), 34 deletions(-) diff --git a/Plugins/ZShell/Blobs/blobgroup.cpp b/Plugins/ZShell/Blobs/blobgroup.cpp index 1ccd85f..e77cf05 100644 --- a/Plugins/ZShell/Blobs/blobgroup.cpp +++ b/Plugins/ZShell/Blobs/blobgroup.cpp @@ -67,48 +67,24 @@ void BlobGroup::markDirty() { } } -void BlobGroup::markShapeDirty(BlobShape* source, const QRectF& oldGeometry) { +void BlobGroup::markShapeDirty(BlobShape* source) { m_physicsUpdated = false; source->polish(); source->update(); + // Use cached padded rects to find spatial neighbors const float pad = static_cast(m_smoothing) * 2.0f; + const QRectF srcRect(static_cast(source->m_cachedPaddedX - pad), + static_cast(source->m_cachedPaddedY - pad), static_cast(source->m_cachedPaddedW + pad * 2.0f), + static_cast(source->m_cachedPaddedH + pad * 2.0f)); - // Compute the source's dirty rect in scene space using its old position - const QPointF oldScene(oldGeometry.x(), oldGeometry.y()); - const QRectF oldDirtyRect(oldScene.x() - static_cast(pad), - oldScene.y() - static_cast(pad), - static_cast(oldGeometry.width() + pad * 2.0), - static_cast(oldGeometry.height() + pad * 2.0)); - - // Compute the source's dirty rect using its new position (fresh from polish) - const QRectF newDirtyRect(static_cast(source->m_cachedPaddedX - pad), - static_cast(source->m_cachedPaddedY - pad), - static_cast(source->m_cachedPaddedW + pad * 2.0), - static_cast(source->m_cachedPaddedH + pad * 2.0)); - - // Mark shapes near the old position as dirty (prevents ghost blobs after panel moves) for (auto* shape : std::as_const(m_shapes)) { if (shape == source) continue; - const QPointF shapeScene = shape->mapToScene(QPointF(0, 0)); - const QRectF shapeRect(shapeScene.x(), shapeScene.y(), - static_cast(shape->width()), static_cast(shape->height())); - if (oldDirtyRect.intersects(shapeRect)) { - shape->polish(); - shape->update(); - } - } - - // Mark shapes near the new position as dirty - for (auto* shape : std::as_const(m_shapes)) { - if (shape == source) - continue; - const QPointF shapeScene = shape->mapToScene(QPointF(0, 0)); - const QRectF shapeRect(shapeScene.x(), shapeScene.y(), - static_cast(shape->width()), static_cast(shape->height())); - if (newDirtyRect.intersects(shapeRect)) { + const QRectF otherRect(static_cast(shape->m_cachedPaddedX), static_cast(shape->m_cachedPaddedY), + static_cast(shape->m_cachedPaddedW), static_cast(shape->m_cachedPaddedH)); + if (srcRect.intersects(otherRect)) { shape->polish(); shape->update(); } diff --git a/Plugins/ZShell/Blobs/blobgroup.hpp b/Plugins/ZShell/Blobs/blobgroup.hpp index 44d5f87..ce5d933 100644 --- a/Plugins/ZShell/Blobs/blobgroup.hpp +++ b/Plugins/ZShell/Blobs/blobgroup.hpp @@ -45,7 +45,7 @@ BlobInvertedRect* invertedRect() const { } void markDirty(); -void markShapeDirty(BlobShape* source, const QRectF& oldGeometry); +void markShapeDirty(BlobShape* source); void ensurePhysicsUpdated(); signals: diff --git a/Plugins/ZShell/Blobs/blobshape.cpp b/Plugins/ZShell/Blobs/blobshape.cpp index 306a666..85bebf2 100644 --- a/Plugins/ZShell/Blobs/blobshape.cpp +++ b/Plugins/ZShell/Blobs/blobshape.cpp @@ -77,7 +77,7 @@ void BlobShape::geometryChange(const QRectF& newGeometry, const QRectF& oldGeome if (std::abs(m_pendingDx) > 0.5f || std::abs(m_pendingDy) > 0.5f || dw > 0.5 || dh > 0.5) { m_pendingDx = 0; m_pendingDy = 0; - m_group->markShapeDirty(this, oldGeometry); + m_group->markShapeDirty(this); } } } From c060be79e842e6853eedbd6985d4dd9fb09755dc Mon Sep 17 00:00:00 2001 From: zach Date: Tue, 19 May 2026 03:56:56 +0200 Subject: [PATCH 05/20] update blobs --- Plugins/ZShell/Blobs/blobshape.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Plugins/ZShell/Blobs/blobshape.cpp b/Plugins/ZShell/Blobs/blobshape.cpp index 85bebf2..d872714 100644 --- a/Plugins/ZShell/Blobs/blobshape.cpp +++ b/Plugins/ZShell/Blobs/blobshape.cpp @@ -69,6 +69,26 @@ void BlobShape::geometryChange(const QRectF& newGeometry, const QRectF& oldGeome QQuickItem::geometryChange(newGeometry, oldGeometry); updateCenteredDeformMatrix(); if (m_group) { + // Update cached values so markShapeDirty uses current geometry + const QPointF scenePos = mapToScene(QPointF(0, 0)); + if (isInvertedRect()) { + m_cachedPaddedX = static_cast(scenePos.x()); + m_cachedPaddedY = static_cast(scenePos.y()); + m_cachedPaddedW = static_cast(width()); + m_cachedPaddedH = static_cast(height()); + m_localPaddedRect = QRectF(0, 0, width(), height()); + } else { + const float hw = static_cast(width()) * 0.5f; + const float hh = static_cast(height()) * 0.5f; + const float totalPad = static_cast(m_group->smoothing()) + deformPadding(m_deformMatrix, hw, hh); + m_cachedPaddedX = static_cast(scenePos.x()) - totalPad; + m_cachedPaddedY = static_cast(scenePos.y()) - totalPad; + m_cachedPaddedW = static_cast(width()) + 2.0f * totalPad; + m_cachedPaddedH = static_cast(height()) + 2.0f * totalPad; + m_localPaddedRect = QRectF(static_cast(-totalPad), static_cast(-totalPad), + width() + 2.0 * static_cast(totalPad), height() + 2.0 * static_cast(totalPad)); + } + // Accumulate sub-pixel drift so slow movements don't desync the shader m_pendingDx += static_cast(newGeometry.x() - oldGeometry.x()); m_pendingDy += static_cast(newGeometry.y() - oldGeometry.y()); From 3d2fc0a3b124921b513b984f31fa096fa8f40bad Mon Sep 17 00:00:00 2001 From: zach Date: Tue, 19 May 2026 04:02:46 +0200 Subject: [PATCH 06/20] update blobs --- Plugins/ZShell/Blobs/blobgroup.cpp | 2 ++ Plugins/ZShell/Blobs/blobshape.cpp | 48 ++++++++++-------------------- Plugins/ZShell/Blobs/blobshape.hpp | 21 ++++++++++--- 3 files changed, 35 insertions(+), 36 deletions(-) diff --git a/Plugins/ZShell/Blobs/blobgroup.cpp b/Plugins/ZShell/Blobs/blobgroup.cpp index e77cf05..62eef10 100644 --- a/Plugins/ZShell/Blobs/blobgroup.cpp +++ b/Plugins/ZShell/Blobs/blobgroup.cpp @@ -85,6 +85,8 @@ void BlobGroup::markShapeDirty(BlobShape* source) { const QRectF otherRect(static_cast(shape->m_cachedPaddedX), static_cast(shape->m_cachedPaddedY), static_cast(shape->m_cachedPaddedW), static_cast(shape->m_cachedPaddedH)); if (srcRect.intersects(otherRect)) { + shape->setExpandedRect(srcRect); + shape->m_hasExpandedRect = true; shape->polish(); shape->update(); } diff --git a/Plugins/ZShell/Blobs/blobshape.cpp b/Plugins/ZShell/Blobs/blobshape.cpp index d872714..69d9ea6 100644 --- a/Plugins/ZShell/Blobs/blobshape.cpp +++ b/Plugins/ZShell/Blobs/blobshape.cpp @@ -69,26 +69,6 @@ void BlobShape::geometryChange(const QRectF& newGeometry, const QRectF& oldGeome QQuickItem::geometryChange(newGeometry, oldGeometry); updateCenteredDeformMatrix(); if (m_group) { - // Update cached values so markShapeDirty uses current geometry - const QPointF scenePos = mapToScene(QPointF(0, 0)); - if (isInvertedRect()) { - m_cachedPaddedX = static_cast(scenePos.x()); - m_cachedPaddedY = static_cast(scenePos.y()); - m_cachedPaddedW = static_cast(width()); - m_cachedPaddedH = static_cast(height()); - m_localPaddedRect = QRectF(0, 0, width(), height()); - } else { - const float hw = static_cast(width()) * 0.5f; - const float hh = static_cast(height()) * 0.5f; - const float totalPad = static_cast(m_group->smoothing()) + deformPadding(m_deformMatrix, hw, hh); - m_cachedPaddedX = static_cast(scenePos.x()) - totalPad; - m_cachedPaddedY = static_cast(scenePos.y()) - totalPad; - m_cachedPaddedW = static_cast(width()) + 2.0f * totalPad; - m_cachedPaddedH = static_cast(height()) + 2.0f * totalPad; - m_localPaddedRect = QRectF(static_cast(-totalPad), static_cast(-totalPad), - width() + 2.0 * static_cast(totalPad), height() + 2.0 * static_cast(totalPad)); - } - // Accumulate sub-pixel drift so slow movements don't desync the shader m_pendingDx += static_cast(newGeometry.x() - oldGeometry.x()); m_pendingDy += static_cast(newGeometry.y() - oldGeometry.y()); @@ -138,6 +118,8 @@ void BlobShape::updatePolish() { if (!m_group) return; + m_hasExpandedRect = false; + // Ensure all shapes have up-to-date physics (only once per frame) m_group->ensurePhysicsUpdated(); @@ -186,18 +168,20 @@ void BlobShape::updatePolish() { const QPointF otherScene = other->mapToScene(QPointF(0, 0)); - bool include = false; - if (isInvertedRect()) { - include = true; - } else { - const float otherHW = static_cast(other->width()) * 0.5f; - const float otherHH = static_cast(other->height()) * 0.5f; - const float otherPad = pad + deformPadding(other->m_deformMatrix, otherHW, otherHH); - const QRectF otherPadded(otherScene.x() - static_cast(otherPad), - otherScene.y() - static_cast(otherPad), other->width() + 2.0 * static_cast(otherPad), - other->height() + 2.0 * static_cast(otherPad)); - include = myPadded.intersects(otherPadded); - } +bool include = false; + if (isInvertedRect()) { + include = true; + } else if (m_hasExpandedRect) { + include = m_expandedRect.intersects(otherPadded); + } else { + const float otherHW = static_cast(other->width()) * 0.5f; + const float otherHH = static_cast(other->height()) * 0.5f; + const float otherPad = pad + deformPadding(other->m_deformMatrix, otherHW, otherHH); + const QRectF otherPadded(otherScene.x() - static_cast(otherPad), + otherScene.y() - static_cast(otherPad), other->width() + 2.0 * static_cast(otherPad), + other->height() + 2.0 * static_cast(otherPad)); + include = myPadded.intersects(otherPadded); + } if (include) { if (other == this) diff --git a/Plugins/ZShell/Blobs/blobshape.hpp b/Plugins/ZShell/Blobs/blobshape.hpp index aa1d018..02c18c7 100644 --- a/Plugins/ZShell/Blobs/blobshape.hpp +++ b/Plugins/ZShell/Blobs/blobshape.hpp @@ -67,10 +67,20 @@ virtual void updatePhysics() { } virtual void registerWithGroup(); -virtual void unregisterFromGroup(); -void updateCenteredDeformMatrix(); + virtual void unregisterFromGroup(); + void updateCenteredDeformMatrix(); -BlobGroup* m_group = nullptr; + void setExpandedRect(const QRectF& rect) { + m_expandedRect = rect; + } + const QRectF& expandedRect() const { + return m_expandedRect; + } + bool hasExpandedRect() const { + return m_hasExpandedRect; + } + + BlobGroup* m_group = nullptr; qreal m_radius = 0; QMatrix4x4 m_deformMatrix; // identity by default QMatrix4x4 m_centeredDeformMatrix; @@ -88,5 +98,8 @@ float m_pendingDy = 0; bool m_cachedHasInverted = false; float m_cachedInvertedRadius = 0; float m_cachedInvertedOuter[4] = {}; -float m_cachedInvertedInner[4] = {}; + float m_cachedInvertedInner[4] = {}; + + QRectF m_expandedRect; + bool m_hasExpandedRect = false; }; From 8fba953f5207a3c511e84500f94aec1a3a76aa79 Mon Sep 17 00:00:00 2001 From: zach Date: Tue, 19 May 2026 04:04:32 +0200 Subject: [PATCH 07/20] update blobs --- Plugins/ZShell/Blobs/blobshape.cpp | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Plugins/ZShell/Blobs/blobshape.cpp b/Plugins/ZShell/Blobs/blobshape.cpp index 69d9ea6..0265da0 100644 --- a/Plugins/ZShell/Blobs/blobshape.cpp +++ b/Plugins/ZShell/Blobs/blobshape.cpp @@ -166,22 +166,22 @@ void BlobShape::updatePolish() { if (isExcluded(other)) continue; - const QPointF otherScene = other->mapToScene(QPointF(0, 0)); + const QPointF otherScene = other->mapToScene(QPointF(0, 0)); + const float otherHW = static_cast(other->width()) * 0.5f; + const float otherHH = static_cast(other->height()) * 0.5f; + const float otherPad = pad + deformPadding(other->m_deformMatrix, otherHW, otherHH); + const QRectF otherPadded(otherScene.x() - static_cast(otherPad), + otherScene.y() - static_cast(otherPad), other->width() + 2.0 * static_cast(otherPad), + other->height() + 2.0 * static_cast(otherPad)); -bool include = false; - if (isInvertedRect()) { - include = true; - } else if (m_hasExpandedRect) { - include = m_expandedRect.intersects(otherPadded); - } else { - const float otherHW = static_cast(other->width()) * 0.5f; - const float otherHH = static_cast(other->height()) * 0.5f; - const float otherPad = pad + deformPadding(other->m_deformMatrix, otherHW, otherHH); - const QRectF otherPadded(otherScene.x() - static_cast(otherPad), - otherScene.y() - static_cast(otherPad), other->width() + 2.0 * static_cast(otherPad), - other->height() + 2.0 * static_cast(otherPad)); - include = myPadded.intersects(otherPadded); - } + bool include = false; + if (isInvertedRect()) { + include = true; + } else if (m_hasExpandedRect) { + include = m_expandedRect.intersects(otherPadded); + } else { + include = myPadded.intersects(otherPadded); + } if (include) { if (other == this) From 015ee61885d727f016d36c0ca7c231f0ee0bddd5 Mon Sep 17 00:00:00 2001 From: zach Date: Tue, 19 May 2026 04:06:36 +0200 Subject: [PATCH 08/20] revert blobs --- Plugins/ZShell/Blobs/blobgroup.cpp | 2 -- Plugins/ZShell/Blobs/blobshape.cpp | 18 +++++++----------- Plugins/ZShell/Blobs/blobshape.hpp | 21 ++++----------------- 3 files changed, 11 insertions(+), 30 deletions(-) diff --git a/Plugins/ZShell/Blobs/blobgroup.cpp b/Plugins/ZShell/Blobs/blobgroup.cpp index 62eef10..e77cf05 100644 --- a/Plugins/ZShell/Blobs/blobgroup.cpp +++ b/Plugins/ZShell/Blobs/blobgroup.cpp @@ -85,8 +85,6 @@ void BlobGroup::markShapeDirty(BlobShape* source) { const QRectF otherRect(static_cast(shape->m_cachedPaddedX), static_cast(shape->m_cachedPaddedY), static_cast(shape->m_cachedPaddedW), static_cast(shape->m_cachedPaddedH)); if (srcRect.intersects(otherRect)) { - shape->setExpandedRect(srcRect); - shape->m_hasExpandedRect = true; shape->polish(); shape->update(); } diff --git a/Plugins/ZShell/Blobs/blobshape.cpp b/Plugins/ZShell/Blobs/blobshape.cpp index 0265da0..85bebf2 100644 --- a/Plugins/ZShell/Blobs/blobshape.cpp +++ b/Plugins/ZShell/Blobs/blobshape.cpp @@ -118,8 +118,6 @@ void BlobShape::updatePolish() { if (!m_group) return; - m_hasExpandedRect = false; - // Ensure all shapes have up-to-date physics (only once per frame) m_group->ensurePhysicsUpdated(); @@ -166,20 +164,18 @@ void BlobShape::updatePolish() { if (isExcluded(other)) continue; - const QPointF otherScene = other->mapToScene(QPointF(0, 0)); - const float otherHW = static_cast(other->width()) * 0.5f; - const float otherHH = static_cast(other->height()) * 0.5f; - const float otherPad = pad + deformPadding(other->m_deformMatrix, otherHW, otherHH); - const QRectF otherPadded(otherScene.x() - static_cast(otherPad), - otherScene.y() - static_cast(otherPad), other->width() + 2.0 * static_cast(otherPad), - other->height() + 2.0 * static_cast(otherPad)); + const QPointF otherScene = other->mapToScene(QPointF(0, 0)); bool include = false; if (isInvertedRect()) { include = true; - } else if (m_hasExpandedRect) { - include = m_expandedRect.intersects(otherPadded); } else { + const float otherHW = static_cast(other->width()) * 0.5f; + const float otherHH = static_cast(other->height()) * 0.5f; + const float otherPad = pad + deformPadding(other->m_deformMatrix, otherHW, otherHH); + const QRectF otherPadded(otherScene.x() - static_cast(otherPad), + otherScene.y() - static_cast(otherPad), other->width() + 2.0 * static_cast(otherPad), + other->height() + 2.0 * static_cast(otherPad)); include = myPadded.intersects(otherPadded); } diff --git a/Plugins/ZShell/Blobs/blobshape.hpp b/Plugins/ZShell/Blobs/blobshape.hpp index 02c18c7..aa1d018 100644 --- a/Plugins/ZShell/Blobs/blobshape.hpp +++ b/Plugins/ZShell/Blobs/blobshape.hpp @@ -67,20 +67,10 @@ virtual void updatePhysics() { } virtual void registerWithGroup(); - virtual void unregisterFromGroup(); - void updateCenteredDeformMatrix(); +virtual void unregisterFromGroup(); +void updateCenteredDeformMatrix(); - void setExpandedRect(const QRectF& rect) { - m_expandedRect = rect; - } - const QRectF& expandedRect() const { - return m_expandedRect; - } - bool hasExpandedRect() const { - return m_hasExpandedRect; - } - - BlobGroup* m_group = nullptr; +BlobGroup* m_group = nullptr; qreal m_radius = 0; QMatrix4x4 m_deformMatrix; // identity by default QMatrix4x4 m_centeredDeformMatrix; @@ -98,8 +88,5 @@ float m_pendingDy = 0; bool m_cachedHasInverted = false; float m_cachedInvertedRadius = 0; float m_cachedInvertedOuter[4] = {}; - float m_cachedInvertedInner[4] = {}; - - QRectF m_expandedRect; - bool m_hasExpandedRect = false; +float m_cachedInvertedInner[4] = {}; }; From 8c22855dd80342bc944a7eab2ccbf80d18319c79 Mon Sep 17 00:00:00 2001 From: zach Date: Tue, 19 May 2026 04:20:35 +0200 Subject: [PATCH 09/20] update blobs --- Modules/ClipWrapper.qml | 2 +- Modules/Wrapper.qml | 2 +- Plugins/ZShell/Blobs/blobshape.cpp | 20 ++++++++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/Modules/ClipWrapper.qml b/Modules/ClipWrapper.qml index 6b735f5..6770487 100644 --- a/Modules/ClipWrapper.qml +++ b/Modules/ClipWrapper.qml @@ -24,7 +24,7 @@ Item { const diff = parent.width - Math.floor(off + content.nonAnimWidth); if (diff < 0) return off + diff; - return Math.floor(Math.max(off, 0)); + return Math.max(off, 0); } y: content.isDetached ? (parent.height - content.nonAnimHeight) / 2 : 0 diff --git a/Modules/Wrapper.qml b/Modules/Wrapper.qml index 8bf0ab7..0c25227 100644 --- a/Modules/Wrapper.qml +++ b/Modules/Wrapper.qml @@ -15,8 +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 bool isDetached: detachedMode.length > 0 readonly property real nonAnimHeight: children.find(c => c.shouldBeActive)?.implicitHeight ?? content.implicitHeight readonly property real nonAnimWidth: children.find(c => c.shouldBeActive)?.implicitWidth ?? content.implicitWidth required property real offsetScale diff --git a/Plugins/ZShell/Blobs/blobshape.cpp b/Plugins/ZShell/Blobs/blobshape.cpp index 85bebf2..dd33052 100644 --- a/Plugins/ZShell/Blobs/blobshape.cpp +++ b/Plugins/ZShell/Blobs/blobshape.cpp @@ -77,6 +77,26 @@ void BlobShape::geometryChange(const QRectF& newGeometry, const QRectF& oldGeome if (std::abs(m_pendingDx) > 0.5f || std::abs(m_pendingDy) > 0.5f || dw > 0.5 || dh > 0.5) { m_pendingDx = 0; m_pendingDy = 0; + // Update cached padded values immediately so markShapeDirty() uses + // current dimensions for neighbor-finding. The values are also updated + // in updatePolish() later, but that happens after markShapeDirty() + // has already used them to find intersecting shapes. + if (isInvertedRect()) { + const QPointF scenePos = mapToScene(QPointF(0, 0)); + m_cachedPaddedX = static_cast(scenePos.x()); + m_cachedPaddedY = static_cast(scenePos.y()); + m_cachedPaddedW = static_cast(newGeometry.width()); + m_cachedPaddedH = static_cast(newGeometry.height()); + } else { + const QPointF scenePos = mapToScene(QPointF(0, 0)); + const float hw = static_cast(newGeometry.width()) * 0.5f; + const float hh = static_cast(newGeometry.height()) * 0.5f; + const float totalPad = static_cast(m_group->smoothing()) + deformPadding(m_deformMatrix, hw, hh); + m_cachedPaddedX = static_cast(scenePos.x()) - totalPad; + m_cachedPaddedY = static_cast(scenePos.y()) - totalPad; + m_cachedPaddedW = static_cast(newGeometry.width()) + 2.0f * totalPad; + m_cachedPaddedH = static_cast(newGeometry.height()) + 2.0f * totalPad; + } m_group->markShapeDirty(this); } } From 550630feaa73c5b368f8f9c4fc3b573b89c3a555 Mon Sep 17 00:00:00 2001 From: zach Date: Tue, 19 May 2026 04:24:24 +0200 Subject: [PATCH 10/20] Revert "update blobs" This reverts commit 8c22855dd80342bc944a7eab2ccbf80d18319c79. --- Modules/ClipWrapper.qml | 2 +- Modules/Wrapper.qml | 2 +- Plugins/ZShell/Blobs/blobshape.cpp | 20 -------------------- 3 files changed, 2 insertions(+), 22 deletions(-) diff --git a/Modules/ClipWrapper.qml b/Modules/ClipWrapper.qml index 6770487..6b735f5 100644 --- a/Modules/ClipWrapper.qml +++ b/Modules/ClipWrapper.qml @@ -24,7 +24,7 @@ Item { const diff = parent.width - Math.floor(off + content.nonAnimWidth); if (diff < 0) return off + diff; - return Math.max(off, 0); + return Math.floor(Math.max(off, 0)); } y: content.isDetached ? (parent.height - content.nonAnimHeight) / 2 : 0 diff --git a/Modules/Wrapper.qml b/Modules/Wrapper.qml index 0c25227..8bf0ab7 100644 --- a/Modules/Wrapper.qml +++ b/Modules/Wrapper.qml @@ -15,8 +15,8 @@ Item { property real currentCenter property alias currentName: popoutState.currentName property string detachedMode - property alias hasCurrent: popoutState.hasCurrent 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 nonAnimWidth: children.find(c => c.shouldBeActive)?.implicitWidth ?? content.implicitWidth required property real offsetScale diff --git a/Plugins/ZShell/Blobs/blobshape.cpp b/Plugins/ZShell/Blobs/blobshape.cpp index dd33052..85bebf2 100644 --- a/Plugins/ZShell/Blobs/blobshape.cpp +++ b/Plugins/ZShell/Blobs/blobshape.cpp @@ -77,26 +77,6 @@ void BlobShape::geometryChange(const QRectF& newGeometry, const QRectF& oldGeome if (std::abs(m_pendingDx) > 0.5f || std::abs(m_pendingDy) > 0.5f || dw > 0.5 || dh > 0.5) { m_pendingDx = 0; m_pendingDy = 0; - // Update cached padded values immediately so markShapeDirty() uses - // current dimensions for neighbor-finding. The values are also updated - // in updatePolish() later, but that happens after markShapeDirty() - // has already used them to find intersecting shapes. - if (isInvertedRect()) { - const QPointF scenePos = mapToScene(QPointF(0, 0)); - m_cachedPaddedX = static_cast(scenePos.x()); - m_cachedPaddedY = static_cast(scenePos.y()); - m_cachedPaddedW = static_cast(newGeometry.width()); - m_cachedPaddedH = static_cast(newGeometry.height()); - } else { - const QPointF scenePos = mapToScene(QPointF(0, 0)); - const float hw = static_cast(newGeometry.width()) * 0.5f; - const float hh = static_cast(newGeometry.height()) * 0.5f; - const float totalPad = static_cast(m_group->smoothing()) + deformPadding(m_deformMatrix, hw, hh); - m_cachedPaddedX = static_cast(scenePos.x()) - totalPad; - m_cachedPaddedY = static_cast(scenePos.y()) - totalPad; - m_cachedPaddedW = static_cast(newGeometry.width()) + 2.0f * totalPad; - m_cachedPaddedH = static_cast(newGeometry.height()) + 2.0f * totalPad; - } m_group->markShapeDirty(this); } } From d0e696c681d5c84fe1b131ee820f51c0569a8674 Mon Sep 17 00:00:00 2001 From: zach Date: Tue, 19 May 2026 04:52:28 +0200 Subject: [PATCH 11/20] update blobs --- Plugins/ZShell/Blobs/blobshape.cpp | 12 +++++++++--- Plugins/ZShell/Blobs/blobshape.hpp | 6 ++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Plugins/ZShell/Blobs/blobshape.cpp b/Plugins/ZShell/Blobs/blobshape.cpp index 85bebf2..7bcef78 100644 --- a/Plugins/ZShell/Blobs/blobshape.cpp +++ b/Plugins/ZShell/Blobs/blobshape.cpp @@ -72,11 +72,17 @@ void BlobShape::geometryChange(const QRectF& newGeometry, const QRectF& oldGeome // Accumulate sub-pixel drift so slow movements don't desync the shader m_pendingDx += static_cast(newGeometry.x() - oldGeometry.x()); m_pendingDy += static_cast(newGeometry.y() - oldGeometry.y()); - const auto dw = std::abs(newGeometry.width() - oldGeometry.width()); - const auto dh = std::abs(newGeometry.height() - oldGeometry.height()); - if (std::abs(m_pendingDx) > 0.5f || std::abs(m_pendingDy) > 0.5f || dw > 0.5 || dh > 0.5) { + // Accumulate size delta across multiple frames so incremental size + // changes that are each below the threshold still trigger a dirty + // mark once their accumulated delta exceeds it. + m_pendingDw += static_cast(newGeometry.width() - oldGeometry.width()); + m_pendingDh += static_cast(newGeometry.height() - oldGeometry.height()); + if (std::abs(m_pendingDx) > 0.5f || std::abs(m_pendingDy) > 0.5f || + std::abs(m_pendingDw) > 0.5f || std::abs(m_pendingDh) > 0.5f) { m_pendingDx = 0; m_pendingDy = 0; + m_pendingDw = 0; + m_pendingDh = 0; m_group->markShapeDirty(this); } } diff --git a/Plugins/ZShell/Blobs/blobshape.hpp b/Plugins/ZShell/Blobs/blobshape.hpp index aa1d018..cfb60f4 100644 --- a/Plugins/ZShell/Blobs/blobshape.hpp +++ b/Plugins/ZShell/Blobs/blobshape.hpp @@ -84,8 +84,10 @@ QRectF m_localPaddedRect; QVector m_cachedRects; int m_cachedMyIndex = -2; float m_pendingDx = 0; -float m_pendingDy = 0; -bool m_cachedHasInverted = false; + float m_pendingDy = 0; + float m_pendingDw = 0; + float m_pendingDh = 0; + bool m_cachedHasInverted = false; float m_cachedInvertedRadius = 0; float m_cachedInvertedOuter[4] = {}; float m_cachedInvertedInner[4] = {}; From 3bd9444e2f17ad3269d39ce289b921a105d5d2fb Mon Sep 17 00:00:00 2001 From: zach Date: Tue, 19 May 2026 07:09:39 +0200 Subject: [PATCH 12/20] fix settings background desync --- Drawers/Panels.qml | 23 +++++++++--- Drawers/Windows.qml | 2 + Modules/Settings/Wrapper.qml | 71 +++++++++++++++++++++--------------- 3 files changed, 60 insertions(+), 36 deletions(-) diff --git a/Drawers/Panels.qml b/Drawers/Panels.qml index 8902ccf..5c84747 100644 --- a/Drawers/Panels.qml +++ b/Drawers/Panels.qml @@ -34,6 +34,7 @@ Item { readonly property alias resourcesWrapper: resourcesWrapper required property ShellScreen screen readonly property alias settings: settings + readonly property alias settingsWrapper: settingsWrapper readonly property alias sidebar: sidebar readonly property alias toasts: toasts readonly property alias utilities: utilities @@ -176,15 +177,25 @@ Item { visibilities: root.visibilities } - Settings.Wrapper { - id: settings + Item { + id: settingsWrapper anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top - // anchors.centerIn: parent - panels: root - screen: root.screen - visibilities: root.visibilities + clip: true + implicitHeight: settings.implicitHeight * (1 - settings.offsetScale) + implicitWidth: settings.implicitWidth + + Settings.Wrapper { + id: settings + + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + // anchors.centerIn: parent + panels: root + screen: root.screen + visibilities: root.visibilities + } } Dock.Wrapper { diff --git a/Drawers/Windows.qml b/Drawers/Windows.qml index 957b21f..464ccbb 100644 --- a/Drawers/Windows.qml +++ b/Drawers/Windows.qml @@ -272,6 +272,8 @@ Variants { radius: Appearance.rounding.large topLeftRadius: Appearance.rounding.large + Appearance.padding.smaller topRightRadius: Appearance.rounding.large + Appearance.padding.smaller + x: panels.settingsWrapper.x + panels.settings.x + Config.barConfig.border + y: panels.settingsWrapper.y + panels.settings.y + bar.implicitHeight } PanelBg { diff --git a/Modules/Settings/Wrapper.qml b/Modules/Settings/Wrapper.qml index dbcd141..77427bc 100644 --- a/Modules/Settings/Wrapper.qml +++ b/Modules/Settings/Wrapper.qml @@ -7,45 +7,56 @@ import qs.Helpers Item { id: root + property real offsetScale: shouldBeActive ? 0 : 1 required property var panels required property ShellScreen screen + readonly property bool shouldBeActive: visibilities.settings required property PersistentProperties visibilities - implicitHeight: 0 + anchors.topMargin: (-implicitHeight - 5) * offsetScale + implicitHeight: content.implicitHeight implicitWidth: content.implicitWidth - visible: height > 0 + opacity: 1 - offsetScale + visible: offsetScale < 1 - states: State { - name: "visible" - when: root.visibilities.settings - - PropertyChanges { - root.implicitHeight: content.implicitHeight + Behavior on offsetScale { + Anim { + duration: Appearance.anim.durations.expressiveDefaultSpatial + easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial } } - transitions: [ - Transition { - from: "" - to: "visible" - Anim { - duration: MaterialEasing.expressiveEffectsTime - easing.bezierCurve: MaterialEasing.expressiveEffects - property: "implicitHeight" - target: root - } - }, - Transition { - from: "visible" - to: "" - - Anim { - easing.bezierCurve: MaterialEasing.expressiveEffects - property: "implicitHeight" - target: root - } - } - ] + // states: State { + // name: "visible" + // when: root.visibilities.settings + // + // PropertyChanges { + // root.implicitHeight: content.implicitHeight + // } + // } + // transitions: [ + // Transition { + // from: "" + // to: "visible" + // + // Anim { + // duration: MaterialEasing.expressiveEffectsTime + // easing.bezierCurve: MaterialEasing.expressiveEffects + // property: "implicitHeight" + // target: root + // } + // }, + // Transition { + // from: "visible" + // to: "" + // + // Anim { + // easing.bezierCurve: MaterialEasing.expressiveEffects + // property: "implicitHeight" + // target: root + // } + // } + // ] CustomClippingRect { anchors.fill: parent From db7a822caf523bb782c0324b282e0e699ad105f7 Mon Sep 17 00:00:00 2001 From: zach Date: Tue, 19 May 2026 08:24:05 +0200 Subject: [PATCH 13/20] fix some backgrounds, now attaching to wrappers rather than panel itself most of the time --- Config/BarConfig.qml | 1 + Config/Config.qml | 1 + Drawers/Windows.qml | 47 +- Modules/Settings/Categories/Bar.qml | 22 +- Modules/Settings/SettingsIndex.mjs | 2387 ++++++++++++++------------- Modules/Settings/Wrapper.qml | 32 - 6 files changed, 1249 insertions(+), 1241 deletions(-) diff --git a/Config/BarConfig.qml b/Config/BarConfig.qml index a57ed14..a9d2be1 100644 --- a/Config/BarConfig.qml +++ b/Config/BarConfig.qml @@ -58,6 +58,7 @@ JsonObject { property Popouts popouts: Popouts { } property int rounding: 8 + property int smoothing: 32 component Popouts: JsonObject { property bool activeWindow: true diff --git a/Config/Config.qml b/Config/Config.qml index 37eb4c0..da951f4 100644 --- a/Config/Config.qml +++ b/Config/Config.qml @@ -94,6 +94,7 @@ Singleton { hideWhenNotif: barConfig.hideWhenNotif, rounding: barConfig.rounding, border: barConfig.border, + smoothing: barConfig.smoothing, height: barConfig.height, popouts: { tray: barConfig.popouts.tray, diff --git a/Drawers/Windows.qml b/Drawers/Windows.qml index 464ccbb..8e892ee 100644 --- a/Drawers/Windows.qml +++ b/Drawers/Windows.qml @@ -158,6 +158,7 @@ Variants { id: blobGroup color: DynamicColors.palette.m3surface + smoothing: Config.barConfig.smoothing Behavior on color { CAnim { @@ -179,28 +180,34 @@ Variants { PanelBg { id: dashBg - deformAmount: 0.08 * Config.appearance.deform.scale - implicitHeight: panels.dashboard.height + property real extraHeight: 0.2 + + deformAmount: 0.08 + implicitHeight: panels.dashboard.height * (1 + extraHeight) implicitWidth: panels.dashboard.width - panel: panels.dashboard + panel: panels.dashboardWrapper radius: Appearance.rounding.normal x: panels.dashboardWrapper.x + panels.dashboard.x + Config.barConfig.border - y: panels.dashboardWrapper.y + panels.dashboard.y + bar.implicitHeight + y: panels.dashboardWrapper.y + panels.dashboard.y + bar.implicitHeight - panels.dashboard.height * extraHeight } PanelBg { id: launcherBg - deformAmount: 0.08 * Config.appearance.deform.scale + property real extraHeight: 0.2 + + deformAmount: 0.08 + implicitHeight: panels.launcher.height * (1 + extraHeight) panel: panels.launcher radius: Appearance.rounding.smallest + 5 + y: panels.launcher.y + bar.implicitHeight } PanelBg { id: sidebarBg bottomLeftRadius: 0 - deformAmount: 0.08 * Config.appearance.deform.scale + deformAmount: 0.08 exclude: panels.sidebar.offsetScale > 0.08 ? [] : [utilsBg] implicitHeight: panel.height * (1 / rawDeformMatrix.m22) + 2 panel: panels.sidebar @@ -209,10 +216,10 @@ Variants { PanelBg { id: osdBg - deformAmount: 0.1 * Config.appearance.deform.scale + deformAmount: 0.1 implicitHeight: panels.osd.height implicitWidth: panels.osd.width - panel: panels.osd + panel: panels.osdWrapper radius: 20 x: panels.osdWrapper.x + panels.osd.x + Config.barConfig.border y: panels.osdWrapper.y + panels.osd.y + bar.implicitHeight @@ -227,7 +234,7 @@ Variants { PanelBg { id: utilsBg - deformAmount: panels.sidebar.visible ? (0.1 * Config.appearance.deform.scale) : (0.1 * Config.appearance.deform.scale) + deformAmount: panels.sidebar.visible ? (0.1) : (0.1) exclude: panels.sidebar.offsetScale > 0.08 ? [] : [sidebarBg] panel: panels.utilities topLeftRadius: 0 @@ -238,10 +245,10 @@ Variants { property real extraHeight: panels.popouts.isDetached ? 0 : 0.2 - deformAmount: panels.popouts.isDetached ? 0.05 * Config.appearance.deform.scale : panels.popouts.hasCurrent ? 0.15 * Config.appearance.deform.scale : 0.1 * Config.appearance.deform.scale + deformAmount: panels.popouts.isDetached ? 0.05 : panels.popouts.hasCurrent ? 0.15 : 0.1 implicitHeight: panels.popouts.height * (1 + extraHeight) implicitWidth: panels.popouts.width - panel: panels.popouts + panel: panels.popoutsWrapper radius: (panels.popouts.currentName.startsWith("audio") || panels.popouts.currentName.startsWith("updates")) ? Appearance.rounding.normal : 20 * Appearance.rounding.scale x: panels.popoutsWrapper.x + panels.popouts.x + Config.barConfig.border y: panels.popoutsWrapper.y + panels.popouts.y + bar.implicitHeight - panels.popouts.height * extraHeight @@ -255,10 +262,10 @@ Variants { PanelBg { id: resourcesBg - deformAmount: 0.08 * Config.appearance.deform.scale + deformAmount: 0.08 implicitHeight: panels.resources.height implicitWidth: panels.resources.width - panel: panels.resources + panel: panels.resourcesWrapper radius: Appearance.rounding.normal x: panels.resourcesWrapper.x + panels.resources.x + Config.barConfig.border y: panels.resourcesWrapper.y + panels.resources.y + bar.implicitHeight @@ -267,19 +274,23 @@ Variants { PanelBg { id: settingsBg - deformAmount: 0.08 * Config.appearance.deform.scale + property real extraHeight: 0.2 + + deformAmount: 0.08 + implicitHeight: panels.settings.height * (1 + extraHeight) + implicitWidth: panels.settings.width panel: panels.settings radius: Appearance.rounding.large topLeftRadius: Appearance.rounding.large + Appearance.padding.smaller topRightRadius: Appearance.rounding.large + Appearance.padding.smaller x: panels.settingsWrapper.x + panels.settings.x + Config.barConfig.border - y: panels.settingsWrapper.y + panels.settings.y + bar.implicitHeight + y: panels.settingsWrapper.y + panels.settings.y + bar.implicitHeight - panels.settings.height * extraHeight } PanelBg { id: dockBg - deformAmount: 0.08 * Config.appearance.deform.scale + deformAmount: 0.08 panel: panels.dock radius: Appearance.rounding.normal } @@ -287,7 +298,7 @@ Variants { PanelBg { id: drawingBg - deformAmount: 0.08 * Config.appearance.deform.scale + deformAmount: 0.08 panel: panels.drawing radius: Appearance.rounding.normal } @@ -382,7 +393,7 @@ Variants { property real deformAmount: 0.15 required property Item panel - deformScale: deformAmount / 10000 + deformScale: (deformAmount * Config.appearance.deform.scale) / 10000 group: blobGroup implicitHeight: panel.height implicitWidth: panel.width diff --git a/Modules/Settings/Categories/Bar.qml b/Modules/Settings/Categories/Bar.qml index 7ebd5f3..b7afaab 100644 --- a/Modules/Settings/Categories/Bar.qml +++ b/Modules/Settings/Categories/Bar.qml @@ -19,8 +19,8 @@ SettingsPage { } SettingSpinBox { - name: "Height" min: 1 + name: "Height" object: Config.barConfig setting: "height" } @@ -29,8 +29,8 @@ SettingsPage { } SettingSpinBox { - name: "Rounding" min: 0 + name: "Rounding" object: Config.barConfig setting: "rounding" } @@ -39,11 +39,21 @@ SettingsPage { } SettingSpinBox { - name: "Border" min: 0 + name: "Border" object: Config.barConfig setting: "border" } + + Separator { + } + + SettingSpinBox { + min: 0 + name: "Smoothing" + object: Config.barConfig + setting: "smoothing" + } } SettingsSection { @@ -145,8 +155,8 @@ SettingsPage { } SettingSpinBox { - name: "Dock height" min: 1 + name: "Dock height" object: Config.dock setting: "height" } @@ -173,8 +183,8 @@ SettingsPage { } SettingStringList { - name: "Pinned apps" addLabel: qsTr("Add pinned app") + name: "Pinned apps" object: Config.dock setting: "pinnedApps" } @@ -183,8 +193,8 @@ SettingsPage { } SettingStringList { - name: "Ignored app regexes" addLabel: qsTr("Add ignored regex") + name: "Ignored app regexes" object: Config.dock setting: "ignoredAppRegexes" } diff --git a/Modules/Settings/SettingsIndex.mjs b/Modules/Settings/SettingsIndex.mjs index 8ba4b6c..06a2006 100644 --- a/Modules/Settings/SettingsIndex.mjs +++ b/Modules/Settings/SettingsIndex.mjs @@ -7,1217 +7,1234 @@ // - keywords: Additional search terms for better discoverability export const settingsIndex = [ - // GENERAL CATEGORY - // General section - { - name: "Logo", - category: "general", - categoryName: "General", - section: "General", - keywords: ["branding", "icon", "image"], - }, - { - name: "Wallpaper path", - category: "general", - categoryName: "General", - section: "General", - keywords: ["background", "image", "path"], - }, - { - name: "Desktop icons", - category: "general", - categoryName: "General", - section: "General", - keywords: ["icons", "desktop", "show"], - }, - { - name: "Date format", - category: "general", - categoryName: "General", - section: "General", - keywords: ["date", "time", "format"], - }, - // Color section - { - name: "Scheme mode", - category: "general", - categoryName: "General", - section: "Color", - keywords: ["dark mode", "light mode", "theme", "color scheme"], - }, - { - name: "Scheme type", - category: "general", - categoryName: "General", - section: "Color", - keywords: [ - "vibrant", - "expressive", - "monochrome", - "neutral", - "tonal", - "fidelity", - "content", - "rainbow", - "fruit salad", - ], - }, - { - name: "Automatic color scheme", - category: "general", - categoryName: "General", - section: "Color", - keywords: ["auto", "wallpaper", "generate", "color"], - }, - { - name: "Smart color scheme", - category: "general", - categoryName: "General", - section: "Color", - keywords: ["intelligent", "adaptive", "color"], - }, - { - name: "Schedule dark mode", - category: "general", - categoryName: "General", - section: "Color", - keywords: ["dark mode", "night", "time", "schedule", "automatic"], - }, - { - name: "Schedule Hyprsunset", - category: "general", - categoryName: "General", - section: "Color", - keywords: ["night light", "blue light", "temperature", "warm", "schedule"], - }, - // Default Apps section - { - name: "Terminal", - category: "general", - categoryName: "General", - section: "Default Apps", - keywords: ["console", "command", "shell", "default app"], - }, - { - name: "Audio", - category: "general", - categoryName: "General", - section: "Default Apps", - keywords: ["sound", "volume", "mixer", "default app"], - }, - { - name: "Playback", - category: "general", - categoryName: "General", - section: "Default Apps", - keywords: ["media", "player", "music", "video", "default app"], - }, - { - name: "Explorer", - category: "general", - categoryName: "General", - section: "Default Apps", - keywords: ["file manager", "files", "browser", "default app"], - }, + // GENERAL CATEGORY + // General section + { + name: "Logo", + category: "general", + categoryName: "General", + section: "General", + keywords: ["branding", "icon", "image"], + }, + { + name: "Wallpaper path", + category: "general", + categoryName: "General", + section: "General", + keywords: ["background", "image", "path"], + }, + { + name: "Desktop icons", + category: "general", + categoryName: "General", + section: "General", + keywords: ["icons", "desktop", "show"], + }, + { + name: "Date format", + category: "general", + categoryName: "General", + section: "General", + keywords: ["date", "time", "format"], + }, + // Color section + { + name: "Scheme mode", + category: "general", + categoryName: "General", + section: "Color", + keywords: ["dark mode", "light mode", "theme", "color scheme"], + }, + { + name: "Scheme type", + category: "general", + categoryName: "General", + section: "Color", + keywords: [ + "vibrant", + "expressive", + "monochrome", + "neutral", + "tonal", + "fidelity", + "content", + "rainbow", + "fruit salad", + ], + }, + { + name: "Automatic color scheme", + category: "general", + categoryName: "General", + section: "Color", + keywords: ["auto", "wallpaper", "generate", "color"], + }, + { + name: "Smart color scheme", + category: "general", + categoryName: "General", + section: "Color", + keywords: ["intelligent", "adaptive", "color"], + }, + { + name: "Schedule dark mode", + category: "general", + categoryName: "General", + section: "Color", + keywords: ["dark mode", "night", "time", "schedule", "automatic"], + }, + { + name: "Schedule Hyprsunset", + category: "general", + categoryName: "General", + section: "Color", + keywords: [ + "night light", + "blue light", + "temperature", + "warm", + "schedule", + ], + }, + // Default Apps section + { + name: "Terminal", + category: "general", + categoryName: "General", + section: "Default Apps", + keywords: ["console", "command", "shell", "default app"], + }, + { + name: "Audio", + category: "general", + categoryName: "General", + section: "Default Apps", + keywords: ["sound", "volume", "mixer", "default app"], + }, + { + name: "Playback", + category: "general", + categoryName: "General", + section: "Default Apps", + keywords: ["media", "player", "music", "video", "default app"], + }, + { + name: "Explorer", + category: "general", + categoryName: "General", + section: "Default Apps", + keywords: ["file manager", "files", "browser", "default app"], + }, - // WALLPAPER CATEGORY - { - name: "Enable wallpaper rendering", - category: "wallpaper", - categoryName: "Wallpaper", - section: "Wallpaper", - keywords: ["background", "enable", "show"], - }, - { - name: "Fade duration", - category: "wallpaper", - categoryName: "Wallpaper", - section: "Wallpaper", - keywords: ["transition", "animation", "crossfade"], - }, + // WALLPAPER CATEGORY + { + name: "Enable wallpaper rendering", + category: "wallpaper", + categoryName: "Wallpaper", + section: "Wallpaper", + keywords: ["background", "enable", "show"], + }, + { + name: "Fade duration", + category: "wallpaper", + categoryName: "Wallpaper", + section: "Wallpaper", + keywords: ["transition", "animation", "crossfade"], + }, - // BAR CATEGORY - // Bar section - { - name: "Auto hide", - category: "bar", - categoryName: "Bar", - section: "Bar", - keywords: ["hide", "visibility", "autohide", "panel"], - }, - { - name: "Height", - category: "bar", - categoryName: "Bar", - section: "Bar", - keywords: ["size", "panel", "thickness"], - }, - { - name: "Rounding", - category: "bar", - categoryName: "Bar", - section: "Bar", - keywords: ["corners", "radius", "rounded"], - }, - { - name: "Border", - category: "bar", - categoryName: "Bar", - section: "Bar", - keywords: ["outline", "stroke", "edge"], - }, - // Popouts section - { - name: "Tray", - category: "bar", - categoryName: "Bar", - section: "Popouts", - keywords: ["system tray", "icons", "popout"], - }, - { - name: "Audio", - category: "bar", - categoryName: "Bar", - section: "Popouts", - keywords: ["sound", "volume", "popout"], - }, - { - name: "Active window", - category: "bar", - categoryName: "Bar", - section: "Popouts", - keywords: ["window title", "current", "popout"], - }, - { - name: "Resources", - category: "bar", - categoryName: "Bar", - section: "Popouts", - keywords: ["cpu", "memory", "usage", "popout"], - }, - { - name: "Clock", - category: "bar", - categoryName: "Bar", - section: "Popouts", - keywords: ["time", "date", "popout"], - }, - { - name: "Network", - category: "bar", - categoryName: "Bar", - section: "Popouts", - keywords: ["wifi", "internet", "connection", "popout"], - }, - { - name: "Power", - category: "bar", - categoryName: "Bar", - section: "Popouts", - keywords: ["battery", "upower", "charging", "popout"], - }, - // Entries section - { - name: "Bar entries", - category: "bar", - categoryName: "Bar", - section: "Entries", - keywords: ["modules", "widgets", "items", "order"], - }, - // Dock section - { - name: "Enable dock", - category: "bar", - categoryName: "Bar", - section: "Dock", - keywords: ["taskbar", "show", "visibility"], - }, - { - name: "Dock height", - category: "bar", - categoryName: "Bar", - section: "Dock", - keywords: ["size", "taskbar", "thickness"], - }, - { - name: "Hover to reveal", - category: "bar", - categoryName: "Bar", - section: "Dock", - keywords: ["autohide", "mouse", "show"], - }, - { - name: "Pin on startup", - category: "bar", - categoryName: "Bar", - section: "Dock", - keywords: ["always visible", "pinned", "show"], - }, - { - name: "Pinned apps", - category: "bar", - categoryName: "Bar", - section: "Dock", - keywords: ["favorites", "applications", "shortcuts"], - }, - { - name: "Ignored app regexes", - category: "bar", - categoryName: "Bar", - section: "Dock", - keywords: ["filter", "exclude", "hide", "regex"], - }, + // BAR CATEGORY + // Bar section + { + name: "Auto hide", + category: "bar", + categoryName: "Bar", + section: "Bar", + keywords: ["hide", "visibility", "autohide", "panel"], + }, + { + name: "Height", + category: "bar", + categoryName: "Bar", + section: "Bar", + keywords: ["size", "panel", "thickness"], + }, + { + name: "Rounding", + category: "bar", + categoryName: "Bar", + section: "Bar", + keywords: ["corners", "radius", "rounded"], + }, + { + name: "Border", + category: "bar", + categoryName: "Bar", + section: "Bar", + keywords: ["outline", "stroke", "edge"], + }, - // LOCKSCREEN CATEGORY - { - name: "Recolor logo", - category: "lockscreen", - categoryName: "Lockscreen", - section: "Lockscreen", - keywords: ["logo", "color", "tint"], - }, - { - name: "Enable fingerprint", - category: "lockscreen", - categoryName: "Lockscreen", - section: "Lockscreen", - keywords: ["fprint", "biometric", "unlock"], - }, - { - name: "Max fingerprint tries", - category: "lockscreen", - categoryName: "Lockscreen", - section: "Lockscreen", - keywords: ["attempts", "limit", "fprint"], - }, - { - name: "Blur amount", - category: "lockscreen", - categoryName: "Lockscreen", - section: "Lockscreen", - keywords: ["background", "blur", "effect"], - }, - { - name: "Height multiplier", - category: "lockscreen", - categoryName: "Lockscreen", - section: "Lockscreen", - keywords: ["size", "scale", "height"], - }, - { - name: "Aspect ratio", - category: "lockscreen", - categoryName: "Lockscreen", - section: "Lockscreen", - keywords: ["ratio", "width", "height"], - }, - { - name: "Center width", - category: "lockscreen", - categoryName: "Lockscreen", - section: "Lockscreen", - keywords: ["size", "width", "center"], - }, - { - name: "Idle Monitors", - category: "lockscreen", - categoryName: "Lockscreen", - section: "Idle", - keywords: ["timeout", "sleep", "idle", "lock", "suspend"], - }, + { + name: "Smoothing", + category: "bar", + categoryName: "Bar", + section: "Bar", + keywords: ["smoothing", "rounding"], + }, + // Popouts section + { + name: "Tray", + category: "bar", + categoryName: "Bar", + section: "Popouts", + keywords: ["system tray", "icons", "popout"], + }, + { + name: "Audio", + category: "bar", + categoryName: "Bar", + section: "Popouts", + keywords: ["sound", "volume", "popout"], + }, + { + name: "Active window", + category: "bar", + categoryName: "Bar", + section: "Popouts", + keywords: ["window title", "current", "popout"], + }, + { + name: "Resources", + category: "bar", + categoryName: "Bar", + section: "Popouts", + keywords: ["cpu", "memory", "usage", "popout"], + }, + { + name: "Clock", + category: "bar", + categoryName: "Bar", + section: "Popouts", + keywords: ["time", "date", "popout"], + }, + { + name: "Network", + category: "bar", + categoryName: "Bar", + section: "Popouts", + keywords: ["wifi", "internet", "connection", "popout"], + }, + { + name: "Power", + category: "bar", + categoryName: "Bar", + section: "Popouts", + keywords: ["battery", "upower", "charging", "popout"], + }, + // Entries section + { + name: "Bar entries", + category: "bar", + categoryName: "Bar", + section: "Entries", + keywords: ["modules", "widgets", "items", "order"], + }, + // Dock section + { + name: "Enable dock", + category: "bar", + categoryName: "Bar", + section: "Dock", + keywords: ["taskbar", "show", "visibility"], + }, + { + name: "Dock height", + category: "bar", + categoryName: "Bar", + section: "Dock", + keywords: ["size", "taskbar", "thickness"], + }, + { + name: "Hover to reveal", + category: "bar", + categoryName: "Bar", + section: "Dock", + keywords: ["autohide", "mouse", "show"], + }, + { + name: "Pin on startup", + category: "bar", + categoryName: "Bar", + section: "Dock", + keywords: ["always visible", "pinned", "show"], + }, + { + name: "Pinned apps", + category: "bar", + categoryName: "Bar", + section: "Dock", + keywords: ["favorites", "applications", "shortcuts"], + }, + { + name: "Ignored app regexes", + category: "bar", + categoryName: "Bar", + section: "Dock", + keywords: ["filter", "exclude", "hide", "regex"], + }, - // SERVICES CATEGORY - // Services section - { - name: "Weather location", - category: "services", - categoryName: "Services", - section: "Services", - keywords: ["city", "location", "weather"], - }, - { - name: "Use Fahrenheit", - category: "services", - categoryName: "Services", - section: "Services", - keywords: ["temperature", "celsius", "units"], - }, - { - name: "Use twelve hour clock", - category: "services", - categoryName: "Services", - section: "Services", - keywords: ["time", "format", "12h", "24h", "am pm"], - }, - { - name: "Enable ddcutil service", - category: "services", - categoryName: "Services", - section: "Services", - keywords: ["monitor", "brightness", "ddc"], - }, - { - name: "GPU type", - category: "services", - categoryName: "Services", - section: "Services", - keywords: ["graphics", "nvidia", "amd", "intel"], - }, - // Media section - { - name: "Audio increment", - category: "services", - categoryName: "Services", - section: "Media", - keywords: ["volume", "step", "increment"], - }, - { - name: "Brightness increment", - category: "services", - categoryName: "Services", - section: "Media", - keywords: ["screen", "step", "increment"], - }, - { - name: "Max volume", - category: "services", - categoryName: "Services", - section: "Media", - keywords: ["audio", "limit", "maximum"], - }, - { - name: "Default player", - category: "services", - categoryName: "Services", - section: "Media", - keywords: ["music", "media", "mpris"], - }, - { - name: "Visualizer bars", - category: "services", - categoryName: "Services", - section: "Media", - keywords: ["audio", "visualization", "cava"], - }, - { - name: "Player aliases", - category: "services", - categoryName: "Services", - section: "Media", - keywords: ["mpris", "rename", "alias"], - }, + // LOCKSCREEN CATEGORY + { + name: "Recolor logo", + category: "lockscreen", + categoryName: "Lockscreen", + section: "Lockscreen", + keywords: ["logo", "color", "tint"], + }, + { + name: "Enable fingerprint", + category: "lockscreen", + categoryName: "Lockscreen", + section: "Lockscreen", + keywords: ["fprint", "biometric", "unlock"], + }, + { + name: "Max fingerprint tries", + category: "lockscreen", + categoryName: "Lockscreen", + section: "Lockscreen", + keywords: ["attempts", "limit", "fprint"], + }, + { + name: "Blur amount", + category: "lockscreen", + categoryName: "Lockscreen", + section: "Lockscreen", + keywords: ["background", "blur", "effect"], + }, + { + name: "Height multiplier", + category: "lockscreen", + categoryName: "Lockscreen", + section: "Lockscreen", + keywords: ["size", "scale", "height"], + }, + { + name: "Aspect ratio", + category: "lockscreen", + categoryName: "Lockscreen", + section: "Lockscreen", + keywords: ["ratio", "width", "height"], + }, + { + name: "Center width", + category: "lockscreen", + categoryName: "Lockscreen", + section: "Lockscreen", + keywords: ["size", "width", "center"], + }, + { + name: "Idle Monitors", + category: "lockscreen", + categoryName: "Lockscreen", + section: "Idle", + keywords: ["timeout", "sleep", "idle", "lock", "suspend"], + }, - // NOTIFICATIONS CATEGORY - // Notifications section - { - name: "Expire notifications", - category: "notifications", - categoryName: "Notifications", - section: "Notifications", - keywords: ["auto dismiss", "timeout", "expire"], - }, - { - name: "Default expire timeout", - category: "notifications", - categoryName: "Notifications", - section: "Notifications", - keywords: ["duration", "time", "dismiss"], - }, - { - name: "App notification cooldown", - category: "notifications", - categoryName: "Notifications", - section: "Notifications", - keywords: ["rate limit", "spam", "cooldown"], - }, - { - name: "Clear threshold", - category: "notifications", - categoryName: "Notifications", - section: "Notifications", - keywords: ["swipe", "dismiss", "threshold"], - }, - { - name: "Expand threshold", - category: "notifications", - categoryName: "Notifications", - section: "Notifications", - keywords: ["swipe", "expand", "threshold"], - }, - { - name: "Action on click", - category: "notifications", - categoryName: "Notifications", - section: "Notifications", - keywords: ["click", "action", "default"], - }, - { - name: "Group preview count", - category: "notifications", - categoryName: "Notifications", - section: "Notifications", - keywords: ["group", "stack", "preview"], - }, - // Sizes section - { - name: "Width", - category: "notifications", - categoryName: "Notifications", - section: "Sizes", - keywords: ["notification", "size", "width"], - }, - { - name: "Image size", - category: "notifications", - categoryName: "Notifications", - section: "Sizes", - keywords: ["notification", "image", "icon"], - }, - { - name: "Badge size", - category: "notifications", - categoryName: "Notifications", - section: "Sizes", - keywords: ["notification", "badge", "app icon"], - }, + // SERVICES CATEGORY + // Services section + { + name: "Weather location", + category: "services", + categoryName: "Services", + section: "Services", + keywords: ["city", "location", "weather"], + }, + { + name: "Use Fahrenheit", + category: "services", + categoryName: "Services", + section: "Services", + keywords: ["temperature", "celsius", "units"], + }, + { + name: "Use twelve hour clock", + category: "services", + categoryName: "Services", + section: "Services", + keywords: ["time", "format", "12h", "24h", "am pm"], + }, + { + name: "Enable ddcutil service", + category: "services", + categoryName: "Services", + section: "Services", + keywords: ["monitor", "brightness", "ddc"], + }, + { + name: "GPU type", + category: "services", + categoryName: "Services", + section: "Services", + keywords: ["graphics", "nvidia", "amd", "intel"], + }, + // Media section + { + name: "Audio increment", + category: "services", + categoryName: "Services", + section: "Media", + keywords: ["volume", "step", "increment"], + }, + { + name: "Brightness increment", + category: "services", + categoryName: "Services", + section: "Media", + keywords: ["screen", "step", "increment"], + }, + { + name: "Max volume", + category: "services", + categoryName: "Services", + section: "Media", + keywords: ["audio", "limit", "maximum"], + }, + { + name: "Default player", + category: "services", + categoryName: "Services", + section: "Media", + keywords: ["music", "media", "mpris"], + }, + { + name: "Visualizer bars", + category: "services", + categoryName: "Services", + section: "Media", + keywords: ["audio", "visualization", "cava"], + }, + { + name: "Player aliases", + category: "services", + categoryName: "Services", + section: "Media", + keywords: ["mpris", "rename", "alias"], + }, - // SIDEBAR CATEGORY - { - name: "Enable sidebar", - category: "sidebar", - categoryName: "Sidebar", - section: "Sidebar", - keywords: ["show", "panel", "side"], - }, - { - name: "Width", - category: "sidebar", - categoryName: "Sidebar", - section: "Sidebar", - keywords: ["size", "panel", "width"], - }, + // NOTIFICATIONS CATEGORY + // Notifications section + { + name: "Expire notifications", + category: "notifications", + categoryName: "Notifications", + section: "Notifications", + keywords: ["auto dismiss", "timeout", "expire"], + }, + { + name: "Default expire timeout", + category: "notifications", + categoryName: "Notifications", + section: "Notifications", + keywords: ["duration", "time", "dismiss"], + }, + { + name: "App notification cooldown", + category: "notifications", + categoryName: "Notifications", + section: "Notifications", + keywords: ["rate limit", "spam", "cooldown"], + }, + { + name: "Clear threshold", + category: "notifications", + categoryName: "Notifications", + section: "Notifications", + keywords: ["swipe", "dismiss", "threshold"], + }, + { + name: "Expand threshold", + category: "notifications", + categoryName: "Notifications", + section: "Notifications", + keywords: ["swipe", "expand", "threshold"], + }, + { + name: "Action on click", + category: "notifications", + categoryName: "Notifications", + section: "Notifications", + keywords: ["click", "action", "default"], + }, + { + name: "Group preview count", + category: "notifications", + categoryName: "Notifications", + section: "Notifications", + keywords: ["group", "stack", "preview"], + }, + // Sizes section + { + name: "Width", + category: "notifications", + categoryName: "Notifications", + section: "Sizes", + keywords: ["notification", "size", "width"], + }, + { + name: "Image size", + category: "notifications", + categoryName: "Notifications", + section: "Sizes", + keywords: ["notification", "image", "icon"], + }, + { + name: "Badge size", + category: "notifications", + categoryName: "Notifications", + section: "Sizes", + keywords: ["notification", "badge", "app icon"], + }, - // UTILITIES CATEGORY - // Utilities section - { - name: "Enable utilities", - category: "utilities", - categoryName: "Utilities", - section: "Utilities", - keywords: ["show", "enable"], - }, - { - name: "Max toasts", - category: "utilities", - categoryName: "Utilities", - section: "Utilities", - keywords: ["notifications", "limit", "maximum"], - }, - { - name: "Panel width", - category: "utilities", - categoryName: "Utilities", - section: "Utilities", - keywords: ["size", "width"], - }, - { - name: "Toast width", - category: "utilities", - categoryName: "Utilities", - section: "Utilities", - keywords: ["notification", "size", "width"], - }, - // Toasts section - { - name: "Config loaded", - category: "utilities", - categoryName: "Utilities", - section: "Toasts", - keywords: ["toast", "notification", "config"], - }, - { - name: "Charging changed", - category: "utilities", - categoryName: "Utilities", - section: "Toasts", - keywords: ["toast", "notification", "battery", "power"], - }, - { - name: "Game mode changed", - category: "utilities", - categoryName: "Utilities", - section: "Toasts", - keywords: ["toast", "notification", "gaming"], - }, - { - name: "Do not disturb changed", - category: "utilities", - categoryName: "Utilities", - section: "Toasts", - keywords: ["toast", "notification", "dnd", "quiet"], - }, - { - name: "Audio output changed", - category: "utilities", - categoryName: "Utilities", - section: "Toasts", - keywords: ["toast", "notification", "speaker", "headphones"], - }, - { - name: "Audio input changed", - category: "utilities", - categoryName: "Utilities", - section: "Toasts", - keywords: ["toast", "notification", "microphone"], - }, - { - name: "Caps lock changed", - category: "utilities", - categoryName: "Utilities", - section: "Toasts", - keywords: ["toast", "notification", "keyboard"], - }, - { - name: "Num lock changed", - category: "utilities", - categoryName: "Utilities", - section: "Toasts", - keywords: ["toast", "notification", "keyboard"], - }, - { - name: "Keyboard layout changed", - category: "utilities", - categoryName: "Utilities", - section: "Toasts", - keywords: ["toast", "notification", "language", "input"], - }, - { - name: "VPN changed", - category: "utilities", - categoryName: "Utilities", - section: "Toasts", - keywords: ["toast", "notification", "vpn", "network"], - }, - { - name: "Now playing", - category: "utilities", - categoryName: "Utilities", - section: "Toasts", - keywords: ["toast", "notification", "music", "media"], - }, - // VPN section - { - name: "Enable VPN integration", - category: "utilities", - categoryName: "Utilities", - section: "VPN", - keywords: ["vpn", "network", "connection"], - }, - { - name: "Provider", - category: "utilities", - categoryName: "Utilities", - section: "VPN", - keywords: ["vpn", "service", "provider"], - }, + // SIDEBAR CATEGORY + { + name: "Enable sidebar", + category: "sidebar", + categoryName: "Sidebar", + section: "Sidebar", + keywords: ["show", "panel", "side"], + }, + { + name: "Width", + category: "sidebar", + categoryName: "Sidebar", + section: "Sidebar", + keywords: ["size", "panel", "width"], + }, - // DASHBOARD CATEGORY - // Dashboard section - { - name: "Enable dashboard", - category: "dashboard", - categoryName: "Dashboard", - section: "Dashboard", - keywords: ["show", "enable", "panel"], - }, - { - name: "Media update interval", - category: "dashboard", - categoryName: "Dashboard", - section: "Dashboard", - keywords: ["refresh", "interval", "music"], - }, - { - name: "Resource update interval", - category: "dashboard", - categoryName: "Dashboard", - section: "Dashboard", - keywords: ["refresh", "interval", "cpu", "memory"], - }, - { - name: "Drag threshold", - category: "dashboard", - categoryName: "Dashboard", - section: "Dashboard", - keywords: ["swipe", "gesture", "threshold"], - }, - // Performance section - { - name: "Show battery", - category: "dashboard", - categoryName: "Dashboard", - section: "Performance", - keywords: ["power", "battery", "visibility"], - }, - { - name: "Show GPU", - category: "dashboard", - categoryName: "Dashboard", - section: "Performance", - keywords: ["graphics", "gpu", "visibility"], - }, - { - name: "Show CPU", - category: "dashboard", - categoryName: "Dashboard", - section: "Performance", - keywords: ["processor", "cpu", "visibility"], - }, - { - name: "Show memory", - category: "dashboard", - categoryName: "Dashboard", - section: "Performance", - keywords: ["ram", "memory", "visibility"], - }, - { - name: "Show storage", - category: "dashboard", - categoryName: "Dashboard", - section: "Performance", - keywords: ["disk", "storage", "visibility"], - }, - { - name: "Show network", - category: "dashboard", - categoryName: "Dashboard", - section: "Performance", - keywords: ["internet", "network", "visibility"], - }, - // Layout Sizes section (read-only) - { - name: "Tab indicator height", - category: "dashboard", - categoryName: "Dashboard", - section: "Layout Sizes", - keywords: ["size", "tab", "indicator"], - }, - { - name: "Tab indicator spacing", - category: "dashboard", - categoryName: "Dashboard", - section: "Layout Sizes", - keywords: ["size", "tab", "spacing"], - }, - { - name: "Info width", - category: "dashboard", - categoryName: "Dashboard", - section: "Layout Sizes", - keywords: ["size", "info", "width"], - }, - { - name: "Info icon size", - category: "dashboard", - categoryName: "Dashboard", - section: "Layout Sizes", - keywords: ["size", "icon"], - }, - { - name: "Date time width", - category: "dashboard", - categoryName: "Dashboard", - section: "Layout Sizes", - keywords: ["size", "clock", "date"], - }, - { - name: "Media width", - category: "dashboard", - categoryName: "Dashboard", - section: "Layout Sizes", - keywords: ["size", "music", "player"], - }, - { - name: "Media progress sweep", - category: "dashboard", - categoryName: "Dashboard", - section: "Layout Sizes", - keywords: ["size", "progress", "arc"], - }, - { - name: "Media progress thickness", - category: "dashboard", - categoryName: "Dashboard", - section: "Layout Sizes", - keywords: ["size", "progress", "stroke"], - }, - { - name: "Resource progress thickness", - category: "dashboard", - categoryName: "Dashboard", - section: "Layout Sizes", - keywords: ["size", "progress", "stroke"], - }, - { - name: "Weather width", - category: "dashboard", - categoryName: "Dashboard", - section: "Layout Sizes", - keywords: ["size", "weather"], - }, - { - name: "Media cover art size", - category: "dashboard", - categoryName: "Dashboard", - section: "Layout Sizes", - keywords: ["size", "album", "artwork"], - }, - { - name: "Media visualiser size", - category: "dashboard", - categoryName: "Dashboard", - section: "Layout Sizes", - keywords: ["size", "visualizer", "cava"], - }, - { - name: "Resource size", - category: "dashboard", - categoryName: "Dashboard", - section: "Layout Sizes", - keywords: ["size", "cpu", "memory"], - }, + // UTILITIES CATEGORY + // Utilities section + { + name: "Enable utilities", + category: "utilities", + categoryName: "Utilities", + section: "Utilities", + keywords: ["show", "enable"], + }, + { + name: "Max toasts", + category: "utilities", + categoryName: "Utilities", + section: "Utilities", + keywords: ["notifications", "limit", "maximum"], + }, + { + name: "Panel width", + category: "utilities", + categoryName: "Utilities", + section: "Utilities", + keywords: ["size", "width"], + }, + { + name: "Toast width", + category: "utilities", + categoryName: "Utilities", + section: "Utilities", + keywords: ["notification", "size", "width"], + }, + // Toasts section + { + name: "Config loaded", + category: "utilities", + categoryName: "Utilities", + section: "Toasts", + keywords: ["toast", "notification", "config"], + }, + { + name: "Charging changed", + category: "utilities", + categoryName: "Utilities", + section: "Toasts", + keywords: ["toast", "notification", "battery", "power"], + }, + { + name: "Game mode changed", + category: "utilities", + categoryName: "Utilities", + section: "Toasts", + keywords: ["toast", "notification", "gaming"], + }, + { + name: "Do not disturb changed", + category: "utilities", + categoryName: "Utilities", + section: "Toasts", + keywords: ["toast", "notification", "dnd", "quiet"], + }, + { + name: "Audio output changed", + category: "utilities", + categoryName: "Utilities", + section: "Toasts", + keywords: ["toast", "notification", "speaker", "headphones"], + }, + { + name: "Audio input changed", + category: "utilities", + categoryName: "Utilities", + section: "Toasts", + keywords: ["toast", "notification", "microphone"], + }, + { + name: "Caps lock changed", + category: "utilities", + categoryName: "Utilities", + section: "Toasts", + keywords: ["toast", "notification", "keyboard"], + }, + { + name: "Num lock changed", + category: "utilities", + categoryName: "Utilities", + section: "Toasts", + keywords: ["toast", "notification", "keyboard"], + }, + { + name: "Keyboard layout changed", + category: "utilities", + categoryName: "Utilities", + section: "Toasts", + keywords: ["toast", "notification", "language", "input"], + }, + { + name: "VPN changed", + category: "utilities", + categoryName: "Utilities", + section: "Toasts", + keywords: ["toast", "notification", "vpn", "network"], + }, + { + name: "Now playing", + category: "utilities", + categoryName: "Utilities", + section: "Toasts", + keywords: ["toast", "notification", "music", "media"], + }, + // VPN section + { + name: "Enable VPN integration", + category: "utilities", + categoryName: "Utilities", + section: "VPN", + keywords: ["vpn", "network", "connection"], + }, + { + name: "Provider", + category: "utilities", + categoryName: "Utilities", + section: "VPN", + keywords: ["vpn", "service", "provider"], + }, - // APPEARANCE CATEGORY - // Scale section - { - name: "Rounding scale", - category: "appearance", - categoryName: "Appearance", - section: "Scale", - keywords: ["corners", "radius", "scale"], - }, - { - name: "Spacing scale", - category: "appearance", - categoryName: "Appearance", - section: "Scale", - keywords: ["gap", "margin", "scale"], - }, - { - name: "Padding scale", - category: "appearance", - categoryName: "Appearance", - section: "Scale", - keywords: ["padding", "inset", "scale"], - }, - { - name: "Font size scale", - category: "appearance", - categoryName: "Appearance", - section: "Scale", - keywords: ["text", "font", "scale"], - }, - { - name: "Animation duration scale", - category: "appearance", - categoryName: "Appearance", - section: "Scale", - keywords: ["animation", "speed", "duration", "scale"], - }, - { - name: "Deform animation scale", - category: "appearance", - categoryName: "Appearance", - section: "Scale", - keywords: ["animation", "deform", "scale"], - }, - // Fonts section - { - name: "Sans family", - category: "appearance", - categoryName: "Appearance", - section: "Fonts", - keywords: ["font", "typeface", "sans-serif"], - }, - { - name: "Monospace family", - category: "appearance", - categoryName: "Appearance", - section: "Fonts", - keywords: ["font", "typeface", "mono", "code"], - }, - { - name: "Material family", - category: "appearance", - categoryName: "Appearance", - section: "Fonts", - keywords: ["font", "icons", "material"], - }, - { - name: "Clock family", - category: "appearance", - categoryName: "Appearance", - section: "Fonts", - keywords: ["font", "time", "clock"], - }, - // Animation section - { - name: "Media GIF speed adjustment", - category: "appearance", - categoryName: "Appearance", - section: "Animation", - keywords: ["gif", "speed", "animation"], - }, - { - name: "Session GIF speed", - category: "appearance", - categoryName: "Appearance", - section: "Animation", - keywords: ["gif", "speed", "animation", "login"], - }, - // Transparency section - { - name: "Enable transparency", - category: "appearance", - categoryName: "Appearance", - section: "Transparency", - keywords: ["opacity", "translucent", "blur"], - }, - { - name: "Base opacity", - category: "appearance", - categoryName: "Appearance", - section: "Transparency", - keywords: ["opacity", "alpha", "transparency"], - }, - { - name: "Layer opacity", - category: "appearance", - categoryName: "Appearance", - section: "Transparency", - keywords: ["opacity", "alpha", "layers"], - }, + // DASHBOARD CATEGORY + // Dashboard section + { + name: "Enable dashboard", + category: "dashboard", + categoryName: "Dashboard", + section: "Dashboard", + keywords: ["show", "enable", "panel"], + }, + { + name: "Media update interval", + category: "dashboard", + categoryName: "Dashboard", + section: "Dashboard", + keywords: ["refresh", "interval", "music"], + }, + { + name: "Resource update interval", + category: "dashboard", + categoryName: "Dashboard", + section: "Dashboard", + keywords: ["refresh", "interval", "cpu", "memory"], + }, + { + name: "Drag threshold", + category: "dashboard", + categoryName: "Dashboard", + section: "Dashboard", + keywords: ["swipe", "gesture", "threshold"], + }, + // Performance section + { + name: "Show battery", + category: "dashboard", + categoryName: "Dashboard", + section: "Performance", + keywords: ["power", "battery", "visibility"], + }, + { + name: "Show GPU", + category: "dashboard", + categoryName: "Dashboard", + section: "Performance", + keywords: ["graphics", "gpu", "visibility"], + }, + { + name: "Show CPU", + category: "dashboard", + categoryName: "Dashboard", + section: "Performance", + keywords: ["processor", "cpu", "visibility"], + }, + { + name: "Show memory", + category: "dashboard", + categoryName: "Dashboard", + section: "Performance", + keywords: ["ram", "memory", "visibility"], + }, + { + name: "Show storage", + category: "dashboard", + categoryName: "Dashboard", + section: "Performance", + keywords: ["disk", "storage", "visibility"], + }, + { + name: "Show network", + category: "dashboard", + categoryName: "Dashboard", + section: "Performance", + keywords: ["internet", "network", "visibility"], + }, + // Layout Sizes section (read-only) + { + name: "Tab indicator height", + category: "dashboard", + categoryName: "Dashboard", + section: "Layout Sizes", + keywords: ["size", "tab", "indicator"], + }, + { + name: "Tab indicator spacing", + category: "dashboard", + categoryName: "Dashboard", + section: "Layout Sizes", + keywords: ["size", "tab", "spacing"], + }, + { + name: "Info width", + category: "dashboard", + categoryName: "Dashboard", + section: "Layout Sizes", + keywords: ["size", "info", "width"], + }, + { + name: "Info icon size", + category: "dashboard", + categoryName: "Dashboard", + section: "Layout Sizes", + keywords: ["size", "icon"], + }, + { + name: "Date time width", + category: "dashboard", + categoryName: "Dashboard", + section: "Layout Sizes", + keywords: ["size", "clock", "date"], + }, + { + name: "Media width", + category: "dashboard", + categoryName: "Dashboard", + section: "Layout Sizes", + keywords: ["size", "music", "player"], + }, + { + name: "Media progress sweep", + category: "dashboard", + categoryName: "Dashboard", + section: "Layout Sizes", + keywords: ["size", "progress", "arc"], + }, + { + name: "Media progress thickness", + category: "dashboard", + categoryName: "Dashboard", + section: "Layout Sizes", + keywords: ["size", "progress", "stroke"], + }, + { + name: "Resource progress thickness", + category: "dashboard", + categoryName: "Dashboard", + section: "Layout Sizes", + keywords: ["size", "progress", "stroke"], + }, + { + name: "Weather width", + category: "dashboard", + categoryName: "Dashboard", + section: "Layout Sizes", + keywords: ["size", "weather"], + }, + { + name: "Media cover art size", + category: "dashboard", + categoryName: "Dashboard", + section: "Layout Sizes", + keywords: ["size", "album", "artwork"], + }, + { + name: "Media visualiser size", + category: "dashboard", + categoryName: "Dashboard", + section: "Layout Sizes", + keywords: ["size", "visualizer", "cava"], + }, + { + name: "Resource size", + category: "dashboard", + categoryName: "Dashboard", + section: "Layout Sizes", + keywords: ["size", "cpu", "memory"], + }, - // OSD CATEGORY - // On Screen Display section - { - name: "Enable OSD", - category: "osd", - categoryName: "On screen display", - section: "On Screen Display", - keywords: ["show", "enable", "overlay"], - }, - { - name: "Hide delay", - category: "osd", - categoryName: "On screen display", - section: "On Screen Display", - keywords: ["timeout", "duration", "dismiss"], - }, - { - name: "Enable brightness OSD", - category: "osd", - categoryName: "On screen display", - section: "On Screen Display", - keywords: ["screen", "brightness", "overlay"], - }, - { - name: "Enable microphone OSD", - category: "osd", - categoryName: "On screen display", - section: "On Screen Display", - keywords: ["mic", "audio", "overlay"], - }, - { - name: "Brightness on all monitors", - category: "osd", - categoryName: "On screen display", - section: "On Screen Display", - keywords: ["multi-monitor", "all screens"], - }, - // Sizes section - { - name: "Slider width", - category: "osd", - categoryName: "On screen display", - section: "Sizes", - keywords: ["size", "osd", "width"], - }, - { - name: "Slider height", - category: "osd", - categoryName: "On screen display", - section: "Sizes", - keywords: ["size", "osd", "height"], - }, + // APPEARANCE CATEGORY + // Scale section + { + name: "Rounding scale", + category: "appearance", + categoryName: "Appearance", + section: "Scale", + keywords: ["corners", "radius", "scale"], + }, + { + name: "Spacing scale", + category: "appearance", + categoryName: "Appearance", + section: "Scale", + keywords: ["gap", "margin", "scale"], + }, + { + name: "Padding scale", + category: "appearance", + categoryName: "Appearance", + section: "Scale", + keywords: ["padding", "inset", "scale"], + }, + { + name: "Font size scale", + category: "appearance", + categoryName: "Appearance", + section: "Scale", + keywords: ["text", "font", "scale"], + }, + { + name: "Animation duration scale", + category: "appearance", + categoryName: "Appearance", + section: "Scale", + keywords: ["animation", "speed", "duration", "scale"], + }, + { + name: "Deform animation scale", + category: "appearance", + categoryName: "Appearance", + section: "Scale", + keywords: ["animation", "deform", "scale"], + }, + // Fonts section + { + name: "Sans family", + category: "appearance", + categoryName: "Appearance", + section: "Fonts", + keywords: ["font", "typeface", "sans-serif"], + }, + { + name: "Monospace family", + category: "appearance", + categoryName: "Appearance", + section: "Fonts", + keywords: ["font", "typeface", "mono", "code"], + }, + { + name: "Material family", + category: "appearance", + categoryName: "Appearance", + section: "Fonts", + keywords: ["font", "icons", "material"], + }, + { + name: "Clock family", + category: "appearance", + categoryName: "Appearance", + section: "Fonts", + keywords: ["font", "time", "clock"], + }, + // Animation section + { + name: "Media GIF speed adjustment", + category: "appearance", + categoryName: "Appearance", + section: "Animation", + keywords: ["gif", "speed", "animation"], + }, + { + name: "Session GIF speed", + category: "appearance", + categoryName: "Appearance", + section: "Animation", + keywords: ["gif", "speed", "animation", "login"], + }, + // Transparency section + { + name: "Enable transparency", + category: "appearance", + categoryName: "Appearance", + section: "Transparency", + keywords: ["opacity", "translucent", "blur"], + }, + { + name: "Base opacity", + category: "appearance", + categoryName: "Appearance", + section: "Transparency", + keywords: ["opacity", "alpha", "transparency"], + }, + { + name: "Layer opacity", + category: "appearance", + categoryName: "Appearance", + section: "Transparency", + keywords: ["opacity", "alpha", "layers"], + }, - // SCREENSHOT CATEGORY - // Screenshot section - { - name: "Enable effects", - category: "screenshot", - categoryName: "Screenshot", - section: "Screenshot", - keywords: ["effects", "shadow", "screenshot"], - }, - { - name: "Effects mode", - category: "screenshot", - categoryName: "Screenshot", - section: "Screenshot", - keywords: ["effects", "mode"], - }, - { - name: "Corner radius", - category: "screenshot", - categoryName: "Screenshot", - section: "Screenshot", - keywords: ["corner", "radius"], - }, - { - name: "Enable drop shadow", - category: "screenshot", - categoryName: "Screenshot", - section: "Screenshot", - keywords: ["drop", "shadow"], - }, - { - name: "Enable rounded corners", - category: "screenshot", - categoryName: "Screenshot", - section: "Screenshot", - keywords: ["rounded", "corners"], - }, - { - name: "Shadow blur radius", - category: "screenshot", - categoryName: "Screenshot", - section: "Screenshot", - keywords: ["blur", "shadow", "radius"], - }, - { - name: "Shadow color", - category: "screenshot", - categoryName: "Screenshot", - section: "Screenshot", - keywords: ["color", "shadow"], - }, - { - name: "Shadow offset X", - category: "screenshot", - categoryName: "Screenshot", - section: "Screenshot", - keywords: ["offset", "shadow"], - }, - { - name: "Shadow offset Y", - category: "screenshot", - categoryName: "Screenshot", - section: "Screenshot", - keywords: ["offset", "shadow"], - }, + // OSD CATEGORY + // On Screen Display section + { + name: "Enable OSD", + category: "osd", + categoryName: "On screen display", + section: "On Screen Display", + keywords: ["show", "enable", "overlay"], + }, + { + name: "Hide delay", + category: "osd", + categoryName: "On screen display", + section: "On Screen Display", + keywords: ["timeout", "duration", "dismiss"], + }, + { + name: "Enable brightness OSD", + category: "osd", + categoryName: "On screen display", + section: "On Screen Display", + keywords: ["screen", "brightness", "overlay"], + }, + { + name: "Enable microphone OSD", + category: "osd", + categoryName: "On screen display", + section: "On Screen Display", + keywords: ["mic", "audio", "overlay"], + }, + { + name: "Brightness on all monitors", + category: "osd", + categoryName: "On screen display", + section: "On Screen Display", + keywords: ["multi-monitor", "all screens"], + }, + // Sizes section + { + name: "Slider width", + category: "osd", + categoryName: "On screen display", + section: "Sizes", + keywords: ["size", "osd", "width"], + }, + { + name: "Slider height", + category: "osd", + categoryName: "On screen display", + section: "Sizes", + keywords: ["size", "osd", "height"], + }, - // LAUNCHER CATEGORY - // Launcher section - { - name: "Max apps shown", - category: "launcher", - categoryName: "Launcher", - section: "Launcher", - keywords: ["limit", "apps", "search results"], - }, - { - name: "Max wallpapers shown", - category: "launcher", - categoryName: "Launcher", - section: "Launcher", - keywords: ["limit", "wallpapers", "search results"], - }, - { - name: "Action prefix", - category: "launcher", - categoryName: "Launcher", - section: "Launcher", - keywords: ["command", "prefix", "action"], - }, - { - name: "Special prefix", - category: "launcher", - categoryName: "Launcher", - section: "Launcher", - keywords: ["command", "prefix", "special"], - }, - { - name: "Use UWSM launch command", - category: "launcher", - categoryName: "Launcher", - section: "Launcher", - keywords: ["command", "uwsm", "systemd"], - }, - // Fuzzy Search section - { - name: "Apps", - category: "launcher", - categoryName: "Launcher", - section: "Fuzzy Search", - keywords: ["fuzzy", "search", "applications"], - }, - { - name: "Actions", - category: "launcher", - categoryName: "Launcher", - section: "Fuzzy Search", - keywords: ["fuzzy", "search", "actions"], - }, - { - name: "Schemes", - category: "launcher", - categoryName: "Launcher", - section: "Fuzzy Search", - keywords: ["fuzzy", "search", "color schemes"], - }, - { - name: "Variants", - category: "launcher", - categoryName: "Launcher", - section: "Fuzzy Search", - keywords: ["fuzzy", "search", "variants"], - }, - { - name: "Wallpapers", - category: "launcher", - categoryName: "Launcher", - section: "Fuzzy Search", - keywords: ["fuzzy", "search", "backgrounds"], - }, - // Sizes section - { - name: "Item width", - category: "launcher", - categoryName: "Launcher", - section: "Sizes", - keywords: ["size", "app", "width"], - }, - { - name: "Item height", - category: "launcher", - categoryName: "Launcher", - section: "Sizes", - keywords: ["size", "app", "height"], - }, - { - name: "Wallpaper width", - category: "launcher", - categoryName: "Launcher", - section: "Sizes", - keywords: ["size", "wallpaper", "width"], - }, - { - name: "Wallpaper height", - category: "launcher", - categoryName: "Launcher", - section: "Sizes", - keywords: ["size", "wallpaper", "height"], - }, - // Actions section - { - name: "Launcher actions", - category: "launcher", - categoryName: "Launcher", - section: "Actions", - keywords: ["commands", "shortcuts", "actions"], - }, + // SCREENSHOT CATEGORY + // Screenshot section + { + name: "Enable effects", + category: "screenshot", + categoryName: "Screenshot", + section: "Screenshot", + keywords: ["effects", "shadow", "screenshot"], + }, + { + name: "Effects mode", + category: "screenshot", + categoryName: "Screenshot", + section: "Screenshot", + keywords: ["effects", "mode"], + }, + { + name: "Corner radius", + category: "screenshot", + categoryName: "Screenshot", + section: "Screenshot", + keywords: ["corner", "radius"], + }, + { + name: "Enable drop shadow", + category: "screenshot", + categoryName: "Screenshot", + section: "Screenshot", + keywords: ["drop", "shadow"], + }, + { + name: "Enable rounded corners", + category: "screenshot", + categoryName: "Screenshot", + section: "Screenshot", + keywords: ["rounded", "corners"], + }, + { + name: "Shadow blur radius", + category: "screenshot", + categoryName: "Screenshot", + section: "Screenshot", + keywords: ["blur", "shadow", "radius"], + }, + { + name: "Shadow color", + category: "screenshot", + categoryName: "Screenshot", + section: "Screenshot", + keywords: ["color", "shadow"], + }, + { + name: "Shadow offset X", + category: "screenshot", + categoryName: "Screenshot", + section: "Screenshot", + keywords: ["offset", "shadow"], + }, + { + name: "Shadow offset Y", + category: "screenshot", + categoryName: "Screenshot", + section: "Screenshot", + keywords: ["offset", "shadow"], + }, + + // LAUNCHER CATEGORY + // Launcher section + { + name: "Max apps shown", + category: "launcher", + categoryName: "Launcher", + section: "Launcher", + keywords: ["limit", "apps", "search results"], + }, + { + name: "Max wallpapers shown", + category: "launcher", + categoryName: "Launcher", + section: "Launcher", + keywords: ["limit", "wallpapers", "search results"], + }, + { + name: "Action prefix", + category: "launcher", + categoryName: "Launcher", + section: "Launcher", + keywords: ["command", "prefix", "action"], + }, + { + name: "Special prefix", + category: "launcher", + categoryName: "Launcher", + section: "Launcher", + keywords: ["command", "prefix", "special"], + }, + { + name: "Use UWSM launch command", + category: "launcher", + categoryName: "Launcher", + section: "Launcher", + keywords: ["command", "uwsm", "systemd"], + }, + // Fuzzy Search section + { + name: "Apps", + category: "launcher", + categoryName: "Launcher", + section: "Fuzzy Search", + keywords: ["fuzzy", "search", "applications"], + }, + { + name: "Actions", + category: "launcher", + categoryName: "Launcher", + section: "Fuzzy Search", + keywords: ["fuzzy", "search", "actions"], + }, + { + name: "Schemes", + category: "launcher", + categoryName: "Launcher", + section: "Fuzzy Search", + keywords: ["fuzzy", "search", "color schemes"], + }, + { + name: "Variants", + category: "launcher", + categoryName: "Launcher", + section: "Fuzzy Search", + keywords: ["fuzzy", "search", "variants"], + }, + { + name: "Wallpapers", + category: "launcher", + categoryName: "Launcher", + section: "Fuzzy Search", + keywords: ["fuzzy", "search", "backgrounds"], + }, + // Sizes section + { + name: "Item width", + category: "launcher", + categoryName: "Launcher", + section: "Sizes", + keywords: ["size", "app", "width"], + }, + { + name: "Item height", + category: "launcher", + categoryName: "Launcher", + section: "Sizes", + keywords: ["size", "app", "height"], + }, + { + name: "Wallpaper width", + category: "launcher", + categoryName: "Launcher", + section: "Sizes", + keywords: ["size", "wallpaper", "width"], + }, + { + name: "Wallpaper height", + category: "launcher", + categoryName: "Launcher", + section: "Sizes", + keywords: ["size", "wallpaper", "height"], + }, + // Actions section + { + name: "Launcher actions", + category: "launcher", + categoryName: "Launcher", + section: "Actions", + keywords: ["commands", "shortcuts", "actions"], + }, ]; // Helper function to search with keywords first, then fuzzy fallback export function searchSettings(query, fuzzyModule) { - if (!query || query.trim() === "") { - return []; - } - - const q = query.toLowerCase().trim(); - const results = []; - const seen = new Set(); - - // 1. Exact keyword match (highest priority) - for (const setting of settingsIndex) { - const key = `${setting.category}:${setting.name}`; - if (seen.has(key)) continue; - - for (const keyword of setting.keywords) { - if (keyword.toLowerCase() === q) { - results.push( - Object.assign({}, setting, { - matchType: "exact-keyword", - score: 1.0, - }), - ); - seen.add(key); - break; - } + if (!query || query.trim() === "") { + return []; } - } - // 2. Partial keyword match - for (const setting of settingsIndex) { - const key = `${setting.category}:${setting.name}`; - if (seen.has(key)) continue; + const q = query.toLowerCase().trim(); + const results = []; + const seen = new Set(); - for (const keyword of setting.keywords) { - if (keyword.toLowerCase().includes(q)) { - results.push( - Object.assign({}, setting, { - matchType: "partial-keyword", - score: 0.8, - }), - ); - seen.add(key); - break; - } + // 1. Exact keyword match (highest priority) + for (const setting of settingsIndex) { + const key = `${setting.category}:${setting.name}`; + if (seen.has(key)) continue; + + for (const keyword of setting.keywords) { + if (keyword.toLowerCase() === q) { + results.push( + Object.assign({}, setting, { + matchType: "exact-keyword", + score: 1.0, + }) + ); + seen.add(key); + break; + } + } } - } - // 3. Name contains query - for (const setting of settingsIndex) { - const key = `${setting.category}:${setting.name}`; - if (seen.has(key)) continue; + // 2. Partial keyword match + for (const setting of settingsIndex) { + const key = `${setting.category}:${setting.name}`; + if (seen.has(key)) continue; - if (setting.name.toLowerCase().includes(q)) { - results.push( - Object.assign({}, setting, { matchType: "name-contains", score: 0.7 }), - ); - seen.add(key); + for (const keyword of setting.keywords) { + if (keyword.toLowerCase().includes(q)) { + results.push( + Object.assign({}, setting, { + matchType: "partial-keyword", + score: 0.8, + }) + ); + seen.add(key); + break; + } + } } - } - // 4. Fuzzy match on name (fallback for typos) - if (fuzzyModule && results.length < 10) { - const fuzzyTargets = settingsIndex - .filter((s) => !seen.has(`${s.category}:${s.name}`)) - .map((s) => Object.assign({}, s, { _searchTarget: s.name })); + // 3. Name contains query + for (const setting of settingsIndex) { + const key = `${setting.category}:${setting.name}`; + if (seen.has(key)) continue; - if (fuzzyTargets.length > 0) { - const fuzzyResults = fuzzyModule.go(query, fuzzyTargets, { - key: "_searchTarget", - limit: 10 - results.length, - threshold: 0.3, - }); - - for (const r of fuzzyResults) { - const setting = r.obj; - results.push( - Object.assign({}, setting, { - matchType: "fuzzy", - score: r.score * 0.6, - _searchTarget: undefined, - }), - ); - } + if (setting.name.toLowerCase().includes(q)) { + results.push( + Object.assign({}, setting, { + matchType: "name-contains", + score: 0.7, + }) + ); + seen.add(key); + } } - } - // Sort by score descending - results.sort((a, b) => b.score - a.score); + // 4. Fuzzy match on name (fallback for typos) + if (fuzzyModule && results.length < 10) { + const fuzzyTargets = settingsIndex + .filter((s) => !seen.has(`${s.category}:${s.name}`)) + .map((s) => Object.assign({}, s, { _searchTarget: s.name })); - return results; + if (fuzzyTargets.length > 0) { + const fuzzyResults = fuzzyModule.go(query, fuzzyTargets, { + key: "_searchTarget", + limit: 10 - results.length, + threshold: 0.3, + }); + + for (const r of fuzzyResults) { + const setting = r.obj; + results.push( + Object.assign({}, setting, { + matchType: "fuzzy", + score: r.score * 0.6, + _searchTarget: undefined, + }) + ); + } + } + } + + // Sort by score descending + results.sort((a, b) => b.score - a.score); + + return results; } diff --git a/Modules/Settings/Wrapper.qml b/Modules/Settings/Wrapper.qml index 77427bc..8795427 100644 --- a/Modules/Settings/Wrapper.qml +++ b/Modules/Settings/Wrapper.qml @@ -26,38 +26,6 @@ Item { } } - // states: State { - // name: "visible" - // when: root.visibilities.settings - // - // PropertyChanges { - // root.implicitHeight: content.implicitHeight - // } - // } - // transitions: [ - // Transition { - // from: "" - // to: "visible" - // - // Anim { - // duration: MaterialEasing.expressiveEffectsTime - // easing.bezierCurve: MaterialEasing.expressiveEffects - // property: "implicitHeight" - // target: root - // } - // }, - // Transition { - // from: "visible" - // to: "" - // - // Anim { - // easing.bezierCurve: MaterialEasing.expressiveEffects - // property: "implicitHeight" - // target: root - // } - // } - // ] - CustomClippingRect { anchors.fill: parent From 405825518a4ba0b82d29078d3c9606ccc9c11a69 Mon Sep 17 00:00:00 2001 From: zach Date: Tue, 19 May 2026 08:30:31 +0200 Subject: [PATCH 14/20] move search index file to correct place --- Modules/Settings/SettingsSearch.qml | 7 ++++--- {Modules/Settings => scripts}/SettingsIndex.mjs | 0 2 files changed, 4 insertions(+), 3 deletions(-) rename {Modules/Settings => scripts}/SettingsIndex.mjs (100%) diff --git a/Modules/Settings/SettingsSearch.qml b/Modules/Settings/SettingsSearch.qml index 7d6b54a..cd69af7 100644 --- a/Modules/Settings/SettingsSearch.qml +++ b/Modules/Settings/SettingsSearch.qml @@ -6,7 +6,7 @@ import QtQuick.Controls import qs.Components import qs.Config import "../../scripts/fuzzysort.js" as Fuzzy -import "./SettingsIndex.mjs" as SettingsIndex +import "../../scripts/SettingsIndex.mjs" as SettingsIndex Item { id: root @@ -51,13 +51,14 @@ Item { implicitHeight: searchContainer.implicitHeight implicitWidth: 200 + Component.onCompleted: console.log(root.height) + Shortcut { sequence: "/" + onActivated: searchField.forceActiveFocus() } - Component.onCompleted: console.log(root.height) - ListModel { id: resultsModel } diff --git a/Modules/Settings/SettingsIndex.mjs b/scripts/SettingsIndex.mjs similarity index 100% rename from Modules/Settings/SettingsIndex.mjs rename to scripts/SettingsIndex.mjs From 362b7bb8c23bd37b262a5214ff9eef8bc1f5ba42 Mon Sep 17 00:00:00 2001 From: zach Date: Tue, 19 May 2026 08:43:52 +0200 Subject: [PATCH 15/20] add the drawing popout background --- Modules/Drawing/Wrapper.qml | 102 +++++++++--------------------------- 1 file changed, 25 insertions(+), 77 deletions(-) diff --git a/Modules/Drawing/Wrapper.qml b/Modules/Drawing/Wrapper.qml index a2df74d..a97cce7 100644 --- a/Modules/Drawing/Wrapper.qml +++ b/Modules/Drawing/Wrapper.qml @@ -12,90 +12,29 @@ Item { required property Canvas drawing property bool expanded: true + property real offsetScale: shouldBeActive ? 0 : 1 required property ShellScreen screen readonly property bool shouldBeActive: visibilities.isDrawing required property var visibilities + anchors.leftMargin: (-implicitWidth - 5) * offsetScale implicitHeight: content.implicitHeight - implicitWidth: 0 - visible: width > 0 + implicitWidth: root.expanded ? content.implicitWidth : icon.implicitWidth + opacity: 1 - offsetScale + visible: offsetScale < 1 - states: [ - State { - name: "hidden" - when: !root.shouldBeActive - - PropertyChanges { - root.implicitWidth: 0 - } - - PropertyChanges { - icon.opacity: 0 - } - - PropertyChanges { - content.opacity: 0 - } - }, - State { - name: "collapsed" - when: root.shouldBeActive && !root.expanded - - PropertyChanges { - root.implicitWidth: icon.implicitWidth - } - - PropertyChanges { - icon.opacity: 1 - } - - PropertyChanges { - content.opacity: 0 - } - }, - State { - name: "visible" - when: root.shouldBeActive && root.expanded - - PropertyChanges { - root.implicitWidth: content.implicitWidth - } - - PropertyChanges { - icon.opacity: 0 - } - - PropertyChanges { - content.opacity: 1 - } + Behavior on implicitWidth { + Anim { + duration: Appearance.anim.durations.expressiveDefaultSpatial + easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial } - ] - transitions: [ - Transition { - from: "*" - to: "*" - - ParallelAnimation { - Anim { - easing.bezierCurve: MaterialEasing.expressiveEffects - property: "implicitWidth" - target: root - } - - Anim { - duration: Appearance.anim.durations.small - property: "opacity" - target: icon - } - - Anim { - duration: Appearance.anim.durations.small - property: "opacity" - target: content - } - } + } + Behavior on offsetScale { + Anim { + duration: Appearance.anim.durations.expressiveDefaultSpatial + easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial } - ] + } onVisibleChanged: { if (!visible) @@ -109,8 +48,12 @@ Item { anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter height: content.contentItem.height - opacity: 1 + opacity: root.expanded ? 0 : 1 + Behavior on opacity { + Anim { + } + } sourceComponent: MaterialIcon { font.pointSize: Appearance.font.size.larger text: "arrow_forward_ios" @@ -122,7 +65,12 @@ Item { anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter + opacity: root.expanded ? 1 : 0 + Behavior on opacity { + Anim { + } + } sourceComponent: Content { drawing: root.drawing visibilities: root.visibilities From b20767c702eb025913ab3f46fcaef881a12bbfe7 Mon Sep 17 00:00:00 2001 From: zach Date: Tue, 19 May 2026 10:04:04 +0200 Subject: [PATCH 16/20] hopefully increase drawing performance --- Drawers/Drawing.qml | 197 +++++---------------------------------- Drawers/DrawingInput.qml | 13 ++- Drawers/Interactions.qml | 36 +++---- 3 files changed, 49 insertions(+), 197 deletions(-) diff --git a/Drawers/Drawing.qml b/Drawers/Drawing.qml index 6a30ff2..9d7bf63 100644 --- a/Drawers/Drawing.qml +++ b/Drawers/Drawing.qml @@ -3,184 +3,33 @@ import QtQuick Canvas { id: root - property rect dirtyRect: Qt.rect(0, 0, 0, 0) - property bool frameQueued: false - property bool fullRepaintPending: true - property point lastPoint: Qt.point(0, 0) - property real minPointDistance: 2.0 property color penColor: "white" property real penWidth: 4 - property var pendingSegments: [] - property bool strokeActive: false - property var strokes: [] + property var points: [] - function appendPoint(x, y) { - if (!strokeActive || strokes.length === 0) + function clear(): void { + var ctx = getContext('2d'); + root.points = []; + ctx.reset(); + root.requestPaint(); + } + + renderStrategy: Canvas.Cooperative + + onPaint: { + if (points.length < 2) return; - const dx = x - lastPoint.x; - const dy = y - lastPoint.y; - - if ((dx * dx + dy * dy) < (minPointDistance * minPointDistance)) - return; - const x1 = lastPoint.x; - const y1 = lastPoint.y; - const x2 = x; - const y2 = y; - - strokes[strokes.length - 1].push(Qt.point(x2, y2)); - - pendingSegments.push({ - dot: false, - x1: x1, - y1: y1, - x2: x2, - y2: y2 - }); - - lastPoint = Qt.point(x2, y2); - queueDirty(segmentDirtyRect(x1, y1, x2, y2)); - } - - function beginStroke(x, y) { - const p = Qt.point(x, y); - strokes.push([p]); - lastPoint = p; - strokeActive = true; - - pendingSegments.push({ - dot: true, - x: x, - y: y - }); - - queueDirty(pointDirtyRect(x, y)); - } - - function clear() { - strokes = []; - pendingSegments = []; - dirtyRect = Qt.rect(0, 0, 0, 0); - fullRepaintPending = true; - markDirty(Qt.rect(0, 0, width, height)); - } - - function drawDot(ctx, x, y) { - ctx.beginPath(); - ctx.arc(x, y, penWidth / 2, 0, Math.PI * 2); - ctx.fill(); - } - - function drawSegment(ctx, x1, y1, x2, y2) { - ctx.beginPath(); - ctx.moveTo(x1, y1); - ctx.lineTo(x2, y2); - ctx.stroke(); - } - - function endStroke() { - strokeActive = false; - } - - function pointDirtyRect(x, y) { - const pad = penWidth + 2; - return Qt.rect(x - pad, y - pad, pad * 2, pad * 2); - } - - function queueDirty(r) { - dirtyRect = unionRects(dirtyRect, r); - - if (frameQueued) - return; - frameQueued = true; - - requestAnimationFrame(function () { - frameQueued = false; - - if (dirtyRect.width > 0 && dirtyRect.height > 0) { - markDirty(dirtyRect); - dirtyRect = Qt.rect(0, 0, 0, 0); - } - }); - } - - function replayAll(ctx) { - ctx.clearRect(0, 0, width, height); - - for (const stroke of strokes) { - if (!stroke || stroke.length === 0) - continue; - if (stroke.length === 1) { - const p = stroke[0]; - drawDot(ctx, p.x, p.y); - continue; - } - - ctx.beginPath(); - ctx.moveTo(stroke[0].x, stroke[0].y); - for (let i = 1; i < stroke.length; ++i) - ctx.lineTo(stroke[i].x, stroke[i].y); - ctx.stroke(); - } - } - - function requestFullRepaint() { - fullRepaintPending = true; - markDirty(Qt.rect(0, 0, width, height)); - } - - function segmentDirtyRect(x1, y1, x2, y2) { - const pad = penWidth + 2; - const left = Math.min(x1, x2) - pad; - const top = Math.min(y1, y2) - pad; - const right = Math.max(x1, x2) + pad; - const bottom = Math.max(y1, y2) + pad; - return Qt.rect(left, top, right - left, bottom - top); - } - - function unionRects(a, b) { - if (a.width <= 0 || a.height <= 0) - return b; - if (b.width <= 0 || b.height <= 0) - return a; - - const left = Math.min(a.x, b.x); - const top = Math.min(a.y, b.y); - const right = Math.max(a.x + a.width, b.x + b.width); - const bottom = Math.max(a.y + a.height, b.y + b.height); - - return Qt.rect(left, top, right - left, bottom - top); - } - - anchors.fill: parent - contextType: "2d" - renderStrategy: Canvas.Threaded - renderTarget: Canvas.Image - - onHeightChanged: requestFullRepaint() - onPaint: region => { - const ctx = getContext("2d"); - + var ctx = root.getContext('2d'); + ctx.save(); + ctx.lineWidth = root.penWidth; + ctx.strokeStyle = root.penColor; ctx.lineCap = "round"; - ctx.lineJoin = "round"; - ctx.lineWidth = penWidth; - ctx.strokeStyle = penColor; - ctx.fillStyle = penColor; - - if (fullRepaintPending) { - fullRepaintPending = false; - replayAll(ctx); - pendingSegments = []; - return; - } - - for (const seg of pendingSegments) { - if (seg.dot) - drawDot(ctx, seg.x, seg.y); - else - drawSegment(ctx, seg.x1, seg.y1, seg.x2, seg.y2); - } - - pendingSegments = []; + ctx.beginPath(); + ctx.moveTo(points[0].x, points[0].y); + for (var i = 1; i < points.length; i++) + ctx.lineTo(points[i].x, points[i].y); + ctx.stroke(); + points = points.slice(points.length - 2); + ctx.restore(); } - onWidthChanged: requestFullRepaint() } diff --git a/Drawers/DrawingInput.qml b/Drawers/DrawingInput.qml index c8f2c18..3fdcd33 100644 --- a/Drawers/DrawingInput.qml +++ b/Drawers/DrawingInput.qml @@ -30,8 +30,11 @@ CustomMouseArea { const x = event.x; const y = event.y; - if (event.buttons & Qt.LeftButton) - root.drawing.appendPoint(x, y); + if (root.visibilities.isDrawing && (event.buttons & Qt.LeftButton)) { + root.drawing.points.push(Qt.point(x, y)); + console.log(root.drawing.points); + root.drawing.requestPaint(); + } if (root.inLeftPanel(root.popout, x, y)) { root.z = -2; @@ -44,7 +47,8 @@ CustomMouseArea { if (root.visibilities.isDrawing && (event.buttons & Qt.LeftButton)) { root.panels.drawing.expanded = false; - root.drawing.beginStroke(x, y); + root.drawing.points.push(Qt.point(x, y)); + root.drawing.requestPaint(); return; } @@ -52,7 +56,6 @@ CustomMouseArea { root.drawing.clear(); } onReleased: { - if (root.visibilities.isDrawing) - root.drawing.endStroke(); + root.drawing.points = []; } } diff --git a/Drawers/Interactions.qml b/Drawers/Interactions.qml index c3abfe8..f7fcdf0 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; } @@ -96,25 +96,25 @@ CustomMouseArea { if (dragY < -10) visibilities.dock = true; - if (panels.sidebar.width === 0) { - const showOsd = inRightPanel(panels.osdWrapper, x, y); + if (panels.sidebar.width === 0) { + const showOsd = inRightPanel(panels.osdWrapper, x, y); - if (showOsd) { - osdShortcutActive = false; - root.panels.osd.hovered = true; - } - } else { - const outOfSidebar = x < width - panels.sidebar.width; - const showOsd = outOfSidebar && inRightPanel(panels.osdWrapper, x, y); - - if (!osdShortcutActive) { - visibilities.osd = showOsd; - root.panels.osd.hovered = showOsd; - } else if (showOsd) { - osdShortcutActive = false; - root.panels.osd.hovered = true; - } + if (showOsd) { + osdShortcutActive = false; + root.panels.osd.hovered = true; } + } else { + const outOfSidebar = x < width - panels.sidebar.width; + const showOsd = outOfSidebar && inRightPanel(panels.osdWrapper, x, y); + + if (!osdShortcutActive) { + visibilities.osd = showOsd; + root.panels.osd.hovered = showOsd; + } else if (showOsd) { + osdShortcutActive = false; + root.panels.osd.hovered = true; + } + } if (Config.dock.enable && !Config.dock.hoverToReveal && !visibilities.dock && !visibilities.launcher && inBottomPanel(panels.dock, x, y)) visibilities.dock = true; From b6ad180b6a3802e0b6067c1c48c51ce96aa133d4 Mon Sep 17 00:00:00 2001 From: zach Date: Tue, 19 May 2026 15:38:59 +0200 Subject: [PATCH 17/20] select part of wallpaper to show --- Config/BackgroundConfig.qml | 4 + Config/Config.qml | 4 + Modules/Settings/Categories/Background.qml | 14 +- .../Settings/Controls/WallpaperCropper.qml | 165 ++++++++++-------- Modules/Wallpaper/WallBackground.qml | 18 +- 5 files changed, 109 insertions(+), 96 deletions(-) diff --git a/Config/BackgroundConfig.qml b/Config/BackgroundConfig.qml index 160fa8e..10e7691 100644 --- a/Config/BackgroundConfig.qml +++ b/Config/BackgroundConfig.qml @@ -7,4 +7,8 @@ JsonObject { property real alignX: 0.5 property real alignY: 0.5 property real zoom: 1.0 + property real sourceClipX: 0 + property real sourceClipY: 0 + property real sourceClipW: 0 + property real sourceClipH: 0 } diff --git a/Config/Config.qml b/Config/Config.qml index da951f4..ba4c6f2 100644 --- a/Config/Config.qml +++ b/Config/Config.qml @@ -83,6 +83,10 @@ Singleton { wallFadeDuration: background.wallFadeDuration, enabled: background.enabled, alignX: background.alignX, + sourceClipX: background.sourceClipX, + sourceClipY: background.sourceClipY, + sourceClipW: background.sourceClipW, + sourceClipH: background.sourceClipH, alignY: background.alignY, zoom: background.zoom }; diff --git a/Modules/Settings/Categories/Background.qml b/Modules/Settings/Categories/Background.qml index 8933102..68a4931 100644 --- a/Modules/Settings/Categories/Background.qml +++ b/Modules/Settings/Categories/Background.qml @@ -29,13 +29,13 @@ SettingsPage { step: 50 } - // Separator { - // } - // - // WallpaperCropper { - // Layout.fillWidth: true - // Layout.preferredHeight: 300 - // } + Separator { + } + + WallpaperCropper { + Layout.fillWidth: true + Layout.preferredHeight: 600 + } } SettingsSection { diff --git a/Modules/Settings/Controls/WallpaperCropper.qml b/Modules/Settings/Controls/WallpaperCropper.qml index d0f5faa..7090ff5 100644 --- a/Modules/Settings/Controls/WallpaperCropper.qml +++ b/Modules/Settings/Controls/WallpaperCropper.qml @@ -6,97 +6,108 @@ import qs.Config import qs.Components import qs.Helpers -ColumnLayout { +Item { id: root - spacing: 15 - width: Math.min(parent ? parent.width : 600, 600) + Image { + id: imageView - Rectangle { - id: previewContainer + property real displayH: paintedHeight + property real displayW: paintedWidth + property real displayX: (width - paintedWidth) * 0.5 + property real displayY: (height - paintedHeight) * 0.5 + property real scaleX: sourceW / displayW + property real scaleY: sourceH / displayH + property real sourceH: sourceSize.height + property real sourceW: sourceSize.width + + anchors.fill: parent + fillMode: Image.PreserveAspectFit + smooth: true + source: Wallpapers.current + } + + Item { + id: overlay - Layout.fillHeight: true - Layout.preferredWidth: height * (Quickshell.screens.length > 0 ? (Quickshell.screens[0].height / Math.max(1, Quickshell.screens[0].width)) : 16 / 9) clip: true - color: DynamicColors.palette.m3surfaceContainer - radius: Config.appearance.rounding.scale * 10 + height: imageView.displayH + width: imageView.displayW + x: imageView.displayX + y: imageView.displayY - Image { - id: img + CustomRect { + id: cropRect + + property real aspectRatio: Quickshell.screens[0].width / Quickshell.screens[0].height + readonly property rect sourceRect: Qt.rect(x * imageView.scaleX, y * imageView.scaleY, width * imageView.scaleX, height * imageView.scaleY) + property real zoom: Config.background.zoom + + function clampToBounds() { + x = Math.max(0, Math.min(x, overlay.width - width)); + + y = Math.max(0, Math.min(y, overlay.height - height)); + } + + border.color: DynamicColors.palette.m3primary + border.width: 2 + color: DynamicColors.tPalette.m3primary + height: width / aspectRatio + radius: Appearance.rounding.small + visible: imageView.status === Image.Ready + width: Math.min(overlay.width / zoom, overlay.height * aspectRatio / zoom) + x: Config.background.sourceClipX / imageView.scaleX + y: Config.background.sourceClipY / imageView.scaleY + } + + MouseArea { + function updateCrop(mouseX, mouseY) { + let nx = mouseX - cropRect.width * 0.5; + let ny = mouseY - cropRect.height * 0.5; + + nx = Math.max(0, Math.min(nx, overlay.width - cropRect.width)); + + ny = Math.max(0, Math.min(ny, overlay.height - cropRect.height)); + + cropRect.x = nx; + cropRect.y = ny; + } anchors.fill: parent - asynchronous: true - fillMode: Image.PreserveAspectFit - source: Wallpapers.current + hoverEnabled: true + preventStealing: true - Rectangle { - id: cropRect + onPositionChanged: mouse => { + if (pressed) + updateCrop(mouse.x, mouse.y); + } + onPressed: mouse => { + updateCrop(mouse.x, mouse.y); + } + onReleased: { + Config.background.sourceClipX = cropRect.sourceRect.x; + Config.background.sourceClipY = cropRect.sourceRect.y; + Config.background.sourceClipW = cropRect.sourceRect.width; + Config.background.sourceClipH = cropRect.sourceRect.height; + Config.save(); + } + onWheel: wheel => { + let oldCenterX = cropRect.x + cropRect.width * 0.5; + let oldCenterY = cropRect.y + cropRect.height * 0.5; - property real cropHeight: (imageAspect > screenAspect ? paintedHeight : paintedWidth / screenAspect) / Config.background.zoom - property real cropWidth: (imageAspect > screenAspect ? paintedHeight * screenAspect : paintedWidth) / Config.background.zoom - property real imageAspect: Math.max(1, paintedWidth) / Math.max(1, paintedHeight) - property real paintedHeight: img.paintedHeight > 0 ? img.paintedHeight : img.height - property real paintedWidth: img.paintedWidth > 0 ? img.paintedWidth : img.width - property real paintedX: (img.width - paintedWidth) / 2 - property real paintedY: (img.height - paintedHeight) / 2 - property real screenAspect: Quickshell.screens.length > 0 ? (Quickshell.screens[0].width / Math.max(1, Quickshell.screens[0].height)) : 16 / 9 + if (wheel.angleDelta.y > 0) + cropRect.zoom *= 1.1; + else + cropRect.zoom /= 1.1; - border.color: DynamicColors.palette.m3primary - border.width: 2 - color: Qt.alpha(DynamicColors.palette.m3primaryContainer, 0.3) - height: cropHeight - width: cropWidth - x: paintedX + (paintedWidth - width) * Config.background.alignX - y: paintedY + (paintedHeight - height) * Config.background.alignY + cropRect.zoom = Math.max(1.0, Math.min(cropRect.zoom, 10.0)); + Config.background.zoom = cropRect.zoom; - DragHandler { - target: null + cropRect.x = oldCenterX - cropRect.width * 0.5; + cropRect.y = oldCenterY - cropRect.height * 0.5; - onActiveTranslationChanged: { - if (active) { - let newX = cropRect.x - cropRect.paintedX + translation.x; - let newY = cropRect.y - cropRect.paintedY + translation.y; - - let rangeX = cropRect.paintedWidth - cropRect.width; - let rangeY = cropRect.paintedHeight - cropRect.height; - - if (rangeX > 0) { - let valX = newX / rangeX; - Config.background.alignX = Math.max(0.0, Math.min(1.0, valX)); - Config.save(); - } - - if (rangeY > 0) { - let valY = newY / rangeY; - Config.background.alignY = Math.max(0.0, Math.min(1.0, valY)); - Config.save(); - } - } - } - } - - PinchHandler { - maximumScale: 5.0 - minimumScale: 1.0 - target: null - - onActiveScaleChanged: { - if (active) { - let newZoom = Config.background.zoom * (1 / (1 + (activeScale - 1) * 0.1)); - Config.background.zoom = Math.max(1.0, Math.min(newZoom, 5.0)); - } - } - } + cropRect.clampToBounds(); } } } - - SettingSpinBox { - max: 5.0 - min: 1.0 - name: "Zoom" - object: Config.background - setting: "zoom" - step: 0.1 - } } diff --git a/Modules/Wallpaper/WallBackground.qml b/Modules/Wallpaper/WallBackground.qml index c2c8a35..b8bb649 100644 --- a/Modules/Wallpaper/WallBackground.qml +++ b/Modules/Wallpaper/WallBackground.qml @@ -35,30 +35,24 @@ Item { id: two } - component Img: CachingImage { + component Img: Image { id: img - property real imageRatio: Math.max(1, sourceSize.width) / Math.max(1, sourceSize.height) - property bool isValid: sourceSize.width > 0 && sourceSize.height > 0 && root.width > 0 && root.height > 0 - property real windowRatio: root.width / Math.max(1, root.height) - function update(): void { - if (path === root.source) { + if (source === root.source) { root.current = this; } else { - path = root.source; + source = root.source; } } - anchors.fill: undefined + anchors.fill: parent asynchronous: true fillMode: Image.PreserveAspectCrop - height: isValid ? (imageRatio > windowRatio ? root.height : root.width / imageRatio) * Config.background.zoom : root.height opacity: 0 + retainWhileLoading: true scale: Wallpapers.showPreview ? 1 : 0.8 - width: isValid ? (imageRatio > windowRatio ? root.height * imageRatio : root.width) * Config.background.zoom : root.width - x: isValid ? (root.width - width) * Config.background.alignX : 0 - y: isValid ? (root.height - height) * Config.background.alignY : 0 + sourceClipRect: Qt.rect(Config.background.sourceClipX, Config.background.sourceClipY, Config.background.sourceClipW, Config.background.sourceClipH) states: State { name: "visible" From 9c6a1ce1a46fdab146ca4f9a03b28b14b1f44a12 Mon Sep 17 00:00:00 2001 From: zach Date: Tue, 19 May 2026 16:10:00 +0200 Subject: [PATCH 18/20] hide notification content on lockscreen, toggleable --- Config/Config.qml | 1 + Config/LockConf.qml | 1 + Modules/Lock/NotifGroup.qml | 2 ++ Modules/Settings/Categories/Lockscreen.qml | 19 ++++++++++++++----- scripts/SettingsIndex.mjs | 7 +++++++ 5 files changed, 25 insertions(+), 5 deletions(-) diff --git a/Config/Config.qml b/Config/Config.qml index ba4c6f2..c694c28 100644 --- a/Config/Config.qml +++ b/Config/Config.qml @@ -241,6 +241,7 @@ Singleton { return { recolorLogo: lock.recolorLogo, enableFprint: lock.enableFprint, + showNotifContent: lock.showNotifContent, maxFprintTries: lock.maxFprintTries, blurAmount: lock.blurAmount, sizes: { diff --git a/Config/LockConf.qml b/Config/LockConf.qml index e377459..bf83565 100644 --- a/Config/LockConf.qml +++ b/Config/LockConf.qml @@ -5,6 +5,7 @@ JsonObject { property bool enableFprint: true property int maxFprintTries: 3 property bool recolorLogo: false + property bool showNotifContent: false property Sizes sizes: Sizes { } diff --git a/Modules/Lock/NotifGroup.qml b/Modules/Lock/NotifGroup.qml index a0d0d21..eaca951 100644 --- a/Modules/Lock/NotifGroup.qml +++ b/Modules/Lock/NotifGroup.qml @@ -284,6 +284,8 @@ CustomRect { Layout.fillWidth: true color: root.urgency === "critical" ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface text: { + if (!Config.lock.showNotifContent) + return "Unlock to view"; const summary = modelData.summary.replace(/\n/g, " "); const body = modelData.body.replace(/\n/g, " "); const color = root.urgency === "critical" ? DynamicColors.palette.m3secondary : DynamicColors.palette.m3outline; diff --git a/Modules/Settings/Categories/Lockscreen.qml b/Modules/Settings/Categories/Lockscreen.qml index 4c6c3e3..9c63787 100644 --- a/Modules/Settings/Categories/Lockscreen.qml +++ b/Modules/Settings/Categories/Lockscreen.qml @@ -31,8 +31,8 @@ SettingsPage { } SettingSpinBox { - name: "Max fingerprint tries" min: 1 + name: "Max fingerprint tries" object: Config.lock setting: "maxFprintTries" step: 1 @@ -41,9 +41,18 @@ SettingsPage { Separator { } + SettingSwitch { + name: "Show notification details" + object: Config.lock + setting: "showNotifContent" + } + + Separator { + } + SettingSpinBox { - name: "Blur amount" min: 0 + name: "Blur amount" object: Config.lock setting: "blurAmount" step: 1 @@ -53,9 +62,9 @@ SettingsPage { } SettingSpinBox { - name: "Height multiplier" max: 2 min: 0.1 + name: "Height multiplier" object: Config.lock.sizes setting: "heightMult" step: 0.05 @@ -65,9 +74,9 @@ SettingsPage { } SettingSpinBox { - name: "Aspect ratio" max: 4 min: 0.5 + name: "Aspect ratio" object: Config.lock.sizes setting: "ratio" step: 0.05 @@ -77,8 +86,8 @@ SettingsPage { } SettingSpinBox { - name: "Center width" min: 100 + name: "Center width" object: Config.lock.sizes setting: "centerWidth" step: 10 diff --git a/scripts/SettingsIndex.mjs b/scripts/SettingsIndex.mjs index 06a2006..8e6303a 100644 --- a/scripts/SettingsIndex.mjs +++ b/scripts/SettingsIndex.mjs @@ -304,6 +304,13 @@ export const settingsIndex = [ section: "Lockscreen", keywords: ["attempts", "limit", "fprint"], }, + { + name: "Show notification details", + category: "lockscreen", + categoryName: "Lockscreen", + section: "Lockscreen", + keywords: ["notification", "hide", "privacy"], + }, { name: "Blur amount", category: "lockscreen", From 62ec1b9f334cba7cb7e85196a0e27970640e0c78 Mon Sep 17 00:00:00 2001 From: zach Date: Tue, 19 May 2026 16:15:39 +0200 Subject: [PATCH 19/20] cleanup unneeded logging --- Drawers/DrawingInput.qml | 1 - Greeter/Helpers/SystemUsage.qml | 1 - Greeter/Helpers/Users.qml | 64 +++++++++-------------------- Modules/Launcher/Services/Apps.qml | 1 - Modules/Settings/SettingsSearch.qml | 2 - Modules/Shortcuts.qml | 1 - 6 files changed, 20 insertions(+), 50 deletions(-) diff --git a/Drawers/DrawingInput.qml b/Drawers/DrawingInput.qml index 3fdcd33..0851a66 100644 --- a/Drawers/DrawingInput.qml +++ b/Drawers/DrawingInput.qml @@ -32,7 +32,6 @@ CustomMouseArea { if (root.visibilities.isDrawing && (event.buttons & Qt.LeftButton)) { root.drawing.points.push(Qt.point(x, y)); - console.log(root.drawing.points); root.drawing.requestPaint(); } diff --git a/Greeter/Helpers/SystemUsage.qml b/Greeter/Helpers/SystemUsage.qml index e613060..11813f3 100644 --- a/Greeter/Helpers/SystemUsage.qml +++ b/Greeter/Helpers/SystemUsage.qml @@ -346,7 +346,6 @@ Singleton { stdout: StdioCollector { onStreamFinished: { - console.log("this is running"); if (root.gpuType === "GENERIC") { const percs = text.trim().split("\n"); const sum = percs.reduce((acc, d) => acc + parseInt(d, 10), 0); diff --git a/Greeter/Helpers/Users.qml b/Greeter/Helpers/Users.qml index d2ffdc9..77a9935 100644 --- a/Greeter/Helpers/Users.qml +++ b/Greeter/Helpers/Users.qml @@ -7,23 +7,30 @@ import QtQuick Singleton { id: root - // The list of users that can log in graphically - // Each user object has: username, uid, home, shell, gecos (full name), face (avatar path) + readonly property string defaultUserFile: "/etc/zshell-greeter/default-user" + property int selectedIndex: 0 + readonly property var selectedUser: selectedIndex >= 0 && selectedIndex < users.length ? users[selectedIndex] : null + readonly property string selectedUsername: selectedUser ? selectedUser.username : "" property var users: [] - // The currently selected user index - property int selectedIndex: 0 + function saveDefaultUser(): void { + if (selectedUser) { + defaultUserStorage.setText(selectedUser.username); + } + } - // The currently selected user object (or null if none) - readonly property var selectedUser: selectedIndex >= 0 && selectedIndex < users.length ? users[selectedIndex] : null + function selectNext(): void { + if (users.length === 0) + return; + selectedIndex = (selectedIndex + 1) % users.length; + } - // Convenience property for the selected username - readonly property string selectedUsername: selectedUser ? selectedUser.username : "" + function selectPrevious(): void { + if (users.length === 0) + return; + selectedIndex = (selectedIndex - 1 + users.length) % users.length; + } - // Path to store the default user preference - readonly property string defaultUserFile: "/etc/zshell-greeter/default-user" - - // Select a user by username function selectUser(username: string): bool { for (let i = 0; i < users.length; i++) { if (users[i].username === username) { @@ -34,28 +41,6 @@ Singleton { return false; } - // Select the next user in the list (wraps around) - function selectNext(): void { - if (users.length === 0) - return; - selectedIndex = (selectedIndex + 1) % users.length; - } - - // Select the previous user in the list (wraps around) - function selectPrevious(): void { - if (users.length === 0) - return; - selectedIndex = (selectedIndex - 1 + users.length) % users.length; - } - - // Save the current user as the default for next login - function saveDefaultUser(): void { - if (selectedUser) { - defaultUserStorage.setText(selectedUser.username); - } - } - - // Process to fetch the list of graphical users Process { id: userLister @@ -67,13 +52,10 @@ Singleton { try { root.users = JSON.parse(text); - // If we have users and no selection yet, try to select the default user if (root.users.length > 0) { - // Try to load the default user if (defaultUserStorage.loaded) { const defaultUsername = defaultUserStorage.text().trim(); if (defaultUsername && !root.selectUser(defaultUsername)) { - // Default user not found, select first user root.selectedIndex = 0; } } else { @@ -87,15 +69,14 @@ Singleton { } } - // FileView for persisting the default user FileView { id: defaultUserStorage path: root.defaultUserFile preload: true + onLoadFailed: {} onLoaded: { - // If users are already loaded, try to select the default user if (root.users.length > 0) { const defaultUsername = text().trim(); if (defaultUsername) { @@ -103,10 +84,5 @@ Singleton { } } } - - onLoadFailed: { - // File doesn't exist yet, that's fine - we'll create it on first save - console.log("No default user file found, will use first user"); - } } } diff --git a/Modules/Launcher/Services/Apps.qml b/Modules/Launcher/Services/Apps.qml index 3d4446c..dbd10fd 100644 --- a/Modules/Launcher/Services/Apps.qml +++ b/Modules/Launcher/Services/Apps.qml @@ -13,7 +13,6 @@ Searcher { function launch(entry: DesktopEntry): void { appDb.incrementFrequency(entry.id); - console.log(root.command); if (entry.runInTerminal) Quickshell.execDetached({ diff --git a/Modules/Settings/SettingsSearch.qml b/Modules/Settings/SettingsSearch.qml index cd69af7..ac7906e 100644 --- a/Modules/Settings/SettingsSearch.qml +++ b/Modules/Settings/SettingsSearch.qml @@ -51,8 +51,6 @@ Item { implicitHeight: searchContainer.implicitHeight implicitWidth: 200 - Component.onCompleted: console.log(root.height) - Shortcut { sequence: "/" diff --git a/Modules/Shortcuts.qml b/Modules/Shortcuts.qml index 82a27d3..0a57d75 100644 --- a/Modules/Shortcuts.qml +++ b/Modules/Shortcuts.qml @@ -19,7 +19,6 @@ Scope { if (!root.launcherInterrupted && !root.hasFullscreen) { const visibilities = Visibilities.getForActive(); visibilities.launcher = !visibilities.launcher; - console.log(root.launcherInterrupted); } root.launcherInterrupted = false; } From 24d5584b9828b8f2c0a8778b92cc98afc19e6a57 Mon Sep 17 00:00:00 2001 From: zach Date: Tue, 19 May 2026 22:12:37 +0200 Subject: [PATCH 20/20] wallpaper now uses Image (hopefully temporarily) --- Helpers/Wallpapers.qml | 1 + .../Settings/Controls/WallpaperCropper.qml | 5 +- Modules/Wallpaper/WallBackground-old.qml | 80 +++++++++++++++++++ Modules/Wallpaper/WallBackground.qml | 72 ++++------------- Modules/Wallpaper/Wallpaper.qml | 1 + 5 files changed, 102 insertions(+), 57 deletions(-) create mode 100644 Modules/Wallpaper/WallBackground-old.qml diff --git a/Helpers/Wallpapers.qml b/Helpers/Wallpapers.qml index 3febca8..c22920d 100644 --- a/Helpers/Wallpapers.qml +++ b/Helpers/Wallpapers.qml @@ -14,6 +14,7 @@ Searcher { property string actualCurrent: WallpaperPath.currentWallpaperPath readonly property string current: showPreview ? previewPath : actualCurrent property string previewPath + property bool recentlyChanged property bool showPreview: false function preview(path: string): void { diff --git a/Modules/Settings/Controls/WallpaperCropper.qml b/Modules/Settings/Controls/WallpaperCropper.qml index 7090ff5..a17acda 100644 --- a/Modules/Settings/Controls/WallpaperCropper.qml +++ b/Modules/Settings/Controls/WallpaperCropper.qml @@ -18,8 +18,8 @@ Item { property real displayY: (height - paintedHeight) * 0.5 property real scaleX: sourceW / displayW property real scaleY: sourceH / displayH - property real sourceH: sourceSize.height - property real sourceW: sourceSize.width + property real sourceH: Quickshell.screens[0].height + property real sourceW: Quickshell.screens[0].width anchors.fill: parent fillMode: Image.PreserveAspectFit @@ -85,6 +85,7 @@ Item { updateCrop(mouse.x, mouse.y); } onReleased: { + Wallpapers.recentlyChanged = false; Config.background.sourceClipX = cropRect.sourceRect.x; Config.background.sourceClipY = cropRect.sourceRect.y; Config.background.sourceClipW = cropRect.sourceRect.width; diff --git a/Modules/Wallpaper/WallBackground-old.qml b/Modules/Wallpaper/WallBackground-old.qml new file mode 100644 index 0000000..b8bb649 --- /dev/null +++ b/Modules/Wallpaper/WallBackground-old.qml @@ -0,0 +1,80 @@ +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 b8bb649..9c95859 100644 --- a/Modules/Wallpaper/WallBackground.qml +++ b/Modules/Wallpaper/WallBackground.qml @@ -1,5 +1,6 @@ pragma ComponentBehavior: Bound +import Quickshell import QtQuick import qs.Components import qs.Helpers @@ -8,73 +9,34 @@ import qs.Config Item { id: root - property Image current: one + required property ShellScreen screen 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 { + 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 + opacity: 1 retainWhileLoading: true - scale: Wallpapers.showPreview ? 1 : 0.8 - sourceClipRect: Qt.rect(Config.background.sourceClipX, Config.background.sourceClipY, Config.background.sourceClipW, Config.background.sourceClipH) + source: root.source + sourceClipRect: Wallpapers.recentlyChanged ? null : Qt.rect(Config.background.sourceClipX, Config.background.sourceClipY, Config.background.sourceClipW, Config.background.sourceClipH) + sourceSize.height: root.screen.height + sourceSize.width: root.screen.width - 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; + onSourceChanged: { + if (Wallpapers.recentlyChanged) { + Config.background.sourceClipH = 0; + Config.background.sourceClipW = 0; + Config.background.sourceClipY = 0; + Config.background.sourceClipX = 0; + Config.background.zoom = 1.0; + Config.save(); } + Wallpapers.recentlyChanged = true; } } } diff --git a/Modules/Wallpaper/Wallpaper.qml b/Modules/Wallpaper/Wallpaper.qml index 44c44d2..9fac581 100644 --- a/Modules/Wallpaper/Wallpaper.qml +++ b/Modules/Wallpaper/Wallpaper.qml @@ -30,6 +30,7 @@ Loader { } WallBackground { + screen: root.screen } Loader {