feat: better notification storage and image caching + blobs
C++ / fmt (pull_request) Failing after 5s
C++ / build (pull_request) Failing after 11s
C++ / clang-tidy (pull_request) Failing after 10s
JS/TS / fmt (pull_request) Successful in 12s
JS/TS / lint (pull_request) Successful in 10s
Python / static (pull_request) Failing after 27s
Rust / build (pull_request) Successful in 54s
Python / verify (pull_request) Failing after 1m26s
Rust / fmt (pull_request) Successful in 46s
Rust / clippy (pull_request) Successful in 1m6s
C++ / fmt (pull_request) Failing after 5s
C++ / build (pull_request) Failing after 11s
C++ / clang-tidy (pull_request) Failing after 10s
JS/TS / fmt (pull_request) Successful in 12s
JS/TS / lint (pull_request) Successful in 10s
Python / static (pull_request) Failing after 27s
Rust / build (pull_request) Successful in 54s
Python / verify (pull_request) Failing after 1m26s
Rust / fmt (pull_request) Successful in 46s
Rust / clippy (pull_request) Successful in 1m6s
This commit is contained in:
@@ -16,6 +16,22 @@ constexpr float k_settleVel = 0.05f;
|
||||
constexpr float k_identityEps = 0.0005f;
|
||||
constexpr float k_stillPx = 0.5f;
|
||||
constexpr int k_settleStillSamples = 3;
|
||||
|
||||
// How far the deform matrix is from identity, summed over its three distinct
|
||||
// entries. The matrix is applied as a panel's transform, so it has to reach
|
||||
// *exactly* identity rather than merely close to it: a residue of 1e-4 scales
|
||||
// a 400 logical px panel by 0.04 px, which moves a device-aligned child onto a
|
||||
// fraction of a device pixel, and anything that centers itself inside the
|
||||
// panel (`anchors` with `alignWhenCentered`, `Layout.HCenter`/`VCenter`) then
|
||||
// antialiases. The residue also differs per entry, so different children land
|
||||
// on different sub-pixel offsets - which reads as random blur.
|
||||
float identityResidue(float dm00, float dm01, float dm11) {
|
||||
return std::abs(dm00 - 1.0f) + std::abs(dm01) + std::abs(dm11 - 1.0f);
|
||||
}
|
||||
|
||||
bool nearIdentity(float dm00, float dm01, float dm11) {
|
||||
return identityResidue(dm00, dm01, dm11) <= k_identityEps;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
BlobRect::BlobRect(QQuickItem* parent) : BlobShape(parent) {
|
||||
@@ -27,11 +43,12 @@ BlobRect::~BlobRect() {
|
||||
}
|
||||
|
||||
bool BlobRect::needsFrame() const {
|
||||
// Pump until the matrix is *exactly* identity, not merely within epsilon:
|
||||
// updatePhysics() snaps it there once it is near enough, so this stays true
|
||||
// only for the frames between the last integration and that snap.
|
||||
return m_physicsActive || m_retiring ||
|
||||
m_stillSamples < k_settleStillSamples ||
|
||||
std::abs(m_dm00 - 1.0f) + std::abs(m_dm01) +
|
||||
std::abs(m_dm11 - 1.0f) >
|
||||
k_identityEps;
|
||||
identityResidue(m_dm00, m_dm01, m_dm11) > 0.0f;
|
||||
}
|
||||
|
||||
void BlobRect::resetDeformMatrix() {
|
||||
@@ -55,10 +72,15 @@ void BlobRect::updatePolish() {
|
||||
}
|
||||
|
||||
void BlobRect::finishAtIdentity() {
|
||||
const bool settled = identityResidue(m_dm00, m_dm01, m_dm11) <= 0.0f &&
|
||||
!m_retiring && !m_physicsActive;
|
||||
if (settled) return;
|
||||
|
||||
m_dm00 = 1.0f;
|
||||
m_dm01 = 0.0f;
|
||||
m_dm11 = 1.0f;
|
||||
m_deformMatrix = QMatrix4x4();
|
||||
m_dmVel00 = m_dmVel01 = m_dmVel11 = 0.0f;
|
||||
m_retiring = false;
|
||||
m_physicsActive = false;
|
||||
emit rawDeformMatrixChanged();
|
||||
@@ -110,6 +132,16 @@ void BlobRect::updatePhysics() {
|
||||
m_dm00 = m_retire00 + (1.0f - m_retire00) * s;
|
||||
m_dm01 = m_retire01 - m_retire01 * s;
|
||||
m_dm11 = m_retire11 + (1.0f - m_retire11) * s;
|
||||
|
||||
// Snap the emitted matrix to exact identity while the retire tail is
|
||||
// below the visible threshold, but keep retiring: ending the retire early
|
||||
// hides the background while the panel animation still has a tail.
|
||||
if (nearIdentity(m_dm00, m_dm01, m_dm11)) {
|
||||
m_dm00 = 1.0f;
|
||||
m_dm01 = 0.0f;
|
||||
m_dm11 = 1.0f;
|
||||
}
|
||||
|
||||
m_deformMatrix = QMatrix4x4(
|
||||
m_dm00, m_dm01, 0, 0, m_dm01, m_dm11, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
emit rawDeformMatrixChanged();
|
||||
@@ -119,12 +151,15 @@ void BlobRect::updatePhysics() {
|
||||
const QPointF scenePos = mapToScene(QPointF(width() / 2.0, height() / 2.0));
|
||||
|
||||
if (!m_physicsActive && !m_retiring) {
|
||||
const float stuck = std::abs(m_dm00 - 1.0f) + std::abs(m_dm01) +
|
||||
std::abs(m_dm11 - 1.0f);
|
||||
if (stuck > k_identityEps) {
|
||||
const float residue = identityResidue(m_dm00, m_dm01, m_dm11);
|
||||
if (residue > k_identityEps) {
|
||||
beginRetire();
|
||||
return;
|
||||
}
|
||||
// Residue at or below the threshold is invisible, so it is written as
|
||||
// exact identity - the panel transform then stops being a transform at
|
||||
// all. Sampling continues below, so a new movement can restart physics.
|
||||
if (residue > 0.0f) finishAtIdentity();
|
||||
}
|
||||
|
||||
if (!m_hasPrevPos) {
|
||||
@@ -215,6 +250,21 @@ void BlobRect::updatePhysics() {
|
||||
m_dmVel11 = (m_dmVel11 - kStiffness * (m_dm11 - target11) * dt) * invDamp;
|
||||
m_dm11 += m_dmVel11 * dt;
|
||||
|
||||
// Land on exact identity as soon as the spring is within the invisible
|
||||
// threshold, so the panel's contents are back on the device pixel grid -
|
||||
// but keep physicsActive until trySettle() completes: the at-rest drop
|
||||
// must not happen while the panel animation still has a slow tail.
|
||||
if (speed < k_moveSpeed && nearIdentity(m_dm00, m_dm01, m_dm11)) {
|
||||
m_dm00 = 1.0f;
|
||||
m_dm01 = 0.0f;
|
||||
m_dm11 = 1.0f;
|
||||
m_deformMatrix = QMatrix4x4();
|
||||
emit rawDeformMatrixChanged();
|
||||
updateCenteredDeformMatrix();
|
||||
trySettle(speed);
|
||||
return;
|
||||
}
|
||||
|
||||
m_deformMatrix = QMatrix4x4(
|
||||
m_dm00, m_dm01, 0, 0, m_dm01, m_dm11, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
emit rawDeformMatrixChanged();
|
||||
|
||||
@@ -15,6 +15,22 @@ constexpr float k_settleVel = 0.05f;
|
||||
constexpr float k_identityEps = 0.0005f;
|
||||
constexpr float k_stillPx = 0.5f;
|
||||
constexpr int k_settleStillSamples = 3;
|
||||
|
||||
// How far the deform matrix is from identity, summed over its three distinct
|
||||
// entries. The matrix is applied as a panel's transform, so it has to reach
|
||||
// *exactly* identity rather than merely close to it: a residue of 1e-4 scales
|
||||
// a 400 logical px panel by 0.04 px, which moves a device-aligned child onto a
|
||||
// fraction of a device pixel, and anything that centers itself inside the
|
||||
// panel (`anchors` with `alignWhenCentered`, `Layout.HCenter`/`VCenter`) then
|
||||
// antialiases. The residue also differs per entry, so different children land
|
||||
// on different sub-pixel offsets - which reads as random blur.
|
||||
float identityResidue(float dm00, float dm01, float dm11) {
|
||||
return std::abs(dm00 - 1.0f) + std::abs(dm01) + std::abs(dm11 - 1.0f);
|
||||
}
|
||||
|
||||
bool nearIdentity(float dm00, float dm01, float dm11) {
|
||||
return identityResidue(dm00, dm01, dm11) <= k_identityEps;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
BlobRect2::BlobRect2(QQuickItem* parent) : BlobShape2(parent) {
|
||||
@@ -26,11 +42,12 @@ BlobRect2::~BlobRect2() {
|
||||
}
|
||||
|
||||
bool BlobRect2::needsFrame() const {
|
||||
// Pump until the matrix is *exactly* identity, not merely within epsilon:
|
||||
// updatePhysics() snaps it there once it is near enough, so this stays true
|
||||
// only for the frames between the last integration and that snap.
|
||||
return m_physicsActive || m_retiring ||
|
||||
m_stillSamples < k_settleStillSamples ||
|
||||
std::abs(m_dm00 - 1.0f) + std::abs(m_dm01) +
|
||||
std::abs(m_dm11 - 1.0f) >
|
||||
k_identityEps;
|
||||
identityResidue(m_dm00, m_dm01, m_dm11) > 0.0f;
|
||||
}
|
||||
|
||||
bool BlobRect2::isMoving() const {
|
||||
@@ -60,10 +77,15 @@ void BlobRect2::requestPhysics() {
|
||||
}
|
||||
|
||||
void BlobRect2::finishAtIdentity() {
|
||||
const bool settled = identityResidue(m_dm00, m_dm01, m_dm11) <= 0.0f &&
|
||||
!m_retiring && !m_physicsActive;
|
||||
if (settled) return;
|
||||
|
||||
m_dm00 = 1.0f;
|
||||
m_dm01 = 0.0f;
|
||||
m_dm11 = 1.0f;
|
||||
m_deformMatrix = QMatrix4x4();
|
||||
m_dmVel00 = m_dmVel01 = m_dmVel11 = 0.0f;
|
||||
m_retiring = false;
|
||||
m_physicsActive = false;
|
||||
emit rawDeformMatrixChanged();
|
||||
@@ -117,6 +139,17 @@ void BlobRect2::updatePhysics() {
|
||||
m_dm00 = m_retire00 + (1.0f - m_retire00) * s;
|
||||
m_dm01 = m_retire01 - m_retire01 * s;
|
||||
m_dm11 = m_retire11 + (1.0f - m_retire11) * s;
|
||||
|
||||
// Snap the emitted matrix to exact identity while the retire tail is
|
||||
// below the visible threshold, but keep retiring: isMoving() drives the
|
||||
// at-rest drop, and finishing early hides the background while the panel
|
||||
// animation still has a tail.
|
||||
if (nearIdentity(m_dm00, m_dm01, m_dm11)) {
|
||||
m_dm00 = 1.0f;
|
||||
m_dm01 = 0.0f;
|
||||
m_dm11 = 1.0f;
|
||||
}
|
||||
|
||||
m_deformMatrix = QMatrix4x4(
|
||||
m_dm00, m_dm01, 0, 0, m_dm01, m_dm11, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
emit rawDeformMatrixChanged();
|
||||
@@ -126,12 +159,15 @@ void BlobRect2::updatePhysics() {
|
||||
}
|
||||
|
||||
if (!m_physicsActive && !m_retiring) {
|
||||
const float stuck = std::abs(m_dm00 - 1.0f) + std::abs(m_dm01) +
|
||||
std::abs(m_dm11 - 1.0f);
|
||||
if (stuck > k_identityEps) {
|
||||
const float residue = identityResidue(m_dm00, m_dm01, m_dm11);
|
||||
if (residue > k_identityEps) {
|
||||
beginRetire();
|
||||
return;
|
||||
}
|
||||
// Residue at or below the threshold is invisible, so it is written as
|
||||
// exact identity - the panel transform then stops being a transform at
|
||||
// all. Sampling continues below, so a new movement can restart physics.
|
||||
if (residue > 0.0f) finishAtIdentity();
|
||||
}
|
||||
|
||||
if (m_physicsActive) requestPhysics();
|
||||
@@ -228,6 +264,23 @@ void BlobRect2::updatePhysics() {
|
||||
m_dmVel11 = (m_dmVel11 - kStiffness * (m_dm11 - target11) * dt) * invDamp;
|
||||
m_dm11 += m_dmVel11 * dt;
|
||||
|
||||
// Land on exact identity as soon as the spring is within the invisible
|
||||
// threshold, so the panel's contents are back on the device pixel grid -
|
||||
// but keep physicsActive until trySettle() completes: isMoving() drives the
|
||||
// at-rest drop, and clearing physics early hides the background while the
|
||||
// panel animation still has a slow tail.
|
||||
if (speed < k_moveSpeed && nearIdentity(m_dm00, m_dm01, m_dm11)) {
|
||||
m_dm00 = 1.0f;
|
||||
m_dm01 = 0.0f;
|
||||
m_dm11 = 1.0f;
|
||||
m_deformMatrix = QMatrix4x4();
|
||||
emit rawDeformMatrixChanged();
|
||||
updateCenteredDeformMatrix();
|
||||
trySettle(speed);
|
||||
updateMoving();
|
||||
return;
|
||||
}
|
||||
|
||||
m_deformMatrix = QMatrix4x4(
|
||||
m_dm00, m_dm01, 0, 0, m_dm01, m_dm11, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
emit rawDeformMatrixChanged();
|
||||
|
||||
@@ -43,19 +43,26 @@ float sdBox(vec2 p, vec2 center, vec2 halfSize) {
|
||||
}
|
||||
|
||||
float smin(float a, float b, float k) {
|
||||
float h = max(k - abs(a - b), 0.0) / k;
|
||||
return min(a, b) - h * h * h * k * (1.0 / 6.0);
|
||||
// Circular smooth min — the blend fillet is a true circular arc of radius k,
|
||||
// tangent to both surfaces (not a polynomial/squircle curve). Deviates from
|
||||
// min(a, b) only in the corner region where BOTH a < k and b < k (unlike the
|
||||
// cubic, which deviates over the whole band |a - b| < k and dents the surface
|
||||
// inward while the shapes are still apart). Always <= min(a, b); max blend
|
||||
// depth at a == b is (sqrt(2) - 1) * k.
|
||||
return max(k, min(a, b)) - length(max(vec2(k) - vec2(a, b), vec2(0.0)));
|
||||
}
|
||||
|
||||
float smax(float a, float b, float k) {
|
||||
float h = max(k - abs(a - b), 0.0) / k;
|
||||
return max(a, b) + h * h * h * k * (1.0 / 6.0);
|
||||
// Circular smooth max — dual of smin: -smin(-a, -b, k). Always >= max(a, b).
|
||||
return min(-k, max(a, b)) + length(max(vec2(a, b) + vec2(k), vec2(0.0)));
|
||||
}
|
||||
|
||||
float smaxSharpA(float a, float b, float k) {
|
||||
float h = max(k - abs(a - b), 0.0) / k;
|
||||
float blend = h * h * h * k * (1.0 / 6.0);
|
||||
blend *= smoothstep(0.0, k * 0.5, -a);
|
||||
// Circular smax variant that keeps a's boundary sharp (no inward rounding at
|
||||
// a = 0). Used for the frame outer edge so it always fills to the edges.
|
||||
float sm =
|
||||
min(-k, max(a, b)) + length(max(vec2(a, b) + vec2(k), vec2(0.0)));
|
||||
float blend = (sm - max(a, b)) * smoothstep(0.0, k * 0.5, -a);
|
||||
return max(a, b) + blend;
|
||||
}
|
||||
|
||||
@@ -148,7 +155,8 @@ void main() {
|
||||
continue;
|
||||
if ((excludeMask & (1 << j)) != 0)
|
||||
continue;
|
||||
if (abs(dArr[i] - dArr[j]) >= smoothFactor)
|
||||
// Circular smin deviates from min only where BOTH d values are < k.
|
||||
if (max(dArr[i], dArr[j]) >= smoothFactor)
|
||||
continue;
|
||||
mergedSdf = min(mergedSdf, smin(dArr[i], dArr[j], smoothFactor));
|
||||
}
|
||||
@@ -179,7 +187,13 @@ void main() {
|
||||
smoothstep(innerRight - smoothFactor, innerRight, pixel.x);
|
||||
|
||||
float sinkValue = 0.0;
|
||||
float preOff = smoothFactor * (1.0 / 6.0);
|
||||
// Sink onset / residual overlap: how far a rect must penetrate the border
|
||||
// before the inner wall recedes to form its pocket. Too low and the wall
|
||||
// recedes faster than the junction can stay convex, denting the inner edge
|
||||
// inward near the rect's corners; too high and the rect nestles too deep
|
||||
// before the wall yields. Tuned to the circular smin blend depth: half the
|
||||
// gap-closing distance, (2 - sqrt(2)) * k / 2.
|
||||
float preOff = smoothFactor * (2.0 - sqrt(2.0)) * 0.5;
|
||||
float s = smoothFactor * 2.0;
|
||||
|
||||
if (topZone > 0.0 || botZone > 0.0 || leftZone > 0.0 || rightZone > 0.0) {
|
||||
@@ -215,9 +229,16 @@ void main() {
|
||||
|
||||
dInner -= sinkValue;
|
||||
|
||||
float negInner = -dInner;
|
||||
float dFrame = dOuter >= negInner ? dOuter
|
||||
: smaxSharpA(dOuter, negInner, smoothFactor);
|
||||
// The circular smax fillet has radius kFrame; when it exceeds the border
|
||||
// thickness it can't complete inside the border, so the sharp outer-box term
|
||||
// bleeds onto the inner edge and bulges the inner corners (worst when
|
||||
// thickness < smoothFactor). Clamp kFrame to the thinnest side so the inner
|
||||
// edge stays a clean constant-radius arc. Each inner corner is bounded by
|
||||
// its thinner adjacent side, so the global min is correct for every corner.
|
||||
float minThick = min(min(innerTop - outerTop, outerBot - innerBot),
|
||||
min(innerLeft - outerLeft, outerRight - innerRight));
|
||||
float kFrame = clamp(min(smoothFactor, minThick - 1.0), 1.0, smoothFactor);
|
||||
float dFrame = smaxSharpA(dOuter, -dInner, kFrame);
|
||||
|
||||
mergedSdf = smin(mergedSdf, dFrame, smoothFactor);
|
||||
if (dFrame < minDist) {
|
||||
|
||||
Reference in New Issue
Block a user