diff --git a/Drawers/Windows.qml b/Drawers/Windows.qml index 2db4151..295a305 100644 --- a/Drawers/Windows.qml +++ b/Drawers/Windows.qml @@ -246,6 +246,7 @@ CustomWindow { PanelBg { id: osdBg + atRestOverride: panels.osd.offsetScale < 0.99 deformAmount: 0.1 implicitHeight: panels.osd.height implicitWidth: panels.osd.width @@ -293,6 +294,7 @@ CustomWindow { property real extraExtent: 0.2 + atRestOverride: panels.resources.offsetScale < 0.99 deformAmount: 0.05 implicitHeight: panels.resources.height * (1 + extraExtent) implicitWidth: panels.resources.width * (1 + extraExtent) @@ -446,6 +448,7 @@ CustomWindow { property real deformAmount: 0.15 required property Item panel + atRestOverride: panel?.offsetScale < 0.99 ?? false damping: 28.3 deformScale: (deformAmount * Config.appearance.deform.scale) / 10000 group: blobGroup diff --git a/Modules/Bar/Bar.qml b/Modules/Bar/Bar.qml index bd4df6f..856b391 100644 --- a/Modules/Bar/Bar.qml +++ b/Modules/Bar/Bar.qml @@ -17,12 +17,12 @@ GridLayout { required property Wrapper popouts required property ClipWrapper popoutsWrapper required property ShellScreen screen - readonly property int vPadding: 6 required property ScreenState screenState + readonly property int vPadding: 6 function axisCenterOf(item: Item): real { const c = item.mapToItem(root, item.implicitWidth / 2, item.implicitHeight / 2); - return horizontal ? c.x : c.y; + return horizontal ? Math.floor(c.x) : Math.floor(c.y); } function checkPopout(pos: real): void { @@ -210,7 +210,8 @@ GridLayout { roleValue: "network" delegate: EntryWrapper { - NetworkWidget {} + NetworkWidget { + } } } diff --git a/Modules/Resources/Cards/MemoryCard.qml b/Modules/Resources/Cards/MemoryCard.qml index bb56af0..366cfa9 100644 --- a/Modules/Resources/Cards/MemoryCard.qml +++ b/Modules/Resources/Cards/MemoryCard.qml @@ -48,10 +48,11 @@ CustomRect { implicitSize: usageColumn.implicitHeight + thickness + Tokens.padding.largeIncreased * 2 startAngle: -225 sweepAngle: 270 - value: Memory.percentage + value: Memory.percentage / 100 Behavior on clampedVal { - Anim {} + Anim { + } } ColumnLayout { diff --git a/Plugins/ZShell/Blobs/blobgroup2.cpp b/Plugins/ZShell/Blobs/blobgroup2.cpp index 2273733..0f642ed 100644 --- a/Plugins/ZShell/Blobs/blobgroup2.cpp +++ b/Plugins/ZShell/Blobs/blobgroup2.cpp @@ -138,11 +138,16 @@ void BlobGroup2::ensureDataBuilt() { const double extent = static_cast(smoothFactor) * 1.5 + static_cast(m_shadowRadius); - // Group order, no pruning: the shader sees this list for every pixel. + // Group order, no pruning by proximity: the shader sees this list for every pixel. A + // shape that is not pushing is not in the list at all: shapes here are data for the + // group draw, so leaving a panel at rest out means it cannot define any part of the + // edge. A closing panel is still in the list while its curve runs out, even with no + // size left, which is what keeps the bounce. // entryShapes maps a shared entry back to the shape it came from. QVector entryShapes; for (auto* shape : std::as_const(m_shapes)) { + if (!shape->isMoving()) continue; if (shape->width() <= 0 || shape->height() <= 0) continue; if (static_cast(m_data.rects.size()) >= BlobGroupData::kMaxRects) continue; @@ -189,9 +194,6 @@ void BlobGroup2::ensureDataBuilt() { const int count = static_cast(m_data.rects.size()); - // Which shape is the source for the bar edge. - resolveSources(entryShapes); - // Exclusion gates only the smooth blend. for (int i = 0; i < count; ++i) { int mask = 0; @@ -254,8 +256,8 @@ void BlobGroup2::ensureDataBuilt() { ri.radius[3] = std::max(ri.radius[3] * fTl, minR); } - // Inverted rect. The frame term is evaluated for every pixel in the bounds, so the - // group flag cannot be gated per shape. + // Inverted rect. The frame term is evaluated for every pixel in the bounds, + // so the group flag cannot be gated per shape. if (!m_invertedRect) return; auto* inv = static_cast(m_invertedRect); @@ -302,28 +304,6 @@ void BlobGroup2::ensureDataBuilt() { 2.0 * (static_cast(outerHH) + extent))); } -// A point on the bar edge can only have one source. A shape that is not pushing is not -// the source: it still fills its own area, but inside the border strip it must not add -// its own outline to a span another shape is already shaping. That is the case that -// matters — one panel at rest next to one that is bouncing — where the edge showed -// several steps instead of one smooth indent of the closing panel. Only one shape can be -// pushing at a time, so the flag is just that, and the shader gates the strip with it. -void BlobGroup2::resolveSources(const QVector& entryShapes) { - const int count = static_cast(m_data.rects.size()); - - // With nothing pushing there is no span being shaped, so a shape at rest keeps its - // own outline and nothing is gated. - bool anyPushing = false; - for (int i = 0; i < count; ++i) - if (entryShapes[i]->isMoving()) { - anyPushing = true; - break; - } - - for (int i = 0; i < count; ++i) - m_data.rects[i].source = !anyPushing || entryShapes[i]->isMoving(); -} - void BlobGroup2::fillMaterial(BlobMaterial* material) { material->m_paddedX = static_cast(m_bounds.x()); material->m_paddedY = static_cast(m_bounds.y()); diff --git a/Plugins/ZShell/Blobs/blobgroup2.hpp b/Plugins/ZShell/Blobs/blobgroup2.hpp index 988816e..9f922e6 100644 --- a/Plugins/ZShell/Blobs/blobgroup2.hpp +++ b/Plugins/ZShell/Blobs/blobgroup2.hpp @@ -22,11 +22,9 @@ class BlobInvertedRect2; // disagree with the first. That is what makes overlapping shapes read as one // surface - one value, one writer, one alpha per pixel. struct BlobGroupData { - // The shader reads rectData[80] = 16 entries of 5 vec4, and excludeMask is a - // 32-bit mask. The shared list is capped at 16, in group order, so the cap is the - // same list for every pixel. Each entry is either a source for its span or not; the - // flag is in the third vec4 alongside the screen half-extents, so no extra data is - // needed. + // The shader reads rectData[80] = 16 entries, and excludeMask is a 32-bit + // mask. The shared list is capped at 16, in group order, so the cap is the + // same list for every pixel. static constexpr int kMaxRects = 16; QVector rects; @@ -111,7 +109,6 @@ class BlobGroup2 : public QQuickItem { private: void ensurePhysicsUpdated(); void ensureDataBuilt(); - void resolveSources(const QVector& entryShapes); void fillMaterial(BlobMaterial* material); qreal m_smoothing = 32.0; diff --git a/Plugins/ZShell/Blobs/blobmaterial.cpp b/Plugins/ZShell/Blobs/blobmaterial.cpp index cc21de9..528d617 100644 --- a/Plugins/ZShell/Blobs/blobmaterial.cpp +++ b/Plugins/ZShell/Blobs/blobmaterial.cpp @@ -92,12 +92,7 @@ bool BlobMaterialShader::updateUniformData( memcpy(&maskAsFloat, &r.excludeMask, sizeof(float)); const float d0[4] = {r.cx, r.cy, r.hw, r.hh}; const float d1[4] = {maskAsFloat, r.offsetX, r.offsetY, r.minEig}; - const float d3[4] = { - r.screenHalfX, - r.screenHalfY, - r.source ? 1.0f : 0.0f, - 0.0f, - }; + 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); diff --git a/Plugins/ZShell/Blobs/blobmaterial.hpp b/Plugins/ZShell/Blobs/blobmaterial.hpp index 659a7e6..19536a5 100644 --- a/Plugins/ZShell/Blobs/blobmaterial.hpp +++ b/Plugins/ZShell/Blobs/blobmaterial.hpp @@ -17,11 +17,6 @@ struct BlobRectData { // Bitmask of indices in this rect's m_cachedRects that mutually exclude (or are excluded by) this rect. // Used by the shader to skip smin between excluded pairs. int excludeMask = 0; - // Whether this entry is pushing on the bar edge. Only the shape that is pushing can - // shape the edge; a shape at rest still fills its own area, but the shader gives it - // no contribution inside the border strip, so it cannot add its own outline to a span - // another shape is shaping. - bool source = true; }; class BlobMaterial : public QSGMaterial { diff --git a/Plugins/ZShell/Blobs/blobrect2.hpp b/Plugins/ZShell/Blobs/blobrect2.hpp index 2cd29dc..16832f0 100644 --- a/Plugins/ZShell/Blobs/blobrect2.hpp +++ b/Plugins/ZShell/Blobs/blobrect2.hpp @@ -22,6 +22,9 @@ class BlobRect2 : public BlobShape2 { Q_PROPERTY( QQmlListProperty excludeCorners READ excludeCorners NOTIFY excludeCornersChanged) + Q_PROPERTY( + bool atRestOverride READ atRestOverride WRITE setAtRestOverride NOTIFY + atRestOverrideChanged) Q_PROPERTY( qreal topLeftRadius READ topLeftRadius WRITE setTopLeftRadius NOTIFY topLeftRadiusChanged) @@ -69,11 +72,24 @@ class BlobRect2 : public BlobShape2 { QQmlListProperty exclude(); QQmlListProperty excludeCorners(); + [[nodiscard]] bool atRestOverride() const { return m_atRestOverride; } + + void setAtRestOverride(bool o) { + if (m_atRestOverride == o) return; + m_atRestOverride = o; + emit atRestOverrideChanged(); + if (m_group) m_group->markDirty(); + } + bool isExcluded(const BlobShape2* other) const override; bool isCornerExcluded(const BlobShape2* other) const override; // The shape that is pushing is the one that can shape the bar edge. A shape that is // retiring still counts, because the retire curve is still moving the surface. - bool isMoving() const override { return m_physicsActive || m_retiring; } + // atRestOverride keeps the shape in the group's data even at rest; when it is false + // the actual spring state decides, so it is not an override of the state itself. + bool isMoving() const override { + return m_atRestOverride || m_physicsActive || m_retiring; + } void cornerRadii(float out[4]) const override; [[nodiscard]] qreal topLeftRadius() const { return m_topLeftRadius; } @@ -100,6 +116,7 @@ class BlobRect2 : public BlobShape2 { void deformScaleChanged(); void excludeChanged(); void excludeCornersChanged(); + void atRestOverrideChanged(); void topLeftRadiusChanged(); void topRightRadiusChanged(); void bottomLeftRadiusChanged(); @@ -119,6 +136,10 @@ class BlobRect2 : public BlobShape2 { bool m_physicsActive = false; bool m_hasPrevPos = false; + // Set from QML. Keeps the shape in the group's data while it is at rest; when it is + // false the spring state decides on its own. + bool m_atRestOverride = false; + bool m_retiring = false; QElapsedTimer m_retireElapsed; float m_retire00 = 1.0f; diff --git a/Plugins/ZShell/Blobs/blobshape2.hpp b/Plugins/ZShell/Blobs/blobshape2.hpp index caadd8f..230b660 100644 --- a/Plugins/ZShell/Blobs/blobshape2.hpp +++ b/Plugins/ZShell/Blobs/blobshape2.hpp @@ -56,8 +56,8 @@ class BlobShape2 : public QQuickItem { virtual bool isExcluded(const BlobShape2*) const { return false; } - // Whether this shape is currently pushing on a border. Only the shape that is pushing - // can shape the bar edge, so this decides the source flag. + // Whether this shape is part of the group's data. Only a pushing shape is, so a shape + // at rest has no part in the draw. BlobRect2 also honours the QML atRestOverride. virtual bool isMoving() const { return false; } virtual bool isCornerExcluded(const BlobShape2* /*other*/) const { diff --git a/Plugins/ZShell/Blobs/shaders/blob.frag b/Plugins/ZShell/Blobs/shaders/blob.frag index 791f272..1231bc6 100644 --- a/Plugins/ZShell/Blobs/shaders/blob.frag +++ b/Plugins/ZShell/Blobs/shaders/blob.frag @@ -65,19 +65,6 @@ float smaxSharpA(float a, float b, float k) { void main() { vec2 pixel = vec2(paddedX, paddedY) + qt_TexCoord0 * vec2(paddedW, paddedH); - // The frame distances are needed before the per-rect pass. In the border strip the - // boundary is the frame, displaced only by the shape that is pushing, so a shape that - // is not the source must not define the boundary there. It still fills the inner area, - // so this only gates the strip, which is the bar edge. - float dOuter = 0.0; - float dInner = 0.0; - bool inStrip = false; - if (hasInverted != 0) { - dOuter = sdBox(pixel, invertedOuter.xy, invertedOuter.zw) - 1.0; - dInner = sdRoundedBox(pixel, invertedInner.xy, invertedInner.zw, invertedRadius); - inStrip = dOuter < 0.0 && dInner > 0.0; - } - // Phase 1: compute per-rect SDF, and track which rect owns the pixel for the // per-shape path (BlobGroup). The single group draw (BlobGroup2) has no owner: // myIndex stays -2, so the ownership test is skipped and this pass writes every @@ -91,17 +78,10 @@ void main() { vec4 props = rectData[i * 5 + 1]; // excludeMask(int bits), offsetX, offsetY, minEig vec4 invDm = rectData[i * 5 + 2]; // inverse deform matrix - vec4 sh = rectData[i * 5 + 3]; // screenHalfX, screenHalfY, isSource, 0 + vec4 sh = rectData[i * 5 + 3]; // screenHalfX, screenHalfY, 0, 0 vec4 radii = rectData[i * 5 + 4]; // effective per-corner radii (tr, br, bl, tl) - // A shape that is not pushing does not shape the bar edge, so inside the strip it - // must not add its own outline to a span another shape is already shaping. - if (inStrip && sh.z < 0.5) { - dArr[i] = 1e10; - continue; - } - // Offset center for asymmetric deformation vec2 center = rect.xy + props.yz; @@ -202,6 +182,10 @@ void main() { } if (hasInverted != 0) { + float dOuter = sdBox(pixel, invertedOuter.xy, invertedOuter.zw) - 1.0; + float dInner = + sdRoundedBox(pixel, invertedInner.xy, invertedInner.zw, invertedRadius); + // Border sinks: track the opposite rect edge, clamped to border thickness float innerTop = invertedInner.y - invertedInner.w; float innerBot = invertedInner.y + invertedInner.w; @@ -214,10 +198,6 @@ void main() { float sinkValue = 0.0; for (int i = 0; i < rectCount; i++) { - // Only a shape that is pushing can move the border. - if (rectData[i * 5 + 3].z < 0.5) - continue; - vec4 rect = rectData[i * 5]; vec4 sinkProps = rectData[i * 5 + 1]; vec2 sinkSh = rectData[i * 5 + 3].xy; @@ -228,7 +208,7 @@ void main() { // Delay sink to absorb smin blend depth (cubic smin max = k/6) float preOff = smoothFactor * (1.0 / 6.0); - // Top border: track rect's BOTTOM edge, clamped to border thickness + // Top border: track rect's BOTTOM edge, only within border thickness float topPen = clamp(innerTop - (ctr.y + sinkSh.y) - preOff, 0.0, innerTop - outerTop); diff --git a/Plugins/ZShell/Components/CMakeLists.txt b/Plugins/ZShell/Components/CMakeLists.txt index b85ad41..6d21cc0 100644 --- a/Plugins/ZShell/Components/CMakeLists.txt +++ b/Plugins/ZShell/Components/CMakeLists.txt @@ -4,6 +4,7 @@ qml_module(ZShell-components lazylistview.hpp lazylistview.cpp chatlistview.hpp chatlistview.cpp wavyline.hpp wavyline.cpp + wavylinerenderer.hpp wavylinerenderer.cpp buttonrow.hpp buttonrow.cpp carouselview.hpp carouselview.cpp sparklinecontent.hpp sparklinecontent.cpp @@ -13,5 +14,6 @@ qml_module(ZShell-components LIBRARIES Qt::Quick Qt::QuickPrivate + Qt::CanvasPainter ZShell-internal ) diff --git a/Plugins/ZShell/Components/wavyline.cpp b/Plugins/ZShell/Components/wavyline.cpp index 8619f05..e5c2539 100644 --- a/Plugins/ZShell/Components/wavyline.cpp +++ b/Plugins/ZShell/Components/wavyline.cpp @@ -1,12 +1,10 @@ #include "wavyline.hpp" - -#include -#include +#include "wavylinerenderer.hpp" namespace ZShell::controls { WavyLine::WavyLine(QQuickItem* parent) - : QQuickPaintedItem(parent) + : QCanvasPainterItem(parent) , m_lineWidth(4) , m_amplitudeMultiplier(0.5) , m_frequency(6) @@ -18,10 +16,13 @@ WavyLine::WavyLine(QQuickItem* parent) , m_startAngle(0) , m_fullAngle(360) , m_radius(-1) - , m_value(1) - , m_startAngleRad(0) - , m_fullAngleRad(2 * M_PI) { - setAntialiasing(true); + , m_value(1) { + setFillColor(Qt::transparent); + setAlphaBlending(true); +} + +QCanvasPainterItemRenderer* WavyLine::createItemRenderer() const { + return new WavyLineRenderer; } int WavyLine::lineWidth() const { @@ -127,7 +128,6 @@ qreal WavyLine::startAngle() const { void WavyLine::setStartAngle(qreal startAngle) { if (!qFuzzyCompare(m_startAngle + 1.0, startAngle + 1.0)) { m_startAngle = startAngle; - m_startAngleRad = startAngle * M_PI / 180.0; emit startAngleChanged(); update(); } @@ -140,7 +140,6 @@ qreal WavyLine::fullAngle() const { void WavyLine::setFullAngle(qreal fullAngle) { if (!qFuzzyCompare(m_fullAngle + 1.0, fullAngle + 1.0)) { m_fullAngle = fullAngle; - m_fullAngleRad = fullAngle * M_PI / 180.0; emit fullAngleChanged(); update(); } @@ -170,90 +169,4 @@ void WavyLine::setValue(qreal value) { } } -void WavyLine::paint(QPainter* painter) { - painter->setRenderHint(QPainter::Antialiasing); - painter->setPen(QPen(m_color, m_lineWidth, Qt::SolidLine, Qt::RoundCap)); - - if (m_pathType == Arc) { - paintArc(painter); - } else { - paintLinear(painter); - } -} - -void WavyLine::paintLinear(QPainter* painter) { - const auto amplitude = m_lineWidth * m_amplitudeMultiplier; - const auto phase = m_waveProgress * 2 * M_PI; - const auto centerY = height() / 2; - 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 drawEnd = start + (fullEnd - start) * m_value; - - QPainterPath path; - bool first = true; - - for (int x = m_lineWidth / 2; x <= drawEnd; ++x) { - const auto theta = - m_frequency * 2 * M_PI * (x + m_startX) / len + phase; - const auto waveY = centerY + amplitude * qSin(theta); - if (first) { - path.moveTo(x, waveY); - first = false; - } else { - path.lineTo(x, waveY); - } - } - - painter->drawPath(path); -} - -void WavyLine::paintArc(QPainter* painter) { - if (m_fullAngleRad <= 0) { - return; - } - - 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; - - if (radius <= 0) { - return; - } - - const auto phase = m_waveProgress * 2 * M_PI; - const auto arcLen = radius * m_fullAngleRad; - const auto len = m_fullLength > 0 ? m_fullLength : arcLen; - const auto drawAngleRad = m_fullAngleRad * m_value; - - if (drawAngleRad <= 0) { - return; - } - - const auto N = qMax(64, qCeil(radius * drawAngleRad)); - const auto dTheta = drawAngleRad / N; - - QPainterPath path; - - for (int i = 0; i <= N; ++i) { - const auto theta = m_startAngleRad + i * dTheta; - const auto s = i * dTheta * radius; - const auto phi = m_frequency * 2 * M_PI * (s + m_startX) / len + phase; - const auto r = radius + amplitude * qSin(phi); - const auto px = cx + r * qCos(theta); - const auto py = cy + r * qSin(theta); - if (i == 0) { - path.moveTo(px, py); - } else { - path.lineTo(px, py); - } - } - - painter->drawPath(path); -} - } // namespace ZShell::controls diff --git a/Plugins/ZShell/Components/wavyline.hpp b/Plugins/ZShell/Components/wavyline.hpp index 8f25589..5ce3b8e 100644 --- a/Plugins/ZShell/Components/wavyline.hpp +++ b/Plugins/ZShell/Components/wavyline.hpp @@ -1,11 +1,14 @@ #pragma once #include -#include +#include +#include namespace ZShell::controls { -class WavyLine : public QQuickPaintedItem { +class WavyLineRenderer; + +class WavyLine : public QCanvasPainterItem { Q_OBJECT QML_ELEMENT @@ -82,9 +85,7 @@ class WavyLine : public QQuickPaintedItem { [[nodiscard]] qreal value() const; void setValue(qreal value); - void paint(QPainter* painter) override; - - signals: +signals: void lineWidthChanged(); void amplitudeMultiplierChanged(); void frequencyChanged(); @@ -98,9 +99,12 @@ class WavyLine : public QQuickPaintedItem { void radiusChanged(); void valueChanged(); + protected: + [[nodiscard]] QCanvasPainterItemRenderer* createItemRenderer() + const override; + private: - void paintLinear(QPainter* painter); - void paintArc(QPainter* painter); + friend class WavyLineRenderer; int m_lineWidth; qreal m_amplitudeMultiplier; @@ -114,8 +118,6 @@ class WavyLine : public QQuickPaintedItem { qreal m_fullAngle; qreal m_radius; qreal m_value; - qreal m_startAngleRad; - qreal m_fullAngleRad; }; } // namespace ZShell::controls diff --git a/Plugins/ZShell/Components/wavylinerenderer.cpp b/Plugins/ZShell/Components/wavylinerenderer.cpp new file mode 100644 index 0000000..7da2b07 --- /dev/null +++ b/Plugins/ZShell/Components/wavylinerenderer.cpp @@ -0,0 +1,116 @@ +#include "wavylinerenderer.hpp" +#include "wavyline.hpp" +#include + +namespace ZShell::controls { + +void WavyLineRenderer::synchronizeData(QCanvasPainterItem* item) { + auto* wavyLine = static_cast(item); + + m_lineWidth = wavyLine->m_lineWidth; + m_amplitudeMultiplier = wavyLine->m_amplitudeMultiplier; + m_frequency = wavyLine->m_frequency; + m_startX = wavyLine->m_startX; + m_fullLength = wavyLine->m_fullLength; + m_color = wavyLine->m_color; + m_waveProgress = wavyLine->m_waveProgress; + m_pathType = wavyLine->m_pathType; + m_startAngle = wavyLine->m_startAngle; + m_fullAngle = wavyLine->m_fullAngle; + m_radius = wavyLine->m_radius; + m_value = wavyLine->m_value; +} + +void WavyLineRenderer::paint(QCanvasPainter* painter) { + painter->clearRect(0, 0, width(), height()); + + painter->setStrokeStyle(m_color); + painter->setLineWidth(m_lineWidth); + painter->setLineCap(QCanvasPainter::LineCap::Round); + + if (m_pathType == WavyLine::Arc) { + paintArc(painter); + } else { + paintLinear(painter); + } +} + +void WavyLineRenderer::paintLinear(QCanvasPainter* painter) { + const auto amplitude = m_lineWidth * m_amplitudeMultiplier; + const auto phase = m_waveProgress * 2 * M_PI; + const auto centerY = height() / 2; + 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 drawEnd = start + (fullEnd - start) * m_value; + + painter->beginPath(); + + bool first = true; + for (int x = m_lineWidth / 2; x <= drawEnd; ++x) { + const auto theta = + m_frequency * 2 * M_PI * (x + m_startX) / len + phase; + const auto waveY = centerY + amplitude * qSin(theta); + if (first) { + painter->moveTo(x, waveY); + first = false; + } else { + painter->lineTo(x, waveY); + } + } + + painter->stroke(); +} + +void WavyLineRenderer::paintArc(QCanvasPainter* painter) { + if (m_fullAngle <= 0) { + return; + } + + 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; + + if (radius <= 0) { + return; + } + + const auto phase = m_waveProgress * 2 * M_PI; + const auto arcLen = radius * fullAngleRad; + const auto len = m_fullLength > 0 ? m_fullLength : arcLen; + const auto drawAngleRad = fullAngleRad * m_value; + + if (drawAngleRad <= 0) { + return; + } + + const auto N = qMax(64, qCeil(radius * drawAngleRad)); + const auto dTheta = drawAngleRad / N; + + painter->beginPath(); + + for (int i = 0; i <= N; ++i) { + const auto theta = startAngleRad + i * dTheta; + const auto s = i * dTheta * radius; + const auto phi = m_frequency * 2 * M_PI * (s + m_startX) / len + phase; + const auto r = radius + amplitude * qSin(phi); + const auto px = cx + r * qCos(theta); + const auto py = cy + r * qSin(theta); + if (i == 0) { + painter->moveTo(px, py); + } else { + painter->lineTo(px, py); + } + } + + painter->stroke(); +} + +} // namespace ZShell::controls diff --git a/Plugins/ZShell/Components/wavylinerenderer.hpp b/Plugins/ZShell/Components/wavylinerenderer.hpp new file mode 100644 index 0000000..191ca5f --- /dev/null +++ b/Plugins/ZShell/Components/wavylinerenderer.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include "wavyline.hpp" + +namespace ZShell::controls { + +class WavyLineRenderer final : public QCanvasPainterItemRenderer { + public: + void synchronizeData(QCanvasPainterItem* item) override; + void paint(QCanvasPainter* painter) override; + + private: + void paintLinear(QCanvasPainter* painter); + void paintArc(QCanvasPainter* painter); + + int m_lineWidth; + qreal m_amplitudeMultiplier; + int m_frequency; + qreal m_startX; + qreal m_fullLength; + QColor m_color; + qreal m_waveProgress; + WavyLine::PathType m_pathType; + qreal m_startAngle; + qreal m_fullAngle; + qreal m_radius; + qreal m_value; +}; + +} // namespace ZShell::controls diff --git a/Plugins/ZShell/Internal/CMakeLists.txt b/Plugins/ZShell/Internal/CMakeLists.txt index 62911fd..b2c039a 100644 --- a/Plugins/ZShell/Internal/CMakeLists.txt +++ b/Plugins/ZShell/Internal/CMakeLists.txt @@ -1,7 +1,6 @@ wl_proto(wlp-layer-shell wlr-layer-shell-unstable-v1 "${CMAKE_CURRENT_SOURCE_DIR}/protocols") wl_proto(wlp-viewporter viewporter "${WAYLAND_PROTOCOLS}/stable/viewporter") wl_proto(wlp-fractional-scale fractional-scale-v1 "${WAYLAND_PROTOCOLS}/staging/fractional-scale") - wl_proto(wlp-xdg-shell xdg-shell "${WAYLAND_PROTOCOLS}/stable/xdg-shell") qml_module(ZShell-internal