fix: ignore stale coordinate data for panels + convert wavyline to use new qt 6.12 canvas painter api
This commit is contained in:
@@ -248,6 +248,7 @@ CustomWindow {
|
||||
PanelBg {
|
||||
id: osdBg
|
||||
|
||||
atRestOverride: panels.osd.offsetScale < 0.99
|
||||
deformAmount: 0.1
|
||||
implicitHeight: panels.osd.height
|
||||
implicitWidth: panels.osd.width
|
||||
@@ -296,6 +297,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)
|
||||
@@ -449,6 +451,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
|
||||
|
||||
+4
-3
@@ -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 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -138,11 +138,16 @@ void BlobGroup2::ensureDataBuilt() {
|
||||
const double extent = static_cast<double>(smoothFactor) * 1.5 +
|
||||
static_cast<double>(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<BlobShape2*> entryShapes;
|
||||
|
||||
for (auto* shape : std::as_const(m_shapes)) {
|
||||
if (!shape->isMoving()) continue;
|
||||
if (shape->width() <= 0 || shape->height() <= 0) continue;
|
||||
if (static_cast<int>(m_data.rects.size()) >= BlobGroupData::kMaxRects)
|
||||
continue;
|
||||
@@ -189,9 +194,6 @@ void BlobGroup2::ensureDataBuilt() {
|
||||
|
||||
const int count = static_cast<int>(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<BlobShape2*>(m_invertedRect);
|
||||
@@ -302,28 +304,6 @@ void BlobGroup2::ensureDataBuilt() {
|
||||
2.0 * (static_cast<double>(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<BlobShape2*>& entryShapes) {
|
||||
const int count = static_cast<int>(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<float>(m_bounds.x());
|
||||
material->m_paddedY = static_cast<float>(m_bounds.y());
|
||||
|
||||
@@ -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<BlobRectData> rects;
|
||||
@@ -111,7 +109,6 @@ class BlobGroup2 : public QQuickItem {
|
||||
private:
|
||||
void ensurePhysicsUpdated();
|
||||
void ensureDataBuilt();
|
||||
void resolveSources(const QVector<BlobShape2*>& entryShapes);
|
||||
void fillMaterial(BlobMaterial* material);
|
||||
|
||||
qreal m_smoothing = 32.0;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -22,6 +22,9 @@ class BlobRect2 : public BlobShape2 {
|
||||
Q_PROPERTY(
|
||||
QQmlListProperty<BlobRect2> 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<BlobRect2> exclude();
|
||||
QQmlListProperty<BlobRect2> 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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ qml_module(ZShell-components
|
||||
SOURCES
|
||||
lazylistview.hpp lazylistview.cpp
|
||||
wavyline.hpp wavyline.cpp
|
||||
wavylinerenderer.hpp wavylinerenderer.cpp
|
||||
buttonrow.hpp buttonrow.cpp
|
||||
carouselview.hpp carouselview.cpp
|
||||
sparklinecontent.hpp sparklinecontent.cpp
|
||||
@@ -11,5 +12,6 @@ qml_module(ZShell-components
|
||||
LIBRARIES
|
||||
Qt::Quick
|
||||
Qt::QuickPrivate
|
||||
Qt::CanvasPainter
|
||||
ZShell-internal
|
||||
)
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
#include "wavyline.hpp"
|
||||
|
||||
#include <qpainter.h>
|
||||
#include <qpainterpath.h>
|
||||
#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
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <qqmlintegration.h>
|
||||
#include <qquickpainteditem.h>
|
||||
#include <QCanvasPainterItem>
|
||||
#include <QColor>
|
||||
|
||||
namespace ZShell::controls {
|
||||
|
||||
class WavyLine : public QQuickPaintedItem {
|
||||
class WavyLineRenderer;
|
||||
|
||||
class WavyLine : public QCanvasPainterItem {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
@@ -82,8 +85,6 @@ class WavyLine : public QQuickPaintedItem {
|
||||
[[nodiscard]] qreal value() const;
|
||||
void setValue(qreal value);
|
||||
|
||||
void paint(QPainter* painter) override;
|
||||
|
||||
signals:
|
||||
void lineWidthChanged();
|
||||
void amplitudeMultiplierChanged();
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
#include "wavylinerenderer.hpp"
|
||||
#include "wavyline.hpp"
|
||||
#include <qcanvaspainter.h>
|
||||
|
||||
namespace ZShell::controls {
|
||||
|
||||
void WavyLineRenderer::synchronizeData(QCanvasPainterItem* item) {
|
||||
auto* wavyLine = static_cast<WavyLine*>(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
|
||||
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <QCanvasPainterItemRenderer>
|
||||
#include <QColor>
|
||||
#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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user