From e8555061aba2708198e43d2eeb09038500271436 Mon Sep 17 00:00:00 2001 From: 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> Date: Fri, 27 Mar 2026 20:58:00 +1100 Subject: [PATCH] fix: sidebar bulge at connection with utilities Basically make sidebar and utilities exclude each other from merging and manually animate corner radius Also format c++ --- modules/drawers/Drawers.qml | 17 +++ plugin/src/Caelestia/Blobs/blobgroup.cpp | 13 +- plugin/src/Caelestia/Blobs/blobgroup.hpp | 3 +- .../src/Caelestia/Blobs/blobinvertedrect.cpp | 49 ++++--- .../src/Caelestia/Blobs/blobinvertedrect.hpp | 12 +- plugin/src/Caelestia/Blobs/blobmaterial.cpp | 10 +- plugin/src/Caelestia/Blobs/blobmaterial.hpp | 11 +- plugin/src/Caelestia/Blobs/blobrect.cpp | 138 +++++++++++++++--- plugin/src/Caelestia/Blobs/blobrect.hpp | 55 ++++++- plugin/src/Caelestia/Blobs/blobshape.cpp | 119 +++++++-------- plugin/src/Caelestia/Blobs/blobshape.hpp | 10 +- plugin/src/Caelestia/Blobs/shaders/blob.frag | 7 +- 12 files changed, 286 insertions(+), 158 deletions(-) diff --git a/modules/drawers/Drawers.qml b/modules/drawers/Drawers.qml index 5211fdcf..23be6f03 100644 --- a/modules/drawers/Drawers.qml +++ b/modules/drawers/Drawers.qml @@ -166,6 +166,15 @@ Variants { panel: panels.sidebar bar: bar deformAmount: 0 + height: panel.height + 1 + exclude: [utilsBg] + bottomLeftRadius: panels.sidebar.visible ? 0 : radius + + Behavior on bottomLeftRadius { + Anim { + duration: Appearance.anim.durations.expressiveDefaultSpatial + } + } } PanelBg { @@ -193,6 +202,14 @@ Variants { blobGroup: blobGroup panel: panels.utilities bar: bar + exclude: [sidebarBg] + topLeftRadius: panels.sidebar.visible ? 0 : radius + + Behavior on topLeftRadius { + Anim { + duration: Appearance.anim.durations.expressiveDefaultSpatial + } + } } PanelBg { diff --git a/plugin/src/Caelestia/Blobs/blobgroup.cpp b/plugin/src/Caelestia/Blobs/blobgroup.cpp index ab4a31e6..a4703c86 100644 --- a/plugin/src/Caelestia/Blobs/blobgroup.cpp +++ b/plugin/src/Caelestia/Blobs/blobgroup.cpp @@ -74,20 +74,15 @@ void BlobGroup::markShapeDirty(BlobShape* source) { // 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), + 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)); 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 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/plugin/src/Caelestia/Blobs/blobgroup.hpp b/plugin/src/Caelestia/Blobs/blobgroup.hpp index b2e83edb..e09125a9 100644 --- a/plugin/src/Caelestia/Blobs/blobgroup.hpp +++ b/plugin/src/Caelestia/Blobs/blobgroup.hpp @@ -11,8 +11,7 @@ class BlobInvertedRect; class BlobGroup : public QObject { Q_OBJECT QML_ELEMENT - Q_PROPERTY(qreal smoothing READ smoothing WRITE setSmoothing NOTIFY - smoothingChanged) + Q_PROPERTY(qreal smoothing READ smoothing WRITE setSmoothing NOTIFY smoothingChanged) Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) public: diff --git a/plugin/src/Caelestia/Blobs/blobinvertedrect.cpp b/plugin/src/Caelestia/Blobs/blobinvertedrect.cpp index 3e392b4a..46ee73a4 100644 --- a/plugin/src/Caelestia/Blobs/blobinvertedrect.cpp +++ b/plugin/src/Caelestia/Blobs/blobinvertedrect.cpp @@ -13,21 +13,36 @@ BlobInvertedRect::BlobInvertedRect(QQuickItem* parent) static void setFrameIndices(quint16* idx) { // Top strip: 0-1-4, 1-5-4 - idx[0] = 0; idx[1] = 1; idx[2] = 4; - idx[3] = 1; idx[4] = 5; idx[5] = 4; + idx[0] = 0; + idx[1] = 1; + idx[2] = 4; + idx[3] = 1; + idx[4] = 5; + idx[5] = 4; // Right strip: 1-2-5, 2-6-5 - idx[6] = 1; idx[7] = 2; idx[8] = 5; - idx[9] = 2; idx[10] = 6; idx[11] = 5; + idx[6] = 1; + idx[7] = 2; + idx[8] = 5; + idx[9] = 2; + idx[10] = 6; + idx[11] = 5; // Bottom strip: 2-3-6, 3-7-6 - idx[12] = 2; idx[13] = 3; idx[14] = 6; - idx[15] = 3; idx[16] = 7; idx[17] = 6; + idx[12] = 2; + idx[13] = 3; + idx[14] = 6; + idx[15] = 3; + idx[16] = 7; + idx[17] = 6; // Left strip: 3-0-7, 0-4-7 - idx[18] = 3; idx[19] = 0; idx[20] = 7; - idx[21] = 0; idx[22] = 4; idx[23] = 7; + idx[18] = 3; + idx[19] = 0; + idx[20] = 7; + idx[21] = 0; + idx[22] = 4; + idx[23] = 7; } -QSGNode* BlobInvertedRect::updatePaintNode( - QSGNode* oldNode, UpdatePaintNodeData*) { +QSGNode* BlobInvertedRect::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) { if (!m_group) { delete oldNode; return nullptr; @@ -55,9 +70,8 @@ QSGNode* BlobInvertedRect::updatePaintNode( delete oldNode; node = new QSGGeometryNode; - auto* geometry = new QSGGeometry( - QSGGeometry::defaultAttributes_TexturedPoint2D(), 8, 24, - QSGGeometry::UnsignedShortType); + auto* geometry = + new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 8, 24, QSGGeometry::UnsignedShortType); geometry->setDrawingMode(QSGGeometry::DrawTriangles); node->setGeometry(geometry); node->setFlag(QSGNode::OwnsGeometry); @@ -105,13 +119,10 @@ QSGNode* BlobInvertedRect::updatePaintNode( material->m_color = m_group->color(); material->m_hasInverted = m_cachedHasInverted ? 1 : 0; material->m_invertedRadius = m_cachedInvertedRadius; - memcpy(material->m_invertedOuter, m_cachedInvertedOuter, - sizeof(m_cachedInvertedOuter)); - memcpy(material->m_invertedInner, m_cachedInvertedInner, - sizeof(m_cachedInvertedInner)); + memcpy(material->m_invertedOuter, m_cachedInvertedOuter, sizeof(m_cachedInvertedOuter)); + memcpy(material->m_invertedInner, m_cachedInvertedInner, sizeof(m_cachedInvertedInner)); - const int count = - static_cast(qMin(m_cachedRects.size(), qsizetype(16))); + const int count = static_cast(qMin(m_cachedRects.size(), qsizetype(16))); material->m_rectCount = count; for (int i = 0; i < count; ++i) material->m_rects[i] = m_cachedRects[i]; diff --git a/plugin/src/Caelestia/Blobs/blobinvertedrect.hpp b/plugin/src/Caelestia/Blobs/blobinvertedrect.hpp index 207244de..f7fa6c0a 100644 --- a/plugin/src/Caelestia/Blobs/blobinvertedrect.hpp +++ b/plugin/src/Caelestia/Blobs/blobinvertedrect.hpp @@ -7,14 +7,10 @@ class BlobInvertedRect : public BlobShape { Q_OBJECT QML_ELEMENT - Q_PROPERTY(qreal borderLeft READ borderLeft WRITE setBorderLeft NOTIFY - borderLeftChanged) - Q_PROPERTY(qreal borderRight READ borderRight WRITE setBorderRight NOTIFY - borderRightChanged) - Q_PROPERTY(qreal borderTop READ borderTop WRITE setBorderTop NOTIFY - borderTopChanged) - Q_PROPERTY(qreal borderBottom READ borderBottom WRITE setBorderBottom NOTIFY - borderBottomChanged) + Q_PROPERTY(qreal borderLeft READ borderLeft WRITE setBorderLeft NOTIFY borderLeftChanged) + Q_PROPERTY(qreal borderRight READ borderRight WRITE setBorderRight NOTIFY borderRightChanged) + Q_PROPERTY(qreal borderTop READ borderTop WRITE setBorderTop NOTIFY borderTopChanged) + Q_PROPERTY(qreal borderBottom READ borderBottom WRITE setBorderBottom NOTIFY borderBottomChanged) public: explicit BlobInvertedRect(QQuickItem* parent = nullptr); diff --git a/plugin/src/Caelestia/Blobs/blobmaterial.cpp b/plugin/src/Caelestia/Blobs/blobmaterial.cpp index f2ead2ed..f71ad1bd 100644 --- a/plugin/src/Caelestia/Blobs/blobmaterial.cpp +++ b/plugin/src/Caelestia/Blobs/blobmaterial.cpp @@ -7,8 +7,7 @@ QSGMaterialType* BlobMaterial::type() const { return &s_type; } -QSGMaterialShader* BlobMaterial::createShader( - QSGRendererInterface::RenderMode) const { +QSGMaterialShader* BlobMaterial::createShader(QSGRendererInterface::RenderMode) const { return new BlobMaterialShader; } @@ -25,8 +24,7 @@ BlobMaterialShader::BlobMaterialShader() { setShaderFileName(FragmentStage, QStringLiteral(":/shaders/blob.frag.qsb")); } -bool BlobMaterialShader::updateUniformData( - RenderState& state, QSGMaterial* newMaterial, QSGMaterial* oldMaterial) { +bool BlobMaterialShader::updateUniformData(RenderState& state, QSGMaterial* newMaterial, QSGMaterial* oldMaterial) { Q_UNUSED(oldMaterial); auto* mat = static_cast(newMaterial); QByteArray* buf = state.uniformData(); @@ -85,13 +83,13 @@ bool BlobMaterialShader::updateUniformData( const auto& r = mat->m_rects[i]; const int base = 160 + i * 80; const float d0[4] = { r.cx, r.cy, r.hw, r.hh }; - const float d1[4] = { r.radius, r.offsetX, r.offsetY, r.minEig }; + const float d1[4] = { 0.0f, 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); memcpy(buf->data() + base + 32, r.invDeform, 16); memcpy(buf->data() + base + 48, d3, 16); - memcpy(buf->data() + base + 64, r.cornerFill, 16); + memcpy(buf->data() + base + 64, r.radius, 16); } return true; diff --git a/plugin/src/Caelestia/Blobs/blobmaterial.hpp b/plugin/src/Caelestia/Blobs/blobmaterial.hpp index ef80fea1..85dbedaa 100644 --- a/plugin/src/Caelestia/Blobs/blobmaterial.hpp +++ b/plugin/src/Caelestia/Blobs/blobmaterial.hpp @@ -6,22 +6,20 @@ struct BlobRectData { float cx = 0, cy = 0, hw = 0, hh = 0; - float radius = 0; float offsetX = 0, offsetY = 0; float minEig = 1.0f; // Inverse of 2x2 deformation matrix, column-major for GLSL float invDeform[4] = { 1, 0, 0, 1 }; // Screen-space AABB half-extents of the deformed rect float screenHalfX = 0, screenHalfY = 0; - // Pre-computed corner fill factors (tr, br, bl, tl) - float cornerFill[4] = { 1, 1, 1, 1 }; + // Effective per-corner radii (tr, br, bl, tl), pre-computed on CPU + float radius[4] = { 0, 0, 0, 0 }; }; class BlobMaterial : public QSGMaterial { public: QSGMaterialType* type() const override; - QSGMaterialShader* createShader( - QSGRendererInterface::RenderMode) const override; + QSGMaterialShader* createShader(QSGRendererInterface::RenderMode) const override; int compare(const QSGMaterial* other) const override; float m_paddedX = 0; @@ -42,6 +40,5 @@ public: class BlobMaterialShader : public QSGMaterialShader { public: BlobMaterialShader(); - bool updateUniformData(RenderState& state, QSGMaterial* newMaterial, - QSGMaterial* oldMaterial) override; + bool updateUniformData(RenderState& state, QSGMaterial* newMaterial, QSGMaterial* oldMaterial) override; }; diff --git a/plugin/src/Caelestia/Blobs/blobrect.cpp b/plugin/src/Caelestia/Blobs/blobrect.cpp index ff021203..efc1e7f0 100644 --- a/plugin/src/Caelestia/Blobs/blobrect.cpp +++ b/plugin/src/Caelestia/Blobs/blobrect.cpp @@ -17,10 +17,8 @@ void BlobRect::updatePolish() { if (m_physicsActive) { // Check if deformation is visually imperceptible - float totalDelta = std::abs(m_dm00 - 1.0f) + std::abs(m_dm01) - + std::abs(m_dm11 - 1.0f); - float totalVel = - std::abs(m_dmVel00) + std::abs(m_dmVel01) + std::abs(m_dmVel11); + float totalDelta = std::abs(m_dm00 - 1.0f) + std::abs(m_dm01) + std::abs(m_dm11 - 1.0f); + float totalVel = std::abs(m_dmVel00) + std::abs(m_dmVel01) + std::abs(m_dmVel11); if (totalDelta < 0.004f && totalVel < 0.05f) { // Snap to rest, no visible deformation @@ -62,10 +60,8 @@ void BlobRect::updatePhysics() { return; } - const float velX = - static_cast(scenePos.x() - m_prevScenePos.x()) / dt; - const float velY = - static_cast(scenePos.y() - m_prevScenePos.y()) / dt; + const float velX = static_cast(scenePos.x() - m_prevScenePos.x()) / dt; + const float velY = static_cast(scenePos.y() - m_prevScenePos.y()) / dt; m_prevScenePos = scenePos; const float speed = std::sqrt(velX * velX + velY * velY); @@ -86,8 +82,7 @@ void BlobRect::updatePhysics() { float target11 = 1.0f; if (speed > 5.0f) { - const float targetStretch = - 1.0f + std::min(speed * kStretchFactor, kMaxStretch); + const float targetStretch = 1.0f + std::min(speed * kStretchFactor, kMaxStretch); const float targetCompress = 1.0f / targetStretch; const float cosA = velX / speed; @@ -105,35 +100,132 @@ void BlobRect::updatePhysics() { const float kStiffness = static_cast(m_stiffness); const float kDamping = static_cast(m_damping); - const float accel00 = - -kStiffness * (m_dm00 - target00) - kDamping * m_dmVel00; + const float accel00 = -kStiffness * (m_dm00 - target00) - kDamping * m_dmVel00; m_dmVel00 += accel00 * dt; m_dm00 += m_dmVel00 * dt; - const float accel01 = - -kStiffness * (m_dm01 - target01) - kDamping * m_dmVel01; + const float accel01 = -kStiffness * (m_dm01 - target01) - kDamping * m_dmVel01; m_dmVel01 += accel01 * dt; m_dm01 += m_dmVel01 * dt; - const float accel11 = - -kStiffness * (m_dm11 - target11) - kDamping * m_dmVel11; + const float accel11 = -kStiffness * (m_dm11 - target11) - kDamping * m_dmVel11; m_dmVel11 += accel11 * dt; m_dm11 += m_dmVel11 * dt; - m_deformMatrix = QMatrix4x4( - m_dm00, m_dm01, 0, 0, m_dm01, m_dm11, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + m_deformMatrix = QMatrix4x4(m_dm00, m_dm01, 0, 0, m_dm01, m_dm11, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); updateCenteredDeformMatrix(); checkAtRest(speed); } +void BlobRect::setTopLeftRadius(qreal r) { + if (!qFuzzyCompare(m_topLeftRadius, r)) { + m_topLeftRadius = r; + emit topLeftRadiusChanged(); + if (m_group) + m_group->markDirty(); + } +} + +void BlobRect::setTopRightRadius(qreal r) { + if (!qFuzzyCompare(m_topRightRadius, r)) { + m_topRightRadius = r; + emit topRightRadiusChanged(); + if (m_group) + m_group->markDirty(); + } +} + +void BlobRect::setBottomLeftRadius(qreal r) { + if (!qFuzzyCompare(m_bottomLeftRadius, r)) { + m_bottomLeftRadius = r; + emit bottomLeftRadiusChanged(); + if (m_group) + m_group->markDirty(); + } +} + +void BlobRect::setBottomRightRadius(qreal r) { + if (!qFuzzyCompare(m_bottomRightRadius, r)) { + m_bottomRightRadius = r; + emit bottomRightRadiusChanged(); + if (m_group) + m_group->markDirty(); + } +} + +void BlobRect::cornerRadii(float out[4]) const { + const auto base = static_cast(m_radius); + out[0] = m_topRightRadius >= 0 ? static_cast(m_topRightRadius) : base; + out[1] = m_bottomRightRadius >= 0 ? static_cast(m_bottomRightRadius) : base; + out[2] = m_bottomLeftRadius >= 0 ? static_cast(m_bottomLeftRadius) : base; + out[3] = m_topLeftRadius >= 0 ? static_cast(m_topLeftRadius) : base; +} + +bool BlobRect::isExcluded(const BlobShape* other) const { + for (const auto& ptr : m_exclude) { + if (ptr == other) + return true; + } + return false; +} + +QQmlListProperty BlobRect::exclude() { + return QQmlListProperty( + this, nullptr, &excludeAppend, &excludeCount, &excludeAt, &excludeClear, &excludeReplace, &excludeRemoveLast); +} + +void BlobRect::excludeAppend(QQmlListProperty* prop, BlobRect* rect) { + auto* self = static_cast(prop->object); + self->m_exclude.append(rect); + if (self->m_group) + self->m_group->markDirty(); + emit self->excludeChanged(); +} + +qsizetype BlobRect::excludeCount(QQmlListProperty* prop) { + auto* self = static_cast(prop->object); + return self->m_exclude.size(); +} + +BlobRect* BlobRect::excludeAt(QQmlListProperty* prop, qsizetype index) { + auto* self = static_cast(prop->object); + return self->m_exclude.at(index); +} + +void BlobRect::excludeClear(QQmlListProperty* prop) { + auto* self = static_cast(prop->object); + if (self->m_exclude.isEmpty()) + return; + self->m_exclude.clear(); + if (self->m_group) + self->m_group->markDirty(); + emit self->excludeChanged(); +} + +void BlobRect::excludeReplace(QQmlListProperty* prop, qsizetype index, BlobRect* rect) { + auto* self = static_cast(prop->object); + self->m_exclude[index] = rect; + if (self->m_group) + self->m_group->markDirty(); + emit self->excludeChanged(); +} + +void BlobRect::excludeRemoveLast(QQmlListProperty* prop) { + auto* self = static_cast(prop->object); + if (self->m_exclude.isEmpty()) + return; + self->m_exclude.removeLast(); + if (self->m_group) + self->m_group->markDirty(); + emit self->excludeChanged(); +} + void BlobRect::checkAtRest(float speed) { constexpr float kEpsilon = 0.002f; - const bool atRest = - std::abs(m_dm00 - 1.0f) < kEpsilon && std::abs(m_dm01) < kEpsilon && - std::abs(m_dm11 - 1.0f) < kEpsilon && std::abs(m_dmVel00) < kEpsilon && - std::abs(m_dmVel01) < kEpsilon && std::abs(m_dmVel11) < kEpsilon && - speed < 5.0f; + const bool atRest = std::abs(m_dm00 - 1.0f) < kEpsilon && std::abs(m_dm01) < kEpsilon && + std::abs(m_dm11 - 1.0f) < kEpsilon && std::abs(m_dmVel00) < kEpsilon && + std::abs(m_dmVel01) < kEpsilon && std::abs(m_dmVel11) < kEpsilon && speed < 5.0f; if (atRest) { m_dm00 = 1.0f; diff --git a/plugin/src/Caelestia/Blobs/blobrect.hpp b/plugin/src/Caelestia/Blobs/blobrect.hpp index 86d4666d..d2d6ad45 100644 --- a/plugin/src/Caelestia/Blobs/blobrect.hpp +++ b/plugin/src/Caelestia/Blobs/blobrect.hpp @@ -3,17 +3,22 @@ #include "blobshape.hpp" #include +#include #include +#include class BlobRect : public BlobShape { Q_OBJECT QML_ELEMENT - Q_PROPERTY(qreal stiffness READ stiffness WRITE setStiffness NOTIFY - stiffnessChanged) + Q_PROPERTY(qreal stiffness READ stiffness WRITE setStiffness NOTIFY stiffnessChanged) + Q_PROPERTY(qreal damping READ damping WRITE setDamping NOTIFY dampingChanged) + Q_PROPERTY(qreal deformScale READ deformScale WRITE setDeformScale NOTIFY deformScaleChanged) + Q_PROPERTY(QQmlListProperty exclude READ exclude NOTIFY excludeChanged) + Q_PROPERTY(qreal topLeftRadius READ topLeftRadius WRITE setTopLeftRadius NOTIFY topLeftRadiusChanged) + Q_PROPERTY(qreal topRightRadius READ topRightRadius WRITE setTopRightRadius NOTIFY topRightRadiusChanged) + Q_PROPERTY(qreal bottomLeftRadius READ bottomLeftRadius WRITE setBottomLeftRadius NOTIFY bottomLeftRadiusChanged) Q_PROPERTY( - qreal damping READ damping WRITE setDamping NOTIFY dampingChanged) - Q_PROPERTY(qreal deformScale READ deformScale WRITE setDeformScale NOTIFY - deformScaleChanged) + qreal bottomRightRadius READ bottomRightRadius WRITE setBottomRightRadius NOTIFY bottomRightRadiusChanged) public: explicit BlobRect(QQuickItem* parent = nullptr); @@ -46,10 +51,36 @@ public: } } + QQmlListProperty exclude(); + + bool isExcluded(const BlobShape* other) const override; + void cornerRadii(float out[4]) const override; + + qreal topLeftRadius() const { return m_topLeftRadius; } + + void setTopLeftRadius(qreal r); + + qreal topRightRadius() const { return m_topRightRadius; } + + void setTopRightRadius(qreal r); + + qreal bottomLeftRadius() const { return m_bottomLeftRadius; } + + void setBottomLeftRadius(qreal r); + + qreal bottomRightRadius() const { return m_bottomRightRadius; } + + void setBottomRightRadius(qreal r); + signals: void stiffnessChanged(); void dampingChanged(); void deformScaleChanged(); + void excludeChanged(); + void topLeftRadiusChanged(); + void topRightRadiusChanged(); + void bottomLeftRadiusChanged(); + void bottomRightRadiusChanged(); protected: void updatePolish() override; @@ -78,4 +109,18 @@ private: qreal m_stiffness = 200.0; qreal m_damping = 16.0; qreal m_deformScale = 0.0005; + + qreal m_topLeftRadius = -1; + qreal m_topRightRadius = -1; + qreal m_bottomLeftRadius = -1; + qreal m_bottomRightRadius = -1; + + QList> m_exclude; + + static void excludeAppend(QQmlListProperty* prop, BlobRect* rect); + static qsizetype excludeCount(QQmlListProperty* prop); + static BlobRect* excludeAt(QQmlListProperty* prop, qsizetype index); + static void excludeClear(QQmlListProperty* prop); + static void excludeReplace(QQmlListProperty* prop, qsizetype index, BlobRect* rect); + static void excludeRemoveLast(QQmlListProperty* prop); }; diff --git a/plugin/src/Caelestia/Blobs/blobshape.cpp b/plugin/src/Caelestia/Blobs/blobshape.cpp index a7938a50..b048c57e 100644 --- a/plugin/src/Caelestia/Blobs/blobshape.cpp +++ b/plugin/src/Caelestia/Blobs/blobshape.cpp @@ -19,8 +19,7 @@ static float deformPadding(const QMatrix4x4& dm, float hw, float hh) { return std::max(extraX, extraY); } -static float cpuSdBox(float px, float py, float cx, float cy, float hw, - float hh) { +static float cpuSdBox(float px, float py, float cx, float cy, float hw, float hh) { const float dx = std::abs(px - cx) - hw; const float dy = std::abs(py - cy) - hh; const float mdx = std::max(dx, 0.0f); @@ -66,8 +65,7 @@ void BlobShape::componentComplete() { registerWithGroup(); } -void BlobShape::geometryChange( - const QRectF& newGeometry, const QRectF& oldGeometry) { +void BlobShape::geometryChange(const QRectF& newGeometry, const QRectF& oldGeometry) { QQuickItem::geometryChange(newGeometry, oldGeometry); updateCenteredDeformMatrix(); if (m_group) { @@ -92,6 +90,14 @@ void BlobShape::updateCenteredDeformMatrix() { emit deformMatrixChanged(); } +void BlobShape::cornerRadii(float out[4]) const { + const auto r = static_cast(m_radius); + out[0] = r; + out[1] = r; + out[2] = r; + out[3] = r; +} + void BlobShape::registerWithGroup() { if (m_group) m_group->addShape(this); @@ -127,19 +133,15 @@ void BlobShape::updatePolish() { 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)); + m_localPaddedRect = QRectF(static_cast(-totalPad), static_cast(-totalPad), + width() + 2.0 * static_cast(totalPad), height() + 2.0 * static_cast(totalPad)); } // Filter nearby normal rects m_cachedRects.clear(); m_cachedMyIndex = -2; - const QRectF myPadded(static_cast(m_cachedPaddedX), - static_cast(m_cachedPaddedY), - static_cast(m_cachedPaddedW), - static_cast(m_cachedPaddedH)); + const QRectF myPadded(static_cast(m_cachedPaddedX), static_cast(m_cachedPaddedY), + static_cast(m_cachedPaddedW), static_cast(m_cachedPaddedH)); for (BlobShape* other : m_group->shapes()) { if (other->isInvertedRect()) @@ -149,6 +151,9 @@ void BlobShape::updatePolish() { if (other->width() <= 0 || other->height() <= 0) continue; + if (isExcluded(other)) + continue; + const QPointF otherScene = other->mapToScene(QPointF(0, 0)); bool include = false; @@ -157,12 +162,9 @@ void BlobShape::updatePolish() { } 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), + 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); } @@ -180,14 +182,13 @@ void BlobShape::updatePolish() { r.cy = static_cast(otherScene.y() + other->height() / 2.0); r.hw = static_cast(other->width() / 2.0); r.hh = static_cast(other->height() / 2.0); - r.radius = static_cast(other->radius()); + other->cornerRadii(r.radius); r.offsetX = dm(0, 3); r.offsetY = dm(1, 3); // Pre-compute inverse deformation matrix const float det = a * d - c * b; - const float invDet = - std::abs(det) > 1e-6f ? 1.0f / det : 1.0f; + const float invDet = std::abs(det) > 1e-6f ? 1.0f / det : 1.0f; r.invDeform[0] = d * invDet; r.invDeform[1] = -b * invDet; r.invDeform[2] = -c * invDet; @@ -218,25 +219,15 @@ void BlobShape::updatePolish() { auto* inv = m_group->invertedRect(); if (inv) { const QPointF invScene = inv->mapToScene(QPointF(0, 0)); - const float outerCX = - static_cast(invScene.x() + inv->width() / 2.0); - const float outerCY = - static_cast(invScene.y() + inv->height() / 2.0); + const float outerCX = static_cast(invScene.x() + inv->width() / 2.0); + const float outerCY = static_cast(invScene.y() + inv->height() / 2.0); const float outerHW = static_cast(inv->width() / 2.0); const float outerHH = static_cast(inv->height() / 2.0); - const float innerCX = - outerCX + - static_cast((inv->borderLeft() - inv->borderRight()) / 2.0); - const float innerCY = - outerCY + - static_cast((inv->borderTop() - inv->borderBottom()) / 2.0); - const float innerHW = - outerHW - - static_cast((inv->borderLeft() + inv->borderRight()) / 2.0); - const float innerHH = - outerHH - - static_cast((inv->borderTop() + inv->borderBottom()) / 2.0); + const float innerCX = outerCX + static_cast((inv->borderLeft() - inv->borderRight()) / 2.0); + const float innerCY = outerCY + static_cast((inv->borderTop() - inv->borderBottom()) / 2.0); + const float innerHW = outerHW - static_cast((inv->borderLeft() + inv->borderRight()) / 2.0); + const float innerHH = outerHH - static_cast((inv->borderTop() + inv->borderBottom()) / 2.0); // Check if this rect is near the border (within 2x smoothing of inner edge) bool nearBorder = isInvertedRect(); @@ -247,10 +238,8 @@ void BlobShape::updatePolish() { const float myHW = m_cachedPaddedW * 0.5f; const float myHH = m_cachedPaddedH * 0.5f; // Near border if any edge of padded rect is within margin of inner edge - nearBorder = (myCX - myHW < innerCX - innerHW + margin) || - (myCX + myHW > innerCX + innerHW - margin) || - (myCY - myHH < innerCY - innerHH + margin) || - (myCY + myHH > innerCY + innerHH - margin); + nearBorder = (myCX - myHW < innerCX - innerHW + margin) || (myCX + myHW > innerCX + innerHW - margin) || + (myCY - myHH < innerCY - innerHH + margin) || (myCY + myHH > innerCY + innerHH - margin); } if (nearBorder) { @@ -269,8 +258,9 @@ void BlobShape::updatePolish() { } } - // Pre-compute corner fill factors (moves O(N²) work from GPU to CPU) + // Pre-compute effective per-corner radii (moves O(N²) work from GPU to CPU) const float smoothFactor = pad; + constexpr float minR = 2.0f; const auto rectCount = m_cachedRects.size(); for (qsizetype i = 0; i < rectCount; ++i) { auto& ri = m_cachedRects[i]; @@ -285,14 +275,10 @@ void BlobShape::updatePolish() { if (j == i) 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))); - fBl = std::min(fBl, cpuSmoothstep(0.0f, smoothFactor, - cpuSdBox(cBlX, cBlY, rj.cx, rj.cy, rj.hw, rj.hh))); - fTl = std::min(fTl, cpuSmoothstep(0.0f, smoothFactor, - cpuSdBox(cTlX, cTlY, rj.cx, rj.cy, rj.hw, rj.hh))); + 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))); + fBl = std::min(fBl, cpuSmoothstep(0.0f, smoothFactor, cpuSdBox(cBlX, cBlY, rj.cx, rj.cy, rj.hw, rj.hh))); + fTl = std::min(fTl, cpuSmoothstep(0.0f, smoothFactor, cpuSdBox(cTlX, cTlY, rj.cx, rj.cy, rj.hw, rj.hh))); } if (m_cachedHasInverted) { @@ -300,20 +286,17 @@ void BlobShape::updatePolish() { const float icy = m_cachedInvertedInner[1]; const float ihw = m_cachedInvertedInner[2]; const float ihh = m_cachedInvertedInner[3]; - fTr = std::min(fTr, cpuSmoothstep(0.0f, smoothFactor, - -cpuSdBox(cTrX, cTrY, icx, icy, ihw, ihh))); - fBr = std::min(fBr, cpuSmoothstep(0.0f, smoothFactor, - -cpuSdBox(cBrX, cBrY, icx, icy, ihw, ihh))); - fBl = std::min(fBl, cpuSmoothstep(0.0f, smoothFactor, - -cpuSdBox(cBlX, cBlY, icx, icy, ihw, ihh))); - fTl = std::min(fTl, cpuSmoothstep(0.0f, smoothFactor, - -cpuSdBox(cTlX, cTlY, icx, icy, ihw, ihh))); + fTr = std::min(fTr, cpuSmoothstep(0.0f, smoothFactor, -cpuSdBox(cTrX, cTrY, icx, icy, ihw, ihh))); + fBr = std::min(fBr, cpuSmoothstep(0.0f, smoothFactor, -cpuSdBox(cBrX, cBrY, icx, icy, ihw, ihh))); + fBl = std::min(fBl, cpuSmoothstep(0.0f, smoothFactor, -cpuSdBox(cBlX, cBlY, icx, icy, ihw, ihh))); + fTl = std::min(fTl, cpuSmoothstep(0.0f, smoothFactor, -cpuSdBox(cTlX, cTlY, icx, icy, ihw, ihh))); } - ri.cornerFill[0] = fTr; - ri.cornerFill[1] = fBr; - ri.cornerFill[2] = fBl; - ri.cornerFill[3] = fTl; + // Combine base radii with fill factors into effective per-corner radii + ri.radius[0] = std::max(ri.radius[0] * fTr, minR); + ri.radius[1] = std::max(ri.radius[1] * fBr, minR); + ri.radius[2] = std::max(ri.radius[2] * fBl, minR); + ri.radius[3] = std::max(ri.radius[3] * fTl, minR); } } @@ -327,8 +310,7 @@ QSGNode* BlobShape::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) { if (!node) { node = new QSGGeometryNode; - auto* geometry = new QSGGeometry( - QSGGeometry::defaultAttributes_TexturedPoint2D(), 4); + auto* geometry = new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4); geometry->setDrawingMode(QSGGeometry::DrawTriangleStrip); node->setGeometry(geometry); node->setFlag(QSGNode::OwnsGeometry); @@ -366,13 +348,10 @@ QSGNode* BlobShape::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) { material->m_color = m_group->color(); material->m_hasInverted = m_cachedHasInverted ? 1 : 0; material->m_invertedRadius = m_cachedInvertedRadius; - memcpy(material->m_invertedOuter, m_cachedInvertedOuter, - sizeof(m_cachedInvertedOuter)); - memcpy(material->m_invertedInner, m_cachedInvertedInner, - sizeof(m_cachedInvertedInner)); + memcpy(material->m_invertedOuter, m_cachedInvertedOuter, sizeof(m_cachedInvertedOuter)); + memcpy(material->m_invertedInner, m_cachedInvertedInner, sizeof(m_cachedInvertedInner)); - const int count = - static_cast(qMin(m_cachedRects.size(), qsizetype(16))); + const int count = static_cast(qMin(m_cachedRects.size(), qsizetype(16))); material->m_rectCount = count; for (int i = 0; i < count; ++i) material->m_rects[i] = m_cachedRects[i]; diff --git a/plugin/src/Caelestia/Blobs/blobshape.hpp b/plugin/src/Caelestia/Blobs/blobshape.hpp index 8c6f1165..6383b644 100644 --- a/plugin/src/Caelestia/Blobs/blobshape.hpp +++ b/plugin/src/Caelestia/Blobs/blobshape.hpp @@ -12,8 +12,7 @@ class BlobShape : public QQuickItem { Q_OBJECT Q_PROPERTY(BlobGroup* group READ group WRITE setGroup NOTIFY groupChanged) Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged) - Q_PROPERTY( - QMatrix4x4 deformMatrix READ deformMatrix NOTIFY deformMatrixChanged) + Q_PROPERTY(QMatrix4x4 deformMatrix READ deformMatrix NOTIFY deformMatrixChanged) friend class BlobGroup; @@ -38,13 +37,16 @@ signals: protected: void componentComplete() override; - void geometryChange( - const QRectF& newGeometry, const QRectF& oldGeometry) override; + void geometryChange(const QRectF& newGeometry, const QRectF& oldGeometry) override; void updatePolish() override; QSGNode* updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) override; virtual bool isInvertedRect() const { return false; } + virtual bool isExcluded(const BlobShape* /*other*/) const { return false; } + + virtual void cornerRadii(float out[4]) const; + virtual void updatePhysics() {} virtual void registerWithGroup(); diff --git a/plugin/src/Caelestia/Blobs/shaders/blob.frag b/plugin/src/Caelestia/Blobs/shaders/blob.frag index f6387a2f..808cf309 100644 --- a/plugin/src/Caelestia/Blobs/shaders/blob.frag +++ b/plugin/src/Caelestia/Blobs/shaders/blob.frag @@ -80,7 +80,7 @@ void main() { vec4 props = rectData[i * 5 + 1]; // radius, offsetX, offsetY, minEig vec4 invDm = rectData[i * 5 + 2]; // inverse deform matrix vec4 sh = rectData[i * 5 + 3]; // screenHalfX, screenHalfY, 0, 0 - vec4 fills = rectData[i * 5 + 4]; // f_tr, f_br, f_bl, f_tl + vec4 radii = rectData[i * 5 + 4]; // effective per-corner radii (tr, br, bl, tl) // Offset center for asymmetric deformation vec2 center = rect.xy + props.yz; @@ -94,10 +94,7 @@ void main() { mat2 invDeform = mat2(invDm.xy, invDm.zw); vec2 transformedPixel = center + invDeform * (pixel - center); - // Use pre-computed corner fill factors - float br = props.x; - float minR = 2.0; - vec4 radii = max(br * fills, vec4(minR)); + // Use pre-computed effective per-corner radii float d = sdRoundedBox4(transformedPixel, center, rect.zw, radii); // Use pre-computed minimum eigenvalue for SDF correction