more unfinished changes, process tab for dashboard
This commit is contained in:
@@ -5,6 +5,12 @@ qml_module(ZShell-components
|
||||
wavyline.hpp wavyline.cpp
|
||||
buttonrow.hpp buttonrow.cpp
|
||||
carouselview.hpp carouselview.cpp
|
||||
LIBRARIES
|
||||
Qt::Quick
|
||||
wheelinverter.hpp
|
||||
sparklinecontent.hpp sparklinecontent.cpp
|
||||
sparklineitem.hpp sparklineitem.cpp
|
||||
sparklinesceneitem.hpp sparklinesceneitem.cpp
|
||||
LIBRARIES
|
||||
Qt::Quick
|
||||
Qt::QuickPrivate
|
||||
ZShell-internal
|
||||
)
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
#include "sparklinecontent.hpp"
|
||||
|
||||
#include <qmath.h>
|
||||
#include <qpainter.h>
|
||||
#include <qpen.h>
|
||||
|
||||
namespace ZShell::components {
|
||||
|
||||
SparklineContent::SparklineContent(QQuickItem* parent)
|
||||
: QQuickPaintedItem(parent) {
|
||||
setAntialiasing(true);
|
||||
}
|
||||
|
||||
qreal SparklineContent::stepX() const {
|
||||
if (m_historyLength < 2 || m_viewportW <= 0.0) return 0.0;
|
||||
if (m_sliding) return m_viewportW / static_cast<qreal>(m_historyLength - 1);
|
||||
|
||||
const int len = qMax(
|
||||
2,
|
||||
(m_line1 && m_line1->count() >= 2) ? m_line1->count()
|
||||
: (m_line2 ? m_line2->count() : 2));
|
||||
return m_viewportW / static_cast<qreal>(len - 1);
|
||||
}
|
||||
|
||||
void SparklineContent::setViewportSize(
|
||||
qreal viewportWidth, qreal viewportHeight) {
|
||||
if (qFuzzyCompare(m_viewportW, viewportWidth) &&
|
||||
qFuzzyCompare(m_viewportH, viewportHeight))
|
||||
return;
|
||||
m_viewportW = viewportWidth;
|
||||
m_viewportH = viewportHeight;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void SparklineContent::setLine1(internal::CircularBuffer* buffer) {
|
||||
if (m_line1 == buffer) return;
|
||||
if (m_line1) disconnect(m_line1, nullptr, this, nullptr);
|
||||
m_line1 = buffer;
|
||||
connectBuffer(buffer);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void SparklineContent::setLine2(internal::CircularBuffer* buffer) {
|
||||
if (m_line2 == buffer) return;
|
||||
if (m_line2) disconnect(m_line2, nullptr, this, nullptr);
|
||||
m_line2 = buffer;
|
||||
connectBuffer(buffer);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void SparklineContent::connectBuffer(internal::CircularBuffer* buffer) {
|
||||
if (!buffer) return;
|
||||
// New data, or the buffer's own smoothingRadius changed (it re-emits
|
||||
// valuesChanged in that case too) -> geometry is stale.
|
||||
connect(
|
||||
buffer,
|
||||
&internal::CircularBuffer::valuesChanged,
|
||||
this,
|
||||
&SparklineContent::invalidate);
|
||||
connect(buffer, &QObject::destroyed, this, [this, buffer]() {
|
||||
if (m_line1 == buffer) m_line1 = nullptr;
|
||||
if (m_line2 == buffer) m_line2 = nullptr;
|
||||
invalidate();
|
||||
});
|
||||
}
|
||||
|
||||
void SparklineContent::setLine1Color(const QColor& color) {
|
||||
if (m_line1Color == color) return;
|
||||
m_line1Color = color;
|
||||
update(); // visual-only, no geometry rebuild needed
|
||||
}
|
||||
|
||||
void SparklineContent::setLine2Color(const QColor& color) {
|
||||
if (m_line2Color == color) return;
|
||||
m_line2Color = color;
|
||||
update();
|
||||
}
|
||||
|
||||
void SparklineContent::setLine1FillAlpha(qreal alpha) {
|
||||
if (qFuzzyCompare(m_line1FillAlpha, alpha)) return;
|
||||
m_line1FillAlpha = alpha;
|
||||
update();
|
||||
}
|
||||
|
||||
void SparklineContent::setLine2FillAlpha(qreal alpha) {
|
||||
if (qFuzzyCompare(m_line2FillAlpha, alpha)) return;
|
||||
m_line2FillAlpha = alpha;
|
||||
update();
|
||||
}
|
||||
|
||||
void SparklineContent::setMaxValue(qreal value) {
|
||||
if (qFuzzyCompare(m_maxValue, value)) return;
|
||||
m_maxValue = value;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void SparklineContent::setLineWidth(qreal width) {
|
||||
if (qFuzzyCompare(m_lineWidth, width)) return;
|
||||
m_lineWidth = width;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void SparklineContent::setCurveSmoothing(bool enabled) {
|
||||
if (m_curveSmoothing == enabled) return;
|
||||
m_curveSmoothing = enabled;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void SparklineContent::setHistoryLength(int length) {
|
||||
if (m_historyLength == length) return;
|
||||
m_historyLength = length;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void SparklineContent::setSliding(bool sliding) {
|
||||
if (m_sliding == sliding) return;
|
||||
m_sliding = sliding;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void SparklineContent::invalidate() {
|
||||
m_dirty = true;
|
||||
applyGeometry();
|
||||
update();
|
||||
}
|
||||
|
||||
void SparklineContent::applyGeometry() {
|
||||
const qreal pad = m_sliding ? stepX() : 0.0;
|
||||
setSize(QSizeF(m_viewportW + pad, m_viewportH));
|
||||
}
|
||||
|
||||
SparklineContent::LineGeometry SparklineContent::buildGeometry(
|
||||
internal::CircularBuffer* buffer) const {
|
||||
LineGeometry geom;
|
||||
if (!buffer || buffer->count() < 2 || m_maxValue <= 0.0 ||
|
||||
m_viewportW <= 0.0)
|
||||
return geom;
|
||||
if (m_historyLength < 2) return geom;
|
||||
|
||||
const int len = buffer->count();
|
||||
const qreal step = stepX();
|
||||
if (step <= 0.0) return geom;
|
||||
|
||||
qreal startX = 0.0;
|
||||
if (m_sliding)
|
||||
startX = m_viewportW - (len - 1) * step + step;
|
||||
else
|
||||
startX = 0.0;
|
||||
|
||||
const qreal h = m_viewportH;
|
||||
const qreal strokePad = qCeil(m_lineWidth / 2);
|
||||
const qreal curvePad = 3.0;
|
||||
const qreal plotTop = strokePad + curvePad;
|
||||
const qreal plotBottom = h - strokePad;
|
||||
const qreal fillBottom = h;
|
||||
const qreal plotH = qMax<qreal>(1.0, plotBottom - plotTop);
|
||||
|
||||
QVector<QPointF> points;
|
||||
points.reserve(len);
|
||||
|
||||
for (int i = 0; i < len; ++i) {
|
||||
const qreal value =
|
||||
qBound<qreal>(0.0, buffer->smoothedAt(i), m_maxValue);
|
||||
|
||||
const qreal x = startX + i * step;
|
||||
const qreal y = plotTop + (1.0 - (value / m_maxValue)) * plotH;
|
||||
points.append(QPointF(x, y));
|
||||
}
|
||||
|
||||
QPainterPath linePath;
|
||||
linePath.moveTo(points[0]);
|
||||
|
||||
if (m_curveSmoothing && points.size() > 2) {
|
||||
const qreal tension = 1.0;
|
||||
const qreal k = tension / 6.0;
|
||||
const int n = points.size();
|
||||
|
||||
for (int i = 0; i < n - 1; ++i) {
|
||||
const QPointF& p0 = points[qMax(i - 1, 0)];
|
||||
const QPointF& p1 = points[i];
|
||||
const QPointF& p2 = points[i + 1];
|
||||
const QPointF& p3 = points[qMin(i + 2, n - 1)];
|
||||
|
||||
QPointF c1 = p1 + (p2 - p0) * k;
|
||||
QPointF c2 = p2 - (p3 - p1) * k;
|
||||
|
||||
const qreal segMinY = qMin(p1.y(), p2.y());
|
||||
const qreal segMaxY = qMax(p1.y(), p2.y());
|
||||
c1.setY(qBound(segMinY, c1.y(), segMaxY));
|
||||
c2.setY(qBound(segMinY, c2.y(), segMaxY));
|
||||
|
||||
linePath.cubicTo(c1, c2, p2);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < points.size() - 1; ++i) {
|
||||
const QPointF& p0 = points[i];
|
||||
const QPointF& p1 = points[i + 1];
|
||||
const qreal ctrlX = (p0.x() + p1.x()) * 0.5;
|
||||
linePath.cubicTo(QPointF(ctrlX, p0.y()), QPointF(ctrlX, p1.y()), p1);
|
||||
}
|
||||
}
|
||||
|
||||
geom.stroke = linePath;
|
||||
|
||||
QPainterPath fillPath = linePath;
|
||||
fillPath.lineTo(startX + (len - 1) * step, fillBottom);
|
||||
fillPath.lineTo(startX, fillBottom);
|
||||
fillPath.closeSubpath();
|
||||
geom.fill = fillPath;
|
||||
geom.hasData = true;
|
||||
|
||||
return geom;
|
||||
}
|
||||
|
||||
void SparklineContent::rebuildIfNeeded() {
|
||||
if (!m_dirty) return;
|
||||
m_geom1 = buildGeometry(m_line1);
|
||||
m_geom2 = buildGeometry(m_line2);
|
||||
m_dirty = false;
|
||||
}
|
||||
|
||||
void SparklineContent::paint(QPainter* painter) {
|
||||
rebuildIfNeeded();
|
||||
if (!m_geom1.hasData && !m_geom2.hasData) return;
|
||||
|
||||
painter->setRenderHint(QPainter::Antialiasing, true);
|
||||
|
||||
auto drawOne =
|
||||
[&](const LineGeometry& geom, const QColor& color, qreal fillAlpha) {
|
||||
if (!geom.hasData) return;
|
||||
|
||||
QColor fillColor = color;
|
||||
fillColor.setAlphaF(static_cast<float>(fillAlpha));
|
||||
painter->setPen(Qt::NoPen);
|
||||
painter->setBrush(fillColor);
|
||||
painter->drawPath(geom.fill);
|
||||
|
||||
QPen pen(color, m_lineWidth);
|
||||
pen.setCapStyle(Qt::RoundCap);
|
||||
pen.setJoinStyle(Qt::RoundJoin);
|
||||
painter->setPen(pen);
|
||||
painter->setBrush(Qt::NoBrush);
|
||||
painter->drawPath(geom.stroke);
|
||||
};
|
||||
|
||||
drawOne(m_geom1, m_line1Color, m_line1FillAlpha);
|
||||
drawOne(m_geom2, m_line2Color, m_line2FillAlpha);
|
||||
}
|
||||
|
||||
} // namespace ZShell::components
|
||||
@@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
|
||||
#include <qcolor.h>
|
||||
#include <qpainterpath.h>
|
||||
#include <qquickpainteditem.h>
|
||||
|
||||
#include "internal/circularbuffer.hpp"
|
||||
|
||||
namespace ZShell::components {
|
||||
|
||||
class SparklineContent : public QQuickPaintedItem {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SparklineContent(QQuickItem* parent = nullptr);
|
||||
|
||||
void paint(QPainter* painter) override;
|
||||
|
||||
void setLine1(internal::CircularBuffer* buffer);
|
||||
void setLine2(internal::CircularBuffer* buffer);
|
||||
void setLine1Color(const QColor& color);
|
||||
void setLine2Color(const QColor& color);
|
||||
void setLine1FillAlpha(qreal alpha);
|
||||
void setLine2FillAlpha(qreal alpha);
|
||||
void setMaxValue(qreal value);
|
||||
void setLineWidth(qreal width);
|
||||
void setCurveSmoothing(bool enabled);
|
||||
void setHistoryLength(int length);
|
||||
void setSliding(bool sliding);
|
||||
|
||||
void setViewportSize(qreal viewportWidth, qreal viewportHeight);
|
||||
|
||||
[[nodiscard]] qreal stepX() const;
|
||||
|
||||
private:
|
||||
struct LineGeometry {
|
||||
QPainterPath stroke;
|
||||
QPainterPath fill;
|
||||
bool hasData = false;
|
||||
};
|
||||
|
||||
void invalidate();
|
||||
void rebuildIfNeeded();
|
||||
[[nodiscard]] LineGeometry buildGeometry(
|
||||
internal::CircularBuffer* buffer) const;
|
||||
void connectBuffer(internal::CircularBuffer* buffer);
|
||||
void applyGeometry();
|
||||
|
||||
internal::CircularBuffer* m_line1 = nullptr;
|
||||
internal::CircularBuffer* m_line2 = nullptr;
|
||||
QColor m_line1Color;
|
||||
QColor m_line2Color;
|
||||
qreal m_line1FillAlpha = 0.15;
|
||||
qreal m_line2FillAlpha = 0.2;
|
||||
qreal m_maxValue = 1024.0;
|
||||
qreal m_lineWidth = 2.0;
|
||||
bool m_curveSmoothing = false;
|
||||
int m_historyLength = 30;
|
||||
bool m_sliding = true;
|
||||
|
||||
qreal m_viewportW = 0.0;
|
||||
qreal m_viewportH = 0.0;
|
||||
|
||||
LineGeometry m_geom1;
|
||||
LineGeometry m_geom2;
|
||||
bool m_dirty = true;
|
||||
};
|
||||
|
||||
} // namespace ZShell::components
|
||||
@@ -0,0 +1,179 @@
|
||||
#include "sparklineitem.hpp"
|
||||
|
||||
#include "sparklinecontent.hpp"
|
||||
|
||||
namespace ZShell::components {
|
||||
|
||||
SparklineItem::SparklineItem(QQuickItem* parent) : QQuickItem(parent) {
|
||||
setClip(true);
|
||||
m_content = new SparklineContent(this);
|
||||
m_content->setHistoryLength(m_historyLength);
|
||||
m_content->setSliding(m_sliding);
|
||||
}
|
||||
|
||||
void SparklineItem::geometryChange(
|
||||
const QRectF& newGeometry, const QRectF& oldGeometry) {
|
||||
QQuickItem::geometryChange(newGeometry, oldGeometry);
|
||||
m_content->setViewportSize(newGeometry.width(), newGeometry.height());
|
||||
updateContentOffset();
|
||||
}
|
||||
|
||||
void SparklineItem::updateContentOffset() {
|
||||
m_content->setX(-m_content->stepX() * m_slideProgress);
|
||||
}
|
||||
|
||||
void SparklineItem::applySmoothingRadius(
|
||||
internal::CircularBuffer* buffer) const {
|
||||
if (buffer) buffer->setSmoothingRadius(qRound(m_smoothing));
|
||||
}
|
||||
|
||||
internal::CircularBuffer* SparklineItem::line1() const {
|
||||
return m_line1;
|
||||
}
|
||||
|
||||
void SparklineItem::setLine1(internal::CircularBuffer* buffer) {
|
||||
if (m_line1 == buffer) return;
|
||||
m_line1 = buffer;
|
||||
m_content->setLine1(buffer);
|
||||
applySmoothingRadius(buffer);
|
||||
emit line1Changed();
|
||||
}
|
||||
|
||||
internal::CircularBuffer* SparklineItem::line2() const {
|
||||
return m_line2;
|
||||
}
|
||||
|
||||
void SparklineItem::setLine2(internal::CircularBuffer* buffer) {
|
||||
if (m_line2 == buffer) return;
|
||||
m_line2 = buffer;
|
||||
m_content->setLine2(buffer);
|
||||
applySmoothingRadius(buffer);
|
||||
emit line2Changed();
|
||||
}
|
||||
|
||||
QColor SparklineItem::line1Color() const {
|
||||
return m_line1Color;
|
||||
}
|
||||
|
||||
void SparklineItem::setLine1Color(const QColor& color) {
|
||||
if (m_line1Color == color) return;
|
||||
m_line1Color = color;
|
||||
m_content->setLine1Color(color);
|
||||
emit line1ColorChanged();
|
||||
}
|
||||
|
||||
QColor SparklineItem::line2Color() const {
|
||||
return m_line2Color;
|
||||
}
|
||||
|
||||
void SparklineItem::setLine2Color(const QColor& color) {
|
||||
if (m_line2Color == color) return;
|
||||
m_line2Color = color;
|
||||
m_content->setLine2Color(color);
|
||||
emit line2ColorChanged();
|
||||
}
|
||||
|
||||
qreal SparklineItem::line1FillAlpha() const {
|
||||
return m_line1FillAlpha;
|
||||
}
|
||||
|
||||
void SparklineItem::setLine1FillAlpha(qreal alpha) {
|
||||
if (qFuzzyCompare(m_line1FillAlpha, alpha)) return;
|
||||
m_line1FillAlpha = alpha;
|
||||
m_content->setLine1FillAlpha(alpha);
|
||||
emit line1FillAlphaChanged();
|
||||
}
|
||||
|
||||
qreal SparklineItem::line2FillAlpha() const {
|
||||
return m_line2FillAlpha;
|
||||
}
|
||||
|
||||
void SparklineItem::setLine2FillAlpha(qreal alpha) {
|
||||
if (qFuzzyCompare(m_line2FillAlpha, alpha)) return;
|
||||
m_line2FillAlpha = alpha;
|
||||
m_content->setLine2FillAlpha(alpha);
|
||||
emit line2FillAlphaChanged();
|
||||
}
|
||||
|
||||
qreal SparklineItem::maxValue() const {
|
||||
return m_maxValue;
|
||||
}
|
||||
|
||||
void SparklineItem::setMaxValue(qreal value) {
|
||||
if (qFuzzyCompare(m_maxValue, value)) return;
|
||||
m_maxValue = value;
|
||||
m_content->setMaxValue(value);
|
||||
emit maxValueChanged();
|
||||
}
|
||||
|
||||
qreal SparklineItem::slideProgress() const {
|
||||
return m_slideProgress;
|
||||
}
|
||||
|
||||
void SparklineItem::setSlideProgress(qreal progress) {
|
||||
if (qFuzzyCompare(m_slideProgress, progress)) return;
|
||||
m_slideProgress = progress;
|
||||
updateContentOffset();
|
||||
emit slideProgressChanged();
|
||||
}
|
||||
|
||||
int SparklineItem::historyLength() const {
|
||||
return m_historyLength;
|
||||
}
|
||||
|
||||
void SparklineItem::setHistoryLength(int length) {
|
||||
if (m_historyLength == length) return;
|
||||
m_historyLength = length;
|
||||
m_content->setHistoryLength(length);
|
||||
updateContentOffset();
|
||||
emit historyLengthChanged();
|
||||
}
|
||||
|
||||
qreal SparklineItem::lineWidth() const {
|
||||
return m_lineWidth;
|
||||
}
|
||||
|
||||
void SparklineItem::setLineWidth(qreal width) {
|
||||
if (qFuzzyCompare(m_lineWidth, width)) return;
|
||||
m_lineWidth = width;
|
||||
m_content->setLineWidth(width);
|
||||
emit lineWidthChanged();
|
||||
}
|
||||
|
||||
void SparklineItem::setSliding(bool sliding) {
|
||||
if (m_sliding == sliding) return;
|
||||
m_sliding = sliding;
|
||||
m_content->setSliding(sliding);
|
||||
updateContentOffset();
|
||||
emit slidingChanged();
|
||||
}
|
||||
|
||||
bool SparklineItem::sliding() const {
|
||||
return m_sliding;
|
||||
}
|
||||
|
||||
void SparklineItem::setSmoothing(qreal smoothing) {
|
||||
smoothing = qMax<qreal>(0.0, smoothing);
|
||||
if (qFuzzyCompare(m_smoothing, smoothing)) return;
|
||||
m_smoothing = smoothing;
|
||||
applySmoothingRadius(m_line1);
|
||||
applySmoothingRadius(m_line2);
|
||||
emit smoothingChanged();
|
||||
}
|
||||
|
||||
qreal SparklineItem::smoothing() const {
|
||||
return m_smoothing;
|
||||
}
|
||||
|
||||
void SparklineItem::setCurveSmoothing(bool enabled) {
|
||||
if (m_curveSmoothing == enabled) return;
|
||||
m_curveSmoothing = enabled;
|
||||
m_content->setCurveSmoothing(enabled);
|
||||
emit curveSmoothingChanged();
|
||||
}
|
||||
|
||||
bool SparklineItem::curveSmoothing() const {
|
||||
return m_curveSmoothing;
|
||||
}
|
||||
|
||||
} // namespace ZShell::components
|
||||
@@ -0,0 +1,137 @@
|
||||
#pragma once
|
||||
|
||||
#include <qcolor.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qquickitem.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
#include "internal/circularbuffer.hpp"
|
||||
|
||||
namespace ZShell::components {
|
||||
|
||||
class SparklineContent;
|
||||
|
||||
class SparklineItem : public QQuickItem {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
Q_PROPERTY(
|
||||
internal::CircularBuffer* line1 READ line1 WRITE setLine1 NOTIFY
|
||||
line1Changed)
|
||||
Q_PROPERTY(
|
||||
internal::CircularBuffer* line2 READ line2 WRITE setLine2 NOTIFY
|
||||
line2Changed)
|
||||
Q_PROPERTY(
|
||||
QColor line1Color READ line1Color WRITE setLine1Color NOTIFY
|
||||
line1ColorChanged)
|
||||
Q_PROPERTY(
|
||||
QColor line2Color READ line2Color WRITE setLine2Color NOTIFY
|
||||
line2ColorChanged)
|
||||
Q_PROPERTY(
|
||||
qreal line1FillAlpha READ line1FillAlpha WRITE setLine1FillAlpha NOTIFY
|
||||
line1FillAlphaChanged)
|
||||
Q_PROPERTY(
|
||||
qreal line2FillAlpha READ line2FillAlpha WRITE setLine2FillAlpha NOTIFY
|
||||
line2FillAlphaChanged)
|
||||
Q_PROPERTY(
|
||||
qreal maxValue READ maxValue WRITE setMaxValue NOTIFY maxValueChanged)
|
||||
Q_PROPERTY(
|
||||
qreal slideProgress READ slideProgress WRITE setSlideProgress NOTIFY
|
||||
slideProgressChanged)
|
||||
Q_PROPERTY(
|
||||
int historyLength READ historyLength WRITE setHistoryLength NOTIFY
|
||||
historyLengthChanged)
|
||||
Q_PROPERTY(
|
||||
qreal lineWidth READ lineWidth WRITE setLineWidth NOTIFY
|
||||
lineWidthChanged)
|
||||
Q_PROPERTY(bool sliding READ sliding WRITE setSliding NOTIFY slidingChanged)
|
||||
Q_PROPERTY(
|
||||
qreal smoothing READ smoothing WRITE setSmoothing NOTIFY
|
||||
smoothingChanged)
|
||||
Q_PROPERTY(
|
||||
bool curveSmoothing READ curveSmoothing WRITE setCurveSmoothing NOTIFY
|
||||
curveSmoothingChanged)
|
||||
|
||||
public:
|
||||
explicit SparklineItem(QQuickItem* parent = nullptr);
|
||||
|
||||
[[nodiscard]] internal::CircularBuffer* line1() const;
|
||||
void setLine1(internal::CircularBuffer* buffer);
|
||||
|
||||
[[nodiscard]] internal::CircularBuffer* line2() const;
|
||||
void setLine2(internal::CircularBuffer* buffer);
|
||||
|
||||
[[nodiscard]] QColor line1Color() const;
|
||||
void setLine1Color(const QColor& color);
|
||||
|
||||
[[nodiscard]] QColor line2Color() const;
|
||||
void setLine2Color(const QColor& color);
|
||||
|
||||
[[nodiscard]] qreal line1FillAlpha() const;
|
||||
void setLine1FillAlpha(qreal alpha);
|
||||
|
||||
[[nodiscard]] qreal line2FillAlpha() const;
|
||||
void setLine2FillAlpha(qreal alpha);
|
||||
|
||||
[[nodiscard]] qreal maxValue() const;
|
||||
void setMaxValue(qreal value);
|
||||
|
||||
[[nodiscard]] qreal slideProgress() const;
|
||||
void setSlideProgress(qreal progress);
|
||||
|
||||
[[nodiscard]] int historyLength() const;
|
||||
void setHistoryLength(int length);
|
||||
|
||||
[[nodiscard]] qreal lineWidth() const;
|
||||
void setLineWidth(qreal width);
|
||||
|
||||
[[nodiscard]] bool sliding() const;
|
||||
void setSliding(bool sliding);
|
||||
|
||||
[[nodiscard]] qreal smoothing() const;
|
||||
void setSmoothing(qreal smoothing);
|
||||
|
||||
[[nodiscard]] bool curveSmoothing() const;
|
||||
void setCurveSmoothing(bool curveSmoothing);
|
||||
|
||||
signals:
|
||||
void line1Changed();
|
||||
void line2Changed();
|
||||
void line1ColorChanged();
|
||||
void line2ColorChanged();
|
||||
void line1FillAlphaChanged();
|
||||
void line2FillAlphaChanged();
|
||||
void maxValueChanged();
|
||||
void slideProgressChanged();
|
||||
void historyLengthChanged();
|
||||
void lineWidthChanged();
|
||||
void slidingChanged();
|
||||
void smoothingChanged();
|
||||
void curveSmoothingChanged();
|
||||
|
||||
protected:
|
||||
void geometryChange(
|
||||
const QRectF& newGeometry, const QRectF& oldGeometry) override;
|
||||
|
||||
private:
|
||||
void updateContentOffset();
|
||||
void applySmoothingRadius(internal::CircularBuffer* buffer) const;
|
||||
|
||||
SparklineContent* m_content = nullptr;
|
||||
internal::CircularBuffer* m_line1 = nullptr;
|
||||
internal::CircularBuffer* m_line2 = nullptr;
|
||||
QColor m_line1Color;
|
||||
QColor m_line2Color;
|
||||
qreal m_line1FillAlpha = 0.15;
|
||||
qreal m_line2FillAlpha = 0.2;
|
||||
qreal m_maxValue = 1024.0;
|
||||
qreal m_slideProgress = 0.0;
|
||||
int m_historyLength = 30;
|
||||
qreal m_lineWidth = 2.0;
|
||||
bool m_sliding = true;
|
||||
qreal m_smoothing = 0.0;
|
||||
bool m_curveSmoothing = false;
|
||||
};
|
||||
|
||||
} // namespace ZShell::components
|
||||
@@ -0,0 +1,447 @@
|
||||
#include "sparklinesceneitem.hpp"
|
||||
|
||||
#include <qmath.h>
|
||||
#include <qpainterpath.h>
|
||||
#include <qsgflatcolormaterial.h>
|
||||
#include <qsggeometry.h>
|
||||
|
||||
namespace ZShell::components {
|
||||
|
||||
SparklineSceneItem::SparklineSceneItem(QQuickItem* parent)
|
||||
: QQuickItem(parent) {
|
||||
setClip(true);
|
||||
setFlag(QQuickItem::ItemHasContents, true);
|
||||
}
|
||||
|
||||
void SparklineSceneItem::geometryChange(
|
||||
const QRectF& newGeometry, const QRectF& oldGeometry) {
|
||||
QQuickItem::geometryChange(newGeometry, oldGeometry);
|
||||
if (newGeometry.size() != oldGeometry.size()) invalidateGeometry();
|
||||
}
|
||||
|
||||
void SparklineSceneItem::invalidateGeometry() {
|
||||
m_geometryDirty = true;
|
||||
update();
|
||||
}
|
||||
|
||||
void SparklineSceneItem::applySmoothingRadius(
|
||||
internal::CircularBuffer* buffer) const {
|
||||
if (buffer) buffer->setSmoothingRadius(qRound(m_smoothing));
|
||||
}
|
||||
|
||||
void SparklineSceneItem::connectBuffer(internal::CircularBuffer* buffer) {
|
||||
if (!buffer) return;
|
||||
connect(
|
||||
buffer,
|
||||
&internal::CircularBuffer::valuesChanged,
|
||||
this,
|
||||
&SparklineSceneItem::invalidateGeometry);
|
||||
connect(buffer, &QObject::destroyed, this, [this, buffer]() {
|
||||
if (m_line1 == buffer) {
|
||||
m_line1 = nullptr;
|
||||
emit line1Changed();
|
||||
}
|
||||
if (m_line2 == buffer) {
|
||||
m_line2 = nullptr;
|
||||
emit line2Changed();
|
||||
}
|
||||
invalidateGeometry();
|
||||
});
|
||||
}
|
||||
|
||||
qreal SparklineSceneItem::stepX() const {
|
||||
if (m_historyLength < 2 || width() <= 0.0) return 0.0;
|
||||
if (m_sliding) return width() / static_cast<qreal>(m_historyLength - 1);
|
||||
|
||||
const int len = qMax(
|
||||
2,
|
||||
(m_line1 && m_line1->count() >= 2) ? m_line1->count()
|
||||
: (m_line2 ? m_line2->count() : 2));
|
||||
return width() / static_cast<qreal>(len - 1);
|
||||
}
|
||||
|
||||
SparklineSceneItem::LineSample SparklineSceneItem::buildSample(
|
||||
internal::CircularBuffer* buffer) const {
|
||||
LineSample sample;
|
||||
if (!buffer || buffer->count() < 2 || m_maxValue <= 0.0 || width() <= 0.0)
|
||||
return sample;
|
||||
if (m_historyLength < 2) return sample;
|
||||
|
||||
const int len = buffer->count();
|
||||
const qreal step = stepX();
|
||||
if (step <= 0.0) return sample;
|
||||
|
||||
qreal startX = m_sliding ? (width() - (len - 1) * step + step) : 0.0;
|
||||
|
||||
const qreal h = height();
|
||||
const qreal strokePad = qCeil(m_lineWidth / 2);
|
||||
const qreal curvePad = 3.0;
|
||||
const qreal plotTop = strokePad + curvePad;
|
||||
const qreal plotBottom = h - strokePad;
|
||||
const qreal plotH = qMax<qreal>(1.0, plotBottom - plotTop);
|
||||
|
||||
QVector<QPointF> points;
|
||||
points.reserve(len);
|
||||
|
||||
for (int i = 0; i < len; ++i) {
|
||||
// Already smoothed once, incrementally, by the buffer itself.
|
||||
const qreal value =
|
||||
qBound<qreal>(0.0, buffer->smoothedAt(i), m_maxValue);
|
||||
|
||||
const qreal x = startX + i * step;
|
||||
const qreal y = plotTop + (1.0 - (value / m_maxValue)) * plotH;
|
||||
points.append(QPointF(x, y));
|
||||
}
|
||||
|
||||
QPainterPath path;
|
||||
path.moveTo(points[0]);
|
||||
|
||||
if (m_curveSmoothing && points.size() > 2) {
|
||||
const qreal tension = 1.0;
|
||||
const qreal k = tension / 6.0;
|
||||
const int n = points.size();
|
||||
for (int i = 0; i < n - 1; ++i) {
|
||||
const QPointF& p0 = points[qMax(i - 1, 0)];
|
||||
const QPointF& p1 = points[i];
|
||||
const QPointF& p2 = points[i + 1];
|
||||
const QPointF& p3 = points[qMin(i + 2, n - 1)];
|
||||
|
||||
QPointF c1 = p1 + (p2 - p0) * k;
|
||||
QPointF c2 = p2 - (p3 - p1) * k;
|
||||
|
||||
const qreal segMinY = qMin(p1.y(), p2.y());
|
||||
const qreal segMaxY = qMax(p1.y(), p2.y());
|
||||
c1.setY(qBound(segMinY, c1.y(), segMaxY));
|
||||
c2.setY(qBound(segMinY, c2.y(), segMaxY));
|
||||
|
||||
path.cubicTo(c1, c2, p2);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < points.size() - 1; ++i) {
|
||||
const QPointF& p0 = points[i];
|
||||
const QPointF& p1 = points[i + 1];
|
||||
const qreal ctrlX = (p0.x() + p1.x()) * 0.5;
|
||||
path.cubicTo(QPointF(ctrlX, p0.y()), QPointF(ctrlX, p1.y()), p1);
|
||||
}
|
||||
}
|
||||
|
||||
// Flatten the curve to a polyline once, here, on data change only.
|
||||
// This is CPU math only -- no QPainter draw calls, no image, no
|
||||
// texture upload happen as a result of this.
|
||||
const auto subpaths = path.toSubpathPolygons();
|
||||
if (subpaths.isEmpty()) return sample;
|
||||
|
||||
sample.polyline = subpaths.first();
|
||||
sample.fillBottom = h;
|
||||
sample.hasData = sample.polyline.size() >= 2;
|
||||
return sample;
|
||||
}
|
||||
|
||||
void SparklineSceneItem::writeFillGeometry(
|
||||
QSGGeometryNode* node, const LineSample& sample) {
|
||||
const int n = sample.polyline.size();
|
||||
auto* geometry = node->geometry();
|
||||
geometry->allocate(n * 2);
|
||||
geometry->setDrawingMode(QSGGeometry::DrawTriangleStrip);
|
||||
|
||||
auto* vertices = geometry->vertexDataAsPoint2D();
|
||||
for (int i = 0; i < n; ++i) {
|
||||
const QPointF& p = sample.polyline[i];
|
||||
vertices[i * 2].set(p.x(), p.y());
|
||||
vertices[i * 2 + 1].set(p.x(), sample.fillBottom);
|
||||
}
|
||||
|
||||
node->markDirty(QSGNode::DirtyGeometry);
|
||||
}
|
||||
|
||||
void SparklineSceneItem::writeStrokeGeometry(
|
||||
QSGGeometryNode* node, const LineSample& sample, qreal lineWidth) {
|
||||
const int n = sample.polyline.size();
|
||||
auto* geometry = node->geometry();
|
||||
geometry->allocate(n * 2);
|
||||
geometry->setDrawingMode(QSGGeometry::DrawTriangleStrip);
|
||||
|
||||
auto* vertices = geometry->vertexDataAsPoint2D();
|
||||
const qreal half = lineWidth * 0.5;
|
||||
|
||||
for (int i = 0; i < n; ++i) {
|
||||
QPointF dir;
|
||||
if (i == 0)
|
||||
dir = sample.polyline[1] - sample.polyline[0];
|
||||
else if (i == n - 1)
|
||||
dir = sample.polyline[n - 1] - sample.polyline[n - 2];
|
||||
else
|
||||
dir = sample.polyline[i + 1] - sample.polyline[i - 1];
|
||||
|
||||
const qreal len = std::hypot(dir.x(), dir.y());
|
||||
QPointF normal = len > 1e-6 ? QPointF(-dir.y() / len, dir.x() / len)
|
||||
: QPointF(0, 1);
|
||||
|
||||
const QPointF& p = sample.polyline[i];
|
||||
const QPointF offset = normal * half;
|
||||
vertices[i * 2].set(p.x() + offset.x(), p.y() + offset.y());
|
||||
vertices[i * 2 + 1].set(p.x() - offset.x(), p.y() - offset.y());
|
||||
}
|
||||
|
||||
node->markDirty(QSGNode::DirtyGeometry);
|
||||
}
|
||||
|
||||
void SparklineSceneItem::setMaterialColor(
|
||||
QSGGeometryNode* node, const QColor& color, qreal alpha) {
|
||||
auto* material = static_cast<QSGFlatColorMaterial*>(node->material());
|
||||
QColor c = color;
|
||||
c.setAlphaF(static_cast<float>(alpha));
|
||||
material->setColor(c);
|
||||
node->markDirty(QSGNode::DirtyMaterial);
|
||||
}
|
||||
|
||||
QSGNode* SparklineSceneItem::updatePaintNode(
|
||||
QSGNode* node, UpdatePaintNodeData*) {
|
||||
bool samplesRebuilt = false;
|
||||
if (m_geometryDirty) {
|
||||
m_sample1 = buildSample(m_line1);
|
||||
m_sample2 = buildSample(m_line2);
|
||||
m_geometryDirty = false;
|
||||
samplesRebuilt = true;
|
||||
}
|
||||
|
||||
const bool hasAny = m_sample1.hasData || m_sample2.hasData;
|
||||
if (!hasAny) {
|
||||
delete node;
|
||||
m_transformNode = nullptr;
|
||||
m_line1FillNode = m_line1StrokeNode = nullptr;
|
||||
m_line2FillNode = m_line2StrokeNode = nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto* transformNode = static_cast<QSGTransformNode*>(node);
|
||||
bool justCreated = false;
|
||||
if (!transformNode) {
|
||||
transformNode = new QSGTransformNode();
|
||||
m_transformNode = transformNode;
|
||||
|
||||
auto makeNode = [] {
|
||||
auto* n = new QSGGeometryNode();
|
||||
n->setGeometry(
|
||||
new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 0));
|
||||
n->setFlag(QSGNode::OwnsGeometry);
|
||||
n->setMaterial(new QSGFlatColorMaterial());
|
||||
n->setFlag(QSGNode::OwnsMaterial);
|
||||
return n;
|
||||
};
|
||||
|
||||
m_line1FillNode = makeNode();
|
||||
m_line1StrokeNode = makeNode();
|
||||
m_line2FillNode = makeNode();
|
||||
m_line2StrokeNode = makeNode();
|
||||
transformNode->appendChildNode(m_line1FillNode);
|
||||
transformNode->appendChildNode(m_line1StrokeNode);
|
||||
transformNode->appendChildNode(m_line2FillNode);
|
||||
transformNode->appendChildNode(m_line2StrokeNode);
|
||||
|
||||
justCreated = true;
|
||||
}
|
||||
|
||||
QMatrix4x4 matrix;
|
||||
matrix.translate(-stepX() * m_slideProgress, 0);
|
||||
transformNode->setMatrix(matrix);
|
||||
|
||||
const bool needsGeometryWrite = samplesRebuilt || justCreated;
|
||||
const bool needsColorWrite = m_colorsDirty || justCreated;
|
||||
|
||||
auto writeLine = [&](QSGGeometryNode* fillNode,
|
||||
QSGGeometryNode* strokeNode,
|
||||
const LineSample& sample,
|
||||
const QColor& color,
|
||||
qreal fillAlpha) {
|
||||
if (needsGeometryWrite) {
|
||||
if (sample.hasData) {
|
||||
writeFillGeometry(fillNode, sample);
|
||||
writeStrokeGeometry(strokeNode, sample, m_lineWidth);
|
||||
} else {
|
||||
fillNode->geometry()->allocate(0);
|
||||
strokeNode->geometry()->allocate(0);
|
||||
fillNode->markDirty(QSGNode::DirtyGeometry);
|
||||
strokeNode->markDirty(QSGNode::DirtyGeometry);
|
||||
}
|
||||
}
|
||||
if (needsColorWrite) {
|
||||
setMaterialColor(fillNode, color, fillAlpha);
|
||||
setMaterialColor(strokeNode, color, 1.0);
|
||||
}
|
||||
};
|
||||
|
||||
writeLine(
|
||||
m_line1FillNode,
|
||||
m_line1StrokeNode,
|
||||
m_sample1,
|
||||
m_line1Color,
|
||||
m_line1FillAlpha);
|
||||
writeLine(
|
||||
m_line2FillNode,
|
||||
m_line2StrokeNode,
|
||||
m_sample2,
|
||||
m_line2Color,
|
||||
m_line2FillAlpha);
|
||||
|
||||
m_colorsDirty = false;
|
||||
|
||||
return transformNode;
|
||||
}
|
||||
|
||||
internal::CircularBuffer* SparklineSceneItem::line1() const {
|
||||
return m_line1;
|
||||
}
|
||||
|
||||
void SparklineSceneItem::setLine1(internal::CircularBuffer* buffer) {
|
||||
if (m_line1 == buffer) return;
|
||||
if (m_line1) disconnect(m_line1, nullptr, this, nullptr);
|
||||
m_line1 = buffer;
|
||||
connectBuffer(buffer);
|
||||
applySmoothingRadius(buffer);
|
||||
invalidateGeometry();
|
||||
emit line1Changed();
|
||||
}
|
||||
|
||||
internal::CircularBuffer* SparklineSceneItem::line2() const {
|
||||
return m_line2;
|
||||
}
|
||||
|
||||
void SparklineSceneItem::setLine2(internal::CircularBuffer* buffer) {
|
||||
if (m_line2 == buffer) return;
|
||||
if (m_line2) disconnect(m_line2, nullptr, this, nullptr);
|
||||
m_line2 = buffer;
|
||||
connectBuffer(buffer);
|
||||
applySmoothingRadius(buffer);
|
||||
invalidateGeometry();
|
||||
emit line2Changed();
|
||||
}
|
||||
|
||||
QColor SparklineSceneItem::line1Color() const {
|
||||
return m_line1Color;
|
||||
}
|
||||
|
||||
void SparklineSceneItem::setLine1Color(const QColor& color) {
|
||||
if (m_line1Color == color) return;
|
||||
m_line1Color = color;
|
||||
m_colorsDirty = true;
|
||||
update();
|
||||
emit line1ColorChanged();
|
||||
}
|
||||
|
||||
QColor SparklineSceneItem::line2Color() const {
|
||||
return m_line2Color;
|
||||
}
|
||||
|
||||
void SparklineSceneItem::setLine2Color(const QColor& color) {
|
||||
if (m_line2Color == color) return;
|
||||
m_line2Color = color;
|
||||
m_colorsDirty = true;
|
||||
update();
|
||||
emit line2ColorChanged();
|
||||
}
|
||||
|
||||
qreal SparklineSceneItem::line1FillAlpha() const {
|
||||
return m_line1FillAlpha;
|
||||
}
|
||||
|
||||
void SparklineSceneItem::setLine1FillAlpha(qreal alpha) {
|
||||
if (qFuzzyCompare(m_line1FillAlpha, alpha)) return;
|
||||
m_line1FillAlpha = alpha;
|
||||
m_colorsDirty = true;
|
||||
update();
|
||||
emit line1FillAlphaChanged();
|
||||
}
|
||||
|
||||
qreal SparklineSceneItem::line2FillAlpha() const {
|
||||
return m_line2FillAlpha;
|
||||
}
|
||||
|
||||
void SparklineSceneItem::setLine2FillAlpha(qreal alpha) {
|
||||
if (qFuzzyCompare(m_line2FillAlpha, alpha)) return;
|
||||
m_line2FillAlpha = alpha;
|
||||
m_colorsDirty = true;
|
||||
update();
|
||||
emit line2FillAlphaChanged();
|
||||
}
|
||||
|
||||
qreal SparklineSceneItem::maxValue() const {
|
||||
return m_maxValue;
|
||||
}
|
||||
|
||||
void SparklineSceneItem::setMaxValue(qreal value) {
|
||||
if (qFuzzyCompare(m_maxValue, value)) return;
|
||||
m_maxValue = value;
|
||||
invalidateGeometry();
|
||||
emit maxValueChanged();
|
||||
}
|
||||
|
||||
qreal SparklineSceneItem::slideProgress() const {
|
||||
return m_slideProgress;
|
||||
}
|
||||
|
||||
void SparklineSceneItem::setSlideProgress(qreal progress) {
|
||||
if (qFuzzyCompare(m_slideProgress, progress)) return;
|
||||
m_slideProgress = progress;
|
||||
update();
|
||||
emit slideProgressChanged();
|
||||
}
|
||||
|
||||
int SparklineSceneItem::historyLength() const {
|
||||
return m_historyLength;
|
||||
}
|
||||
|
||||
void SparklineSceneItem::setHistoryLength(int length) {
|
||||
if (m_historyLength == length) return;
|
||||
m_historyLength = length;
|
||||
invalidateGeometry();
|
||||
emit historyLengthChanged();
|
||||
}
|
||||
|
||||
qreal SparklineSceneItem::lineWidth() const {
|
||||
return m_lineWidth;
|
||||
}
|
||||
|
||||
void SparklineSceneItem::setLineWidth(qreal width) {
|
||||
if (qFuzzyCompare(m_lineWidth, width)) return;
|
||||
m_lineWidth = width;
|
||||
invalidateGeometry();
|
||||
emit lineWidthChanged();
|
||||
}
|
||||
|
||||
bool SparklineSceneItem::sliding() const {
|
||||
return m_sliding;
|
||||
}
|
||||
|
||||
void SparklineSceneItem::setSliding(bool sliding) {
|
||||
if (m_sliding == sliding) return;
|
||||
m_sliding = sliding;
|
||||
invalidateGeometry();
|
||||
emit slidingChanged();
|
||||
}
|
||||
|
||||
qreal SparklineSceneItem::smoothing() const {
|
||||
return m_smoothing;
|
||||
}
|
||||
|
||||
void SparklineSceneItem::setSmoothing(qreal smoothing) {
|
||||
smoothing = qMax<qreal>(0.0, smoothing);
|
||||
if (qFuzzyCompare(m_smoothing, smoothing)) return;
|
||||
m_smoothing = smoothing;
|
||||
applySmoothingRadius(m_line1);
|
||||
applySmoothingRadius(m_line2);
|
||||
emit smoothingChanged();
|
||||
}
|
||||
|
||||
bool SparklineSceneItem::curveSmoothing() const {
|
||||
return m_curveSmoothing;
|
||||
}
|
||||
|
||||
void SparklineSceneItem::setCurveSmoothing(bool enabled) {
|
||||
if (m_curveSmoothing == enabled) return;
|
||||
m_curveSmoothing = enabled;
|
||||
invalidateGeometry();
|
||||
emit curveSmoothingChanged();
|
||||
}
|
||||
|
||||
} // namespace ZShell::components
|
||||
@@ -0,0 +1,167 @@
|
||||
#pragma once
|
||||
|
||||
#include <qcolor.h>
|
||||
#include <qmatrix4x4.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qquickitem.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qvector.h>
|
||||
|
||||
#include "internal/circularbuffer.hpp"
|
||||
|
||||
class QSGGeometryNode;
|
||||
class QSGTransformNode;
|
||||
|
||||
namespace ZShell::components {
|
||||
|
||||
class SparklineSceneItem : public QQuickItem {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
Q_PROPERTY(
|
||||
internal::CircularBuffer* line1 READ line1 WRITE setLine1 NOTIFY
|
||||
line1Changed)
|
||||
Q_PROPERTY(
|
||||
internal::CircularBuffer* line2 READ line2 WRITE setLine2 NOTIFY
|
||||
line2Changed)
|
||||
Q_PROPERTY(
|
||||
QColor line1Color READ line1Color WRITE setLine1Color NOTIFY
|
||||
line1ColorChanged)
|
||||
Q_PROPERTY(
|
||||
QColor line2Color READ line2Color WRITE setLine2Color NOTIFY
|
||||
line2ColorChanged)
|
||||
Q_PROPERTY(
|
||||
qreal line1FillAlpha READ line1FillAlpha WRITE setLine1FillAlpha NOTIFY
|
||||
line1FillAlphaChanged)
|
||||
Q_PROPERTY(
|
||||
qreal line2FillAlpha READ line2FillAlpha WRITE setLine2FillAlpha NOTIFY
|
||||
line2FillAlphaChanged)
|
||||
Q_PROPERTY(
|
||||
qreal maxValue READ maxValue WRITE setMaxValue NOTIFY maxValueChanged)
|
||||
Q_PROPERTY(
|
||||
qreal slideProgress READ slideProgress WRITE setSlideProgress NOTIFY
|
||||
slideProgressChanged)
|
||||
Q_PROPERTY(
|
||||
int historyLength READ historyLength WRITE setHistoryLength NOTIFY
|
||||
historyLengthChanged)
|
||||
Q_PROPERTY(
|
||||
qreal lineWidth READ lineWidth WRITE setLineWidth NOTIFY
|
||||
lineWidthChanged)
|
||||
Q_PROPERTY(bool sliding READ sliding WRITE setSliding NOTIFY slidingChanged)
|
||||
Q_PROPERTY(
|
||||
qreal smoothing READ smoothing WRITE setSmoothing NOTIFY
|
||||
smoothingChanged)
|
||||
Q_PROPERTY(
|
||||
bool curveSmoothing READ curveSmoothing WRITE setCurveSmoothing NOTIFY
|
||||
curveSmoothingChanged)
|
||||
|
||||
public:
|
||||
explicit SparklineSceneItem(QQuickItem* parent = nullptr);
|
||||
|
||||
[[nodiscard]] internal::CircularBuffer* line1() const;
|
||||
void setLine1(internal::CircularBuffer* buffer);
|
||||
|
||||
[[nodiscard]] internal::CircularBuffer* line2() const;
|
||||
void setLine2(internal::CircularBuffer* buffer);
|
||||
|
||||
[[nodiscard]] QColor line1Color() const;
|
||||
void setLine1Color(const QColor& color);
|
||||
|
||||
[[nodiscard]] QColor line2Color() const;
|
||||
void setLine2Color(const QColor& color);
|
||||
|
||||
[[nodiscard]] qreal line1FillAlpha() const;
|
||||
void setLine1FillAlpha(qreal alpha);
|
||||
|
||||
[[nodiscard]] qreal line2FillAlpha() const;
|
||||
void setLine2FillAlpha(qreal alpha);
|
||||
|
||||
[[nodiscard]] qreal maxValue() const;
|
||||
void setMaxValue(qreal value);
|
||||
|
||||
[[nodiscard]] qreal slideProgress() const;
|
||||
void setSlideProgress(qreal progress);
|
||||
|
||||
[[nodiscard]] int historyLength() const;
|
||||
void setHistoryLength(int length);
|
||||
|
||||
[[nodiscard]] qreal lineWidth() const;
|
||||
void setLineWidth(qreal width);
|
||||
|
||||
[[nodiscard]] bool sliding() const;
|
||||
void setSliding(bool sliding);
|
||||
|
||||
[[nodiscard]] qreal smoothing() const;
|
||||
void setSmoothing(qreal smoothing);
|
||||
|
||||
[[nodiscard]] bool curveSmoothing() const;
|
||||
void setCurveSmoothing(bool curveSmoothing);
|
||||
|
||||
signals:
|
||||
void line1Changed();
|
||||
void line2Changed();
|
||||
void line1ColorChanged();
|
||||
void line2ColorChanged();
|
||||
void line1FillAlphaChanged();
|
||||
void line2FillAlphaChanged();
|
||||
void maxValueChanged();
|
||||
void slideProgressChanged();
|
||||
void historyLengthChanged();
|
||||
void lineWidthChanged();
|
||||
void slidingChanged();
|
||||
void smoothingChanged();
|
||||
void curveSmoothingChanged();
|
||||
|
||||
protected:
|
||||
QSGNode* updatePaintNode(QSGNode* node, UpdatePaintNodeData* data) override;
|
||||
void geometryChange(
|
||||
const QRectF& newGeometry, const QRectF& oldGeometry) override;
|
||||
|
||||
private:
|
||||
struct LineSample {
|
||||
QVector<QPointF> polyline; // local coords, slideProgress baked to 0
|
||||
qreal fillBottom = 0.0;
|
||||
bool hasData = false;
|
||||
};
|
||||
|
||||
void connectBuffer(internal::CircularBuffer* buffer);
|
||||
void invalidateGeometry();
|
||||
void applySmoothingRadius(internal::CircularBuffer* buffer) const;
|
||||
[[nodiscard]] qreal stepX() const;
|
||||
[[nodiscard]] LineSample buildSample(internal::CircularBuffer* buffer) const;
|
||||
static void writeFillGeometry(
|
||||
QSGGeometryNode* node, const LineSample& sample);
|
||||
static void writeStrokeGeometry(
|
||||
QSGGeometryNode* node, const LineSample& sample, qreal lineWidth);
|
||||
static void setMaterialColor(
|
||||
QSGGeometryNode* node, const QColor& color, qreal alpha);
|
||||
|
||||
internal::CircularBuffer* m_line1 = nullptr;
|
||||
internal::CircularBuffer* m_line2 = nullptr;
|
||||
QColor m_line1Color;
|
||||
QColor m_line2Color;
|
||||
qreal m_line1FillAlpha = 0.15;
|
||||
qreal m_line2FillAlpha = 0.2;
|
||||
qreal m_maxValue = 1024.0;
|
||||
qreal m_slideProgress = 0.0;
|
||||
int m_historyLength = 30;
|
||||
qreal m_lineWidth = 2.0;
|
||||
bool m_sliding = true;
|
||||
qreal m_smoothing = 0.0;
|
||||
bool m_curveSmoothing = false;
|
||||
|
||||
LineSample m_sample1;
|
||||
LineSample m_sample2;
|
||||
|
||||
bool m_geometryDirty = true;
|
||||
bool m_colorsDirty = true;
|
||||
|
||||
QSGTransformNode* m_transformNode = nullptr;
|
||||
QSGGeometryNode* m_line1FillNode = nullptr;
|
||||
QSGGeometryNode* m_line1StrokeNode = nullptr;
|
||||
QSGGeometryNode* m_line2FillNode = nullptr;
|
||||
QSGGeometryNode* m_line2StrokeNode = nullptr;
|
||||
};
|
||||
|
||||
} // namespace ZShell::components
|
||||
Reference in New Issue
Block a user