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:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user