add corner fill
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 9s
Python / lint-format (pull_request) Successful in 16s
Python / test (pull_request) Successful in 27s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m8s

This commit is contained in:
2026-06-25 18:58:21 +02:00
parent 6a15be7b15
commit 0b92cbb3b4
7 changed files with 124 additions and 47 deletions
+1
View File
@@ -30,6 +30,7 @@ Item {
id: blobGroup
color: DynamicColors.palette.m3surfaceContainerHighest
cornerFill: false
smoothing: Appearance.rounding.medium
Behavior on color {
+8
View File
@@ -29,6 +29,14 @@ void BlobGroup::setColor(const QColor& c) {
markDirty();
}
void BlobGroup::setCornerFill(bool e) {
if (m_cornerFill == e)
return;
m_cornerFill = e;
emit cornerFillChanged();
markDirty();
}
void BlobGroup::addShape(BlobShape* shape) {
if (!shape || m_shapes.contains(shape))
return;
+9
View File
@@ -13,6 +13,7 @@ Q_OBJECT
QML_ELEMENT
Q_PROPERTY(qreal smoothing READ smoothing WRITE setSmoothing NOTIFY smoothingChanged)
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
Q_PROPERTY(bool cornerFill READ cornerFill WRITE setCornerFill NOTIFY cornerFillChanged)
public:
explicit BlobGroup(QObject* parent = nullptr);
@@ -30,6 +31,12 @@ void setSmoothing(qreal s);
void setColor(const QColor& c);
[[nodiscard]] bool cornerFill() const {
return m_cornerFill;
}
void setCornerFill(bool e);
void addShape(BlobShape* shape);
void removeShape(BlobShape* shape);
@@ -51,10 +58,12 @@ void ensurePhysicsUpdated();
signals:
void smoothingChanged();
void colorChanged();
void cornerFillChanged();
private:
qreal m_smoothing = 32.0;
QColor m_color{ 0x44, 0x88, 0xff };
bool m_cornerFill = true;
QList<BlobShape*> m_shapes;
BlobInvertedRect* m_invertedRect = nullptr;
bool m_physicsUpdated = false;
+64 -27
View File
@@ -27,16 +27,6 @@ void BlobRect::updatePolish() {
emit rawDeformMatrixChanged();
updateCenteredDeformMatrix();
m_physicsActive = false;
if (m_group) {
QMetaObject::invokeMethod(
this,
[this]() {
if (m_group)
m_group->markDirty();
},
Qt::QueuedConnection);
}
} else {
QMetaObject::invokeMethod(
this,
@@ -105,17 +95,15 @@ void BlobRect::updatePhysics() {
const float kStiffness = static_cast<float>(m_stiffness);
const float kDamping = static_cast<float>(m_damping);
const float invDamp = 1.0f / (1.0f + kDamping * dt);
const float accel00 = -kStiffness * (m_dm00 - target00) - kDamping * m_dmVel00;
m_dmVel00 += accel00 * dt;
m_dmVel00 = (m_dmVel00 - kStiffness * (m_dm00 - target00) * dt) * invDamp;
m_dm00 += m_dmVel00 * dt;
const float accel01 = -kStiffness * (m_dm01 - target01) - kDamping * m_dmVel01;
m_dmVel01 += accel01 * dt;
m_dmVel01 = (m_dmVel01 - kStiffness * (m_dm01 - target01) * dt) * invDamp;
m_dm01 += m_dmVel01 * dt;
const float accel11 = -kStiffness * (m_dm11 - target11) - kDamping * m_dmVel11;
m_dmVel11 += accel11 * dt;
m_dmVel11 = (m_dmVel11 - kStiffness * (m_dm11 - target11) * dt) * invDamp;
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);
@@ -178,11 +166,24 @@ bool BlobRect::isExcluded(const BlobShape* other) const {
return false;
}
bool BlobRect::isCornerExcluded(const BlobShape* other) const {
for (const auto& ptr : m_excludeCorners) {
if (ptr == other)
return true;
}
return false;
}
QQmlListProperty<BlobRect> BlobRect::exclude() {
return QQmlListProperty<BlobRect>(
this, nullptr, &excludeAppend, &excludeCount, &excludeAt, &excludeClear, &excludeReplace, &excludeRemoveLast);
}
QQmlListProperty<BlobRect> BlobRect::excludeCorners() {
return QQmlListProperty<BlobRect>(this, nullptr, &excludeCornersAppend, &excludeCornersCount, &excludeCornersAt,
&excludeCornersClear, &excludeCornersReplace, &excludeCornersRemoveLast);
}
void BlobRect::excludeAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect) {
auto* self = static_cast<BlobRect*>(prop->object);
self->m_exclude.append(rect);
@@ -229,6 +230,52 @@ void BlobRect::excludeRemoveLast(QQmlListProperty<BlobRect>* prop) {
emit self->excludeChanged();
}
void BlobRect::excludeCornersAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect) {
auto* self = static_cast<BlobRect*>(prop->object);
self->m_excludeCorners.append(rect);
if (self->m_group)
self->m_group->markDirty();
emit self->excludeCornersChanged();
}
qsizetype BlobRect::excludeCornersCount(QQmlListProperty<BlobRect>* prop) {
auto* self = static_cast<BlobRect*>(prop->object);
return self->m_excludeCorners.size();
}
BlobRect* BlobRect::excludeCornersAt(QQmlListProperty<BlobRect>* prop, qsizetype index) {
auto* self = static_cast<BlobRect*>(prop->object);
return self->m_excludeCorners.at(index);
}
void BlobRect::excludeCornersClear(QQmlListProperty<BlobRect>* prop) {
auto* self = static_cast<BlobRect*>(prop->object);
if (self->m_excludeCorners.isEmpty())
return;
self->m_excludeCorners.clear();
if (self->m_group)
self->m_group->markDirty();
emit self->excludeCornersChanged();
}
void BlobRect::excludeCornersReplace(QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect) {
auto* self = static_cast<BlobRect*>(prop->object);
self->m_excludeCorners[index] = rect;
if (self->m_group)
self->m_group->markDirty();
emit self->excludeCornersChanged();
}
void BlobRect::excludeCornersRemoveLast(QQmlListProperty<BlobRect>* prop) {
auto* self = static_cast<BlobRect*>(prop->object);
if (self->m_excludeCorners.isEmpty())
return;
self->m_excludeCorners.removeLast();
if (self->m_group)
self->m_group->markDirty();
emit self->excludeCornersChanged();
}
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 &&
@@ -242,19 +289,9 @@ void BlobRect::checkAtRest(float speed) {
m_dmVel00 = 0.0f;
m_dmVel01 = 0.0f;
m_dmVel11 = 0.0f;
m_deformMatrix = QMatrix4x4();
m_deformMatrix = QMatrix4x4(); // identity
emit rawDeformMatrixChanged();
updateCenteredDeformMatrix();
m_physicsActive = false;
if (m_group) {
QMetaObject::invokeMethod(
this,
[this]() {
if (m_group)
m_group->markDirty();
},
Qt::QueuedConnection);
}
}
}
+12
View File
@@ -14,6 +14,7 @@ Q_PROPERTY(qreal stiffness READ stiffness WRITE setStiffness NOTIFY stiffnessCha
Q_PROPERTY(qreal damping READ damping WRITE setDamping NOTIFY dampingChanged)
Q_PROPERTY(qreal deformScale READ deformScale WRITE setDeformScale NOTIFY deformScaleChanged)
Q_PROPERTY(QQmlListProperty<BlobRect> exclude READ exclude NOTIFY excludeChanged)
Q_PROPERTY(QQmlListProperty<BlobRect> excludeCorners READ excludeCorners NOTIFY excludeCornersChanged)
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)
@@ -58,8 +59,10 @@ void setDeformScale(qreal s) {
}
QQmlListProperty<BlobRect> exclude();
QQmlListProperty<BlobRect> excludeCorners();
bool isExcluded(const BlobShape* other) const override;
bool isCornerExcluded(const BlobShape* other) const override;
void cornerRadii(float out[4]) const override;
[[nodiscard]] qreal topLeftRadius() const {
@@ -91,6 +94,7 @@ void stiffnessChanged();
void dampingChanged();
void deformScaleChanged();
void excludeChanged();
void excludeCornersChanged();
void topLeftRadiusChanged();
void topRightRadiusChanged();
void bottomLeftRadiusChanged();
@@ -130,6 +134,7 @@ qreal m_bottomLeftRadius = -1;
qreal m_bottomRightRadius = -1;
QList<QPointer<BlobRect> > m_exclude;
QList<QPointer<BlobRect> > m_excludeCorners;
static void excludeAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect);
static qsizetype excludeCount(QQmlListProperty<BlobRect>* prop);
@@ -137,4 +142,11 @@ static BlobRect* excludeAt(QQmlListProperty<BlobRect>* prop, qsizetype index);
static void excludeClear(QQmlListProperty<BlobRect>* prop);
static void excludeReplace(QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect);
static void excludeRemoveLast(QQmlListProperty<BlobRect>* prop);
static void excludeCornersAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect);
static qsizetype excludeCornersCount(QQmlListProperty<BlobRect>* prop);
static BlobRect* excludeCornersAt(QQmlListProperty<BlobRect>* prop, qsizetype index);
static void excludeCornersClear(QQmlListProperty<BlobRect>* prop);
static void excludeCornersReplace(QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect);
static void excludeCornersRemoveLast(QQmlListProperty<BlobRect>* prop);
};
+24 -17
View File
@@ -31,6 +31,12 @@ static float cpuSmoothstep(float edge0, float edge1, float x) {
return t * t * (3.0f - 2.0f * t);
}
static float cornerFillFactor(float sd, float smoothFactor) {
const float outside = cpuSmoothstep(0.0f, smoothFactor, sd);
const float inside = cpuSmoothstep(0.0f, -smoothFactor, sd);
return std::max(outside, inside);
}
BlobShape::BlobShape(QQuickItem* parent)
: QQuickItem(parent) {
setFlag(ItemHasContents);
@@ -70,19 +76,11 @@ void BlobShape::geometryChange(const QRectF& newGeometry, const QRectF& oldGeome
if (m_group) {
m_pendingDx += static_cast<float>(newGeometry.x() - oldGeometry.x());
m_pendingDy += static_cast<float>(newGeometry.y() - oldGeometry.y());
m_pendingDw += static_cast<float>(newGeometry.width() - oldGeometry.width());
m_pendingDh += static_cast<float>(newGeometry.height() - oldGeometry.height());
const float deformMag = std::abs(m_deformMatrix(0, 0) - 1.0f) + std::abs(m_deformMatrix(0, 1)) +
std::abs(m_deformMatrix(1, 0)) + std::abs(m_deformMatrix(1, 1) - 1.0f);
const float syncThreshold = deformMag > 0.001f ? 0.05f : 0.5f;
if (std::abs(m_pendingDx) > syncThreshold || std::abs(m_pendingDy) > syncThreshold ||
std::abs(m_pendingDw) > syncThreshold || std::abs(m_pendingDh) > syncThreshold) {
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) {
m_pendingDx = 0;
m_pendingDy = 0;
m_pendingDw = 0;
m_pendingDh = 0;
m_group->markShapeDirty(this);
}
}
@@ -281,10 +279,12 @@ void BlobShape::updatePolish() {
const float smoothFactor = pad;
constexpr float minR = 2.0f;
const bool cornerFill = m_group->cornerFill();
const auto rectCount = m_cachedRects.size();
for (qsizetype i = 0; i < rectCount; ++i) {
auto& ri = m_cachedRects[i];
const int riExcludeMask = ri.excludeMask;
BlobShape* const si = rectShapes[i];
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;
@@ -292,19 +292,26 @@ void BlobShape::updatePolish() {
const float cBlX = ri.cx - ri.hw, cBlY = ri.cy + ri.hh;
const float cTlX = ri.cx - ri.hw, cTlY = ri.cy - ri.hh;
for (qsizetype j = 0; j < rectCount; ++j) {
for (qsizetype j = 0; cornerFill && j < rectCount; ++j) {
if (j == i)
continue;
if (riExcludeMask & (1 << j))
continue;
BlobShape* const sj = rectShapes[j];
if (si->isCornerExcluded(sj) || sj->isCornerExcluded(si))
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)));
const float sdTr = cpuSdBox(cTrX, cTrY, rj.cx, rj.cy, rj.hw, rj.hh);
const float sdBr = cpuSdBox(cBrX, cBrY, rj.cx, rj.cy, rj.hw, rj.hh);
const float sdBl = cpuSdBox(cBlX, cBlY, rj.cx, rj.cy, rj.hw, rj.hh);
const float sdTl = cpuSdBox(cTlX, cTlY, rj.cx, rj.cy, rj.hw, rj.hh);
fTr = std::min(fTr, cornerFillFactor(sdTr, smoothFactor));
fBr = std::min(fBr, cornerFillFactor(sdBr, smoothFactor));
fBl = std::min(fBl, cornerFillFactor(sdBl, smoothFactor));
fTl = std::min(fTl, cornerFillFactor(sdTl, smoothFactor));
}
if (m_cachedHasInverted) {
if (cornerFill && m_cachedHasInverted) {
const float icx = m_cachedInvertedInner[0];
const float icy = m_cachedInvertedInner[1];
const float ihw = m_cachedInvertedInner[2];
+6 -3
View File
@@ -61,6 +61,10 @@ virtual bool isExcluded(const BlobShape* /*other*/) const {
return false;
}
virtual bool isCornerExcluded(const BlobShape* /*other*/) const {
return false;
}
virtual void cornerRadii(float out[4]) const;
virtual void updatePhysics() {
@@ -72,9 +76,10 @@ void updateCenteredDeformMatrix();
BlobGroup* m_group = nullptr;
qreal m_radius = 0;
QMatrix4x4 m_deformMatrix;
QMatrix4x4 m_deformMatrix; // identity by default
QMatrix4x4 m_centeredDeformMatrix;
// Cached data from updatePolish
float m_cachedPaddedX = 0;
float m_cachedPaddedY = 0;
float m_cachedPaddedW = 0;
@@ -84,8 +89,6 @@ QVector<BlobRectData> m_cachedRects;
int m_cachedMyIndex = -2;
float m_pendingDx = 0;
float m_pendingDy = 0;
float m_pendingDw = 0;
float m_pendingDh = 0;
bool m_cachedHasInverted = false;
float m_cachedInvertedRadius = 0;
float m_cachedInvertedOuter[4] = {};