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

This commit is contained in:
2026-11-02 19:00:43 +01:00
parent 789305286a
commit 11e7aac195
56 changed files with 5003 additions and 2168 deletions
+101 -14
View File
@@ -1,6 +1,8 @@
#include "wavylinerenderer.hpp"
#include "wavyline.hpp"
#include <qcanvaspainter.h>
#include <algorithm>
#include <cmath>
namespace ZShell::controls {
@@ -19,13 +21,48 @@ void WavyLineRenderer::synchronizeData(QCanvasPainterItem* item) {
m_fullAngle = wavyLine->m_fullAngle;
m_radius = wavyLine->m_radius;
m_value = wavyLine->m_value;
m_dpr = wavyLine->m_dpr > 0.0 ? wavyLine->m_dpr : 1.0;
}
namespace {
qint64 toDev(qreal logical, qreal dpr) {
return std::llround(logical * dpr);
}
qreal fromDev(qint64 device, qreal dpr) {
return qreal(device) / dpr;
}
// A center coordinate whose device position shares the parity of the stroke's
// device width: an even width centered on a pixel boundary and an odd width
// centered on a pixel center are the two cases where both edges of the stroke
// land on whole device pixels. Floored, so the caller gets the same value from
// the same device size every time; a rounded half could flip between two grid
// points depending on floating point noise.
qreal snapCenterDev(qint64 sizeDev, qint64 widthDev) {
const double halfParity = (widthDev % 2) == 0 ? 0.0 : 0.5;
return std::floor(static_cast<double>(sizeDev) / 2.0 - halfParity)
+ halfParity;
}
qint64 snapAmplitude(qint64 widthDev, qreal multiplier) {
return std::llround(static_cast<double>(widthDev) * multiplier);
}
} // namespace
void WavyLineRenderer::paint(QCanvasPainter* painter) {
painter->clearRect(0, 0, width(), height());
// The stroke is quantized in the item's own device pixel space, never
// against its position: the parent layout already places the item on the
// device grid, so a local whole-device value stays whole-device wherever
// the item moves, and a move cannot change the painted path.
const qreal dpr = m_dpr;
m_lineWidthDev = (std::max)(qint64(1), toDev(m_lineWidth, dpr));
m_amplitudeDev = snapAmplitude(m_lineWidthDev, m_amplitudeMultiplier);
painter->setStrokeStyle(m_color);
painter->setLineWidth(m_lineWidth);
painter->setLineWidth(fromDev(m_lineWidthDev, dpr));
painter->setLineCap(QCanvasPainter::LineCap::Round);
if (m_pathType == WavyLine::Arc) {
@@ -36,18 +73,36 @@ void WavyLineRenderer::paint(QCanvasPainter* painter) {
}
void WavyLineRenderer::paintLinear(QCanvasPainter* painter) {
const auto amplitude = m_lineWidth * m_amplitudeMultiplier;
const qreal dpr = m_dpr;
const auto lineWidth = fromDev(m_lineWidthDev, dpr);
const auto phase = m_waveProgress * 2 * M_PI;
const auto centerY = height() / 2;
const qint64 heightDev = toDev(height(), dpr);
const qreal cyDev = snapCenterDev(heightDev, m_lineWidthDev);
const auto centerY = cyDev / dpr;
const auto len = m_fullLength > 0 ? m_fullLength : 1;
const auto start = m_lineWidth / 2.0;
const auto fullEnd = width() - m_lineWidth / 2.0;
const auto start = lineWidth / 2.0;
const auto fullEnd = width() - lineWidth / 2.0;
const auto drawEnd = start + (fullEnd - start) * m_value;
// Keep the crest inside the item's own texture, with one device pixel for
// the antialiasing feather, so a tight item cannot cut it flat.
const qint64 halfDev = (m_lineWidthDev + 1) / 2;
const qint64 roomDev = static_cast<qint64>(
std::floor((std::min)(cyDev, qreal(heightDev) - cyDev)));
const qint64 amplitudeDev = (std::max)(qint64(0), roomDev - halfDev - 1);
const auto amplitude =
fromDev((std::min)(m_amplitudeDev, amplitudeDev), dpr);
painter->beginPath();
bool first = true;
for (int x = m_lineWidth / 2; x <= drawEnd; ++x) {
// One vertex per device pixel column. A segment spanning several columns
// interpolates its edge across all of them, which is what makes a sloped
// stroke look soft rather than crisply anti-aliased.
const qint64 firstCol = toDev(start, dpr);
const qint64 lastCol = toDev(drawEnd, dpr);
for (qint64 col = firstCol; col <= lastCol; ++col) {
const auto x = fromDev(col, dpr);
const auto theta =
m_frequency * 2 * M_PI * (x + m_startX) / len + phase;
const auto waveY = centerY + amplitude * qSin(theta);
@@ -67,16 +122,44 @@ void WavyLineRenderer::paintArc(QCanvasPainter* painter) {
return;
}
const qreal dpr = m_dpr;
const auto amplitude = fromDev(m_amplitudeDev, dpr);
const auto startAngleRad = m_startAngle * M_PI / 180.0;
const auto fullAngleRad = m_fullAngle * M_PI / 180.0;
const auto amplitude = m_lineWidth * m_amplitudeMultiplier;
const auto cx = width() / 2.0;
const auto cy = height() / 2.0;
const auto radius =
m_radius > 0
? m_radius
: (qMin(width(), height()) - m_lineWidth - 2 * amplitude) / 2.0;
// The center comes from the item's own device size and the stroke parity,
// the same rule Components/CircularProgress.qml applies to its background
// Shape arc, so the two rings share one center.
const qint64 widthDev = toDev(width(), dpr);
const qint64 heightDev = toDev(height(), dpr);
const qreal cxDev = snapCenterDev(widthDev, m_lineWidthDev);
const qreal cyDev = snapCenterDev(heightDev, m_lineWidthDev);
const auto cx = cxDev / dpr;
const auto cy = cyDev / dpr;
// The path is painted into a texture exactly the item's size, so anything
// past its edge is cut flat. Keep the outer stroke edge, plus the wave
// crest, inside that rect and leave one device pixel for the antialiasing
// feather. Derived from the item's own size and its local center, so it
// does not depend on where the item is placed.
const qint64 halfDev = (m_lineWidthDev + 1) / 2;
const qint64 roomDev = static_cast<qint64>(
std::floor((std::min)((std::min)(cxDev, qreal(widthDev) - cxDev),
(std::min)(cyDev, qreal(heightDev) - cyDev))));
const qint64 maxRadiusDev = roomDev - m_amplitudeDev - halfDev - 1;
// The radius is a whole device pixel count. Derived from the item's own
// device size, so it does not depend on where the item is.
qint64 radiusDev;
if (m_radius > 0) {
radiusDev = toDev(m_radius, dpr);
} else {
const qint64 inner = (std::min)(widthDev, heightDev) - m_lineWidthDev -
2 * m_amplitudeDev;
radiusDev = (std::max)(qint64(1), inner / 2);
}
radiusDev = (std::max)(qint64(1), (std::min)(radiusDev, maxRadiusDev));
const auto radius = fromDev(radiusDev, dpr);
if (radius <= 0) {
return;
@@ -91,7 +174,11 @@ void WavyLineRenderer::paintArc(QCanvasPainter* painter) {
return;
}
const auto N = qMax(64, qCeil(radius * drawAngleRad));
// Sample every device pixel of arc length, not every logical one: at a
// fractional ratio a logical step is more than one device pixel wide, and
// the edge would then be interpolated across the pixels it crosses.
const auto N =
qMax(64, qCeil(static_cast<double>(toDev(radius, dpr)) * drawAngleRad));
const auto dTheta = drawAngleRad / N;
painter->beginPath();