From f5d8010230d7c3fa9a711a4070c1a18dada95dfb Mon Sep 17 00:00:00 2001 From: 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> Date: Thu, 23 Apr 2026 18:51:44 +1000 Subject: [PATCH] feat: add wavy line component --- .../src/Caelestia/Components/CMakeLists.txt | 1 + plugin/src/Caelestia/Components/wavyline.cpp | 255 ++++++++++++++++++ plugin/src/Caelestia/Components/wavyline.hpp | 107 ++++++++ 3 files changed, 363 insertions(+) create mode 100644 plugin/src/Caelestia/Components/wavyline.cpp create mode 100644 plugin/src/Caelestia/Components/wavyline.hpp diff --git a/plugin/src/Caelestia/Components/CMakeLists.txt b/plugin/src/Caelestia/Components/CMakeLists.txt index f880d318..2928f27d 100644 --- a/plugin/src/Caelestia/Components/CMakeLists.txt +++ b/plugin/src/Caelestia/Components/CMakeLists.txt @@ -2,6 +2,7 @@ qml_module(caelestia-components URI Caelestia.Components SOURCES lazylistview.hpp lazylistview.cpp + wavyline.hpp wavyline.cpp LIBRARIES Qt::Quick ) diff --git a/plugin/src/Caelestia/Components/wavyline.cpp b/plugin/src/Caelestia/Components/wavyline.cpp new file mode 100644 index 00000000..7114e573 --- /dev/null +++ b/plugin/src/Caelestia/Components/wavyline.cpp @@ -0,0 +1,255 @@ +#include "wavyline.hpp" + +#include +#include + +namespace caelestia::controls { + +WavyLine::WavyLine(QQuickItem* parent) + : QQuickPaintedItem(parent) + , m_lineWidth(4) + , m_amplitudeMultiplier(0.5) + , m_frequency(6) + , m_startX(0) + , m_fullLength(0) + , m_color(Qt::white) + , m_waveProgress(0) + , m_pathType(Linear) + , m_startAngle(0) + , m_fullAngle(360) + , m_radius(-1) + , m_value(1) + , m_startAngleRad(-M_PI / 2.0) + , m_fullAngleRad(2 * M_PI) { + setAntialiasing(true); +} + +int WavyLine::lineWidth() const { + return m_lineWidth; +} + +void WavyLine::setLineWidth(int lineWidth) { + if (m_lineWidth != lineWidth) { + m_lineWidth = lineWidth; + emit lineWidthChanged(); + update(); + } +} + +qreal WavyLine::amplitudeMultiplier() const { + return m_amplitudeMultiplier; +} + +void WavyLine::setAmplitudeMultiplier(qreal amplitudeMultiplier) { + if (!qFuzzyCompare(m_amplitudeMultiplier + 1.0, amplitudeMultiplier + 1.0)) { + m_amplitudeMultiplier = amplitudeMultiplier; + emit amplitudeMultiplierChanged(); + update(); + } +} + +int WavyLine::frequency() const { + return m_frequency; +} + +void WavyLine::setFrequency(int frequency) { + if (m_frequency != frequency) { + m_frequency = frequency; + emit frequencyChanged(); + update(); + } +} + +qreal WavyLine::startX() const { + return m_startX; +} + +void WavyLine::setStartX(qreal startX) { + if (!qFuzzyCompare(m_startX + 1.0, startX + 1.0)) { + m_startX = startX; + emit startXChanged(); + update(); + } +} + +qreal WavyLine::fullLength() const { + return m_fullLength; +} + +void WavyLine::setFullLength(qreal fullLength) { + if (!qFuzzyCompare(m_fullLength + 1.0, fullLength + 1.0)) { + m_fullLength = fullLength; + emit fullLengthChanged(); + update(); + } +} + +QColor WavyLine::color() const { + return m_color; +} + +void WavyLine::setColor(const QColor& color) { + if (m_color != color) { + m_color = color; + emit colorChanged(); + update(); + } +} + +qreal WavyLine::waveProgress() const { + return m_waveProgress; +} + +void WavyLine::setWaveProgress(qreal progress) { + if (!qFuzzyCompare(m_waveProgress + 1.0, progress + 1.0)) { + m_waveProgress = progress; + emit waveProgressChanged(); + update(); + } +} + +WavyLine::PathType WavyLine::pathType() const { + return m_pathType; +} + +void WavyLine::setPathType(PathType pathType) { + if (m_pathType != pathType) { + m_pathType = pathType; + emit pathTypeChanged(); + update(); + } +} + +qreal WavyLine::startAngle() const { + return m_startAngle; +} + +void WavyLine::setStartAngle(qreal startAngle) { + if (!qFuzzyCompare(m_startAngle + 1.0, startAngle + 1.0)) { + m_startAngle = startAngle; + m_startAngleRad = startAngle * M_PI / 180.0 - M_PI / 2.0; + emit startAngleChanged(); + update(); + } +} + +qreal WavyLine::fullAngle() const { + return m_fullAngle; +} + +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(); + } +} + +qreal WavyLine::radius() const { + return m_radius; +} + +void WavyLine::setRadius(qreal radius) { + if (!qFuzzyCompare(m_radius + 1.0, radius + 1.0)) { + m_radius = radius; + emit radiusChanged(); + update(); + } +} + +qreal WavyLine::value() const { + return m_value; +} + +void WavyLine::setValue(qreal value) { + if (!qFuzzyCompare(m_value + 1.0, value + 1.0)) { + m_value = value; + emit valueChanged(); + update(); + } +} + +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 caelestia::controls diff --git a/plugin/src/Caelestia/Components/wavyline.hpp b/plugin/src/Caelestia/Components/wavyline.hpp new file mode 100644 index 00000000..d0f865b6 --- /dev/null +++ b/plugin/src/Caelestia/Components/wavyline.hpp @@ -0,0 +1,107 @@ +#pragma once + +#include +#include + +namespace caelestia::controls { + +class WavyLine : public QQuickPaintedItem { + Q_OBJECT + QML_ELEMENT + + Q_PROPERTY(int lineWidth READ lineWidth WRITE setLineWidth NOTIFY lineWidthChanged FINAL) + Q_PROPERTY(qreal amplitudeMultiplier READ amplitudeMultiplier WRITE setAmplitudeMultiplier NOTIFY + amplitudeMultiplierChanged FINAL) + Q_PROPERTY(int frequency READ frequency WRITE setFrequency NOTIFY frequencyChanged FINAL) + Q_PROPERTY(qreal startX READ startX WRITE setStartX NOTIFY startXChanged FINAL) + Q_PROPERTY(qreal fullLength READ fullLength WRITE setFullLength NOTIFY fullLengthChanged FINAL) + Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged FINAL) + Q_PROPERTY(qreal waveProgress READ waveProgress WRITE setWaveProgress NOTIFY waveProgressChanged FINAL) + Q_PROPERTY(PathType pathType READ pathType WRITE setPathType NOTIFY pathTypeChanged FINAL) + Q_PROPERTY(qreal startAngle READ startAngle WRITE setStartAngle NOTIFY startAngleChanged FINAL) + Q_PROPERTY(qreal fullAngle READ fullAngle WRITE setFullAngle NOTIFY fullAngleChanged FINAL) + Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged FINAL) + Q_PROPERTY(qreal value READ value WRITE setValue NOTIFY valueChanged FINAL) + +public: + enum PathType { + Linear, + Arc + }; + Q_ENUM(PathType) + + explicit WavyLine(QQuickItem* parent = nullptr); + + [[nodiscard]] int lineWidth() const; + void setLineWidth(int lineWidth); + + [[nodiscard]] qreal amplitudeMultiplier() const; + void setAmplitudeMultiplier(qreal amplitudeMultiplier); + + [[nodiscard]] int frequency() const; + void setFrequency(int frequency); + + [[nodiscard]] qreal startX() const; + void setStartX(qreal startX); + + [[nodiscard]] qreal fullLength() const; + void setFullLength(qreal fullLength); + + [[nodiscard]] QColor color() const; + void setColor(const QColor& color); + + [[nodiscard]] qreal waveProgress() const; + void setWaveProgress(qreal progress); + + [[nodiscard]] PathType pathType() const; + void setPathType(PathType pathType); + + [[nodiscard]] qreal startAngle() const; + void setStartAngle(qreal startAngle); + + [[nodiscard]] qreal fullAngle() const; + void setFullAngle(qreal fullAngle); + + [[nodiscard]] qreal radius() const; + void setRadius(qreal radius); + + [[nodiscard]] qreal value() const; + void setValue(qreal value); + + void paint(QPainter* painter) override; + +signals: + void lineWidthChanged(); + void amplitudeMultiplierChanged(); + void frequencyChanged(); + void startXChanged(); + void fullLengthChanged(); + void colorChanged(); + void waveProgressChanged(); + void pathTypeChanged(); + void startAngleChanged(); + void fullAngleChanged(); + void radiusChanged(); + void valueChanged(); + +private: + void paintLinear(QPainter* painter); + void paintArc(QPainter* painter); + + int m_lineWidth; + qreal m_amplitudeMultiplier; + int m_frequency; + qreal m_startX; + qreal m_fullLength; + QColor m_color; + qreal m_waveProgress; + PathType m_pathType; + qreal m_startAngle; + qreal m_fullAngle; + qreal m_radius; + qreal m_value; + qreal m_startAngleRad; + qreal m_fullAngleRad; +}; + +} // namespace caelestia::controls