From 3552c79aec8d797e1219cf1979a75eee12897c2f Mon Sep 17 00:00:00 2001 From: "Vogel, Rick" Date: Mon, 10 Jul 2023 15:54:53 +0200 Subject: [PATCH] initial commit --- .../LevelingSensor/QsgNodeUtility.cpp | 1 + src/controls/LevelingSensor/QsgNodeUtility.h | 100 +++++ .../LevelingSensor/QskLevelingSensor.cpp | 142 ++++++ .../LevelingSensor/QskLevelingSensor.h | 60 +++ .../LevelingSensor/QskLevelingSensorNodes.cpp | 0 .../LevelingSensor/QskLevelingSensorNodes.h | 297 +++++++++++++ .../QskLevelingSensorSkinlet.cpp | 415 ++++++++++++++++++ .../LevelingSensor/QskLevelingSensorSkinlet.h | 56 +++ .../QskLevelingSensorUtility.cpp | 1 + .../LevelingSensor/QskLevelingSensorUtility.h | 94 ++++ .../QskLevelingSensorUtility_test.cpp | 190 ++++++++ 11 files changed, 1356 insertions(+) create mode 100644 src/controls/LevelingSensor/QsgNodeUtility.cpp create mode 100644 src/controls/LevelingSensor/QsgNodeUtility.h create mode 100644 src/controls/LevelingSensor/QskLevelingSensor.cpp create mode 100644 src/controls/LevelingSensor/QskLevelingSensor.h create mode 100644 src/controls/LevelingSensor/QskLevelingSensorNodes.cpp create mode 100644 src/controls/LevelingSensor/QskLevelingSensorNodes.h create mode 100644 src/controls/LevelingSensor/QskLevelingSensorSkinlet.cpp create mode 100644 src/controls/LevelingSensor/QskLevelingSensorSkinlet.h create mode 100644 src/controls/LevelingSensor/QskLevelingSensorUtility.cpp create mode 100644 src/controls/LevelingSensor/QskLevelingSensorUtility.h create mode 100644 src/controls/LevelingSensor/QskLevelingSensorUtility_test.cpp diff --git a/src/controls/LevelingSensor/QsgNodeUtility.cpp b/src/controls/LevelingSensor/QsgNodeUtility.cpp new file mode 100644 index 00000000..df2a333d --- /dev/null +++ b/src/controls/LevelingSensor/QsgNodeUtility.cpp @@ -0,0 +1 @@ +#include "QsgNodeUtility.h" \ No newline at end of file diff --git a/src/controls/LevelingSensor/QsgNodeUtility.h b/src/controls/LevelingSensor/QsgNodeUtility.h new file mode 100644 index 00000000..11e3ac84 --- /dev/null +++ b/src/controls/LevelingSensor/QsgNodeUtility.h @@ -0,0 +1,100 @@ +#pragma once + +#include +#include + +namespace qsg +{ + using NodeType = QSGNode; + + template + struct seq; + + template<> + struct seq<> + { + static void append(NodeType* root) + { + } + }; + + template + struct seq + { + static void append(NodeType* root) + { + T::append(root); + } + }; + + template + struct seq + { + static void append(NodeType* root) + { + T::append(root); + seq::append(root); + } + }; + + template + struct par; + + template + struct par + { + static void append(NodeType* root) + { + const auto n = N; + for (int i = 0; i < N; ++i) + { + root->appendChildNode(new T); + } + } + }; + + template + struct par + { + static void append(NodeType* root) + { + const auto n = N; + for (int i = 0; i < N; ++i) + { + auto* const t = new T; + U::append(t); + root->appendChildNode(t); + } + } + }; + + template + struct ensure; + + template + struct ensure + { + static Q_REQUIRED_RESULT Root* node(NodeType* root = nullptr) + { + if(root == nullptr) + { + root = new Root; + } + return static_cast(root); + } + }; + + template + struct ensure + { + static Q_REQUIRED_RESULT Root* node(NodeType* root = nullptr) + { + if(root == nullptr) + { + root = new Root; + Append::append(root); + } + return static_cast(root); + } + }; +} \ No newline at end of file diff --git a/src/controls/LevelingSensor/QskLevelingSensor.cpp b/src/controls/LevelingSensor/QskLevelingSensor.cpp new file mode 100644 index 00000000..a42de6fc --- /dev/null +++ b/src/controls/LevelingSensor/QskLevelingSensor.cpp @@ -0,0 +1,142 @@ +#include "QskLevelingSensor.h" +#include + +namespace +{ + template + bool compareExchange(T& dst, const T& src) + { + if (dst != src) + { + dst = src; + return true; + } + return false; + } + + template<> + bool compareExchange(float& dst, const float& src) + { + if (!qFuzzyCompare(dst, src)) + { + dst = src; + return true; + } + return false; + } + + inline bool isAxis(const Qt::Axis axis) { + return axis == Qt::XAxis || axis == Qt::YAxis || axis == Qt::ZAxis; + } +} + +QSK_SUBCONTROL(LevelingSensor, OuterDisk) +QSK_SUBCONTROL(LevelingSensor, Horizon) +QSK_SUBCONTROL(LevelingSensor, TickmarksX) +QSK_SUBCONTROL(LevelingSensor, TickmarksXLabels) +QSK_SUBCONTROL(LevelingSensor, TickmarksY) +QSK_SUBCONTROL(LevelingSensor, TickmarksYLabels) +QSK_SUBCONTROL(LevelingSensor, TickmarksZ) +QSK_SUBCONTROL(LevelingSensor, TickmarksZLabels) + +#define RETURN_IF_FALSE(expr) if(!(expr)) return; + +LevelingSensor::LevelingSensor(QQuickItem* const parent) + : Inherited(parent) +{ +} + +void LevelingSensor::setRotation(const QVector3D& degree) +{ + if (m_rotation != degree) + { + setRotation(Qt::XAxis, degree.x()); + setRotation(Qt::YAxis, degree.y()); + setRotation(Qt::ZAxis, degree.z()); + } +} + +void LevelingSensor::setRotation(const Qt::Axis axis, const float degree) +{ + RETURN_IF_FALSE(isAxis(axis)); + + if (compareExchange(m_rotation[axis], degree)) + { + update(); + switch(axis) + { + case Qt::XAxis: Q_EMIT rotationXChanged(m_rotation[axis]); break; + case Qt::YAxis: Q_EMIT rotationYChanged(m_rotation[axis]); break; + case Qt::ZAxis: Q_EMIT rotationZChanged(m_rotation[axis]); break; + } + Q_EMIT rotationChanged(m_rotation); + } +} + +void LevelingSensor::setTickmarks(const Qt::Axis axis, QskScaleTickmarks tickmarks) +{ + RETURN_IF_FALSE(isAxis(axis)); + + m_tickmarks[axis] = std::move(tickmarks); + update(); +} + +void LevelingSensor::setTickmarksLabels(const Qt::Axis axis, TickmarksLabels labels) +{ + RETURN_IF_FALSE(isAxis(axis)); + + m_tickmarksLabels[axis] = std::move(labels); + update(); +} + +void LevelingSensor::setAngle(const QVector3D& degree) +{ + if (compareExchange(m_angle, degree)) + { + update(); + Q_EMIT anglesChanged(m_angle); + } +} + +void LevelingSensor::setAngle(const Qt::Axis axis, const float degree) +{ + RETURN_IF_FALSE(isAxis(axis)); + + if (compareExchange(m_angle[axis], degree)) + { + update(); + Q_EMIT anglesChanged(m_angle); + } +} + +const QskScaleTickmarks& LevelingSensor::tickmarks(Qt::Axis axis) const +{ + if (isAxis(axis)) + { + return m_tickmarks[axis]; + } + static const QskScaleTickmarks invalid; + return invalid; +} + +const LevelingSensor::TickmarksLabels& LevelingSensor::tickmarkLabels(Qt::Axis axis) const +{ + if (isAxis(axis)) + { + return m_tickmarksLabels[axis]; + } + static const LevelingSensor::TickmarksLabels invalid; + return invalid; +} + +const QVector3D& LevelingSensor::angle() const noexcept +{ + return m_angle; +} + +const QVector3D& LevelingSensor::rotation() const noexcept +{ + return m_rotation; +} + +#include "moc_QskLevelingSensor.cpp" \ No newline at end of file diff --git a/src/controls/LevelingSensor/QskLevelingSensor.h b/src/controls/LevelingSensor/QskLevelingSensor.h new file mode 100644 index 00000000..63b02ddd --- /dev/null +++ b/src/controls/LevelingSensor/QskLevelingSensor.h @@ -0,0 +1,60 @@ +#pragma once + +#include +#include +#include +#include + +/// @brief This control can display the pitch, roll and yaw angles +/// @note x = pitch, y = yaw, z = roll +/// +/// ^y+ +/// | +/// | +/// | +/// +------------->x+ +/// / +/// / +/// v z+ +class LevelingSensor : public QskControl +{ + Q_OBJECT + using Inherited = QskControl; +public: + QSK_SUBCONTROLS( + OuterDisk, + Horizon, + TickmarksX, + TickmarksXLabels, + TickmarksY, + TickmarksYLabels, + TickmarksZ, + TickmarksZLabels) + using Tickmarks = QskScaleTickmarks; + using TickmarksLabels = QVector>; + explicit LevelingSensor(QQuickItem* parent = nullptr); +public Q_SLOTS: + void setRotation(const QVector3D& degree); + void setRotation(Qt::Axis axis, float degree); + void setTickmarks(Qt::Axis axis, Tickmarks tickmarks); + void setTickmarksLabels(Qt::Axis axis, TickmarksLabels labels); + void setAngle(const QVector3D& degree); + void setAngle(Qt::Axis axis, float degree); +signals: + void rotationXChanged(qreal degree); + void rotationYChanged(qreal degree); + void rotationZChanged(qreal degree); + void rotationChanged(const QVector3D& degree); + void anglesChanged(const QVector3D& degree); +public: + Q_REQUIRED_RESULT const QVector3D& rotation() const noexcept; + Q_REQUIRED_RESULT const Tickmarks& tickmarks(Qt::Axis axis) const; + Q_REQUIRED_RESULT const TickmarksLabels& tickmarkLabels(Qt::Axis axis) const; + Q_REQUIRED_RESULT const QVector3D& angle() const noexcept; +private: + /// @brief The sensors rotation per axis: x := roll, y := pitch, z := yaw + QVector3D m_rotation; + QVector3D m_angle = { 45,45,45 }; + Tickmarks m_tickmarks[3]; + TickmarksLabels m_tickmarksLabels[3]; +}; \ No newline at end of file diff --git a/src/controls/LevelingSensor/QskLevelingSensorNodes.cpp b/src/controls/LevelingSensor/QskLevelingSensorNodes.cpp new file mode 100644 index 00000000..e69de29b diff --git a/src/controls/LevelingSensor/QskLevelingSensorNodes.h b/src/controls/LevelingSensor/QskLevelingSensorNodes.h new file mode 100644 index 00000000..1202136e --- /dev/null +++ b/src/controls/LevelingSensor/QskLevelingSensorNodes.h @@ -0,0 +1,297 @@ +#pragma once + +#include +#include +#include + +#include + +class RadialTickmarksNode final : public QSGGeometryNode +{ +public: + RadialTickmarksNode() : m_geometry(QSGGeometry::defaultAttributes_Point2D(), 0) + { + m_geometry.setDrawingMode(QSGGeometry::DrawLines); + m_geometry.setVertexDataPattern(QSGGeometry::StaticPattern); + + setGeometry(&m_geometry); + setMaterial(&m_material); + } + + void setMaterialProperties(const QColor& color) + { + if (m_material.color() != color) + { + m_material.setColor(color); + markDirty(QSGNode::DirtyMaterial); + } + } + + void setGeometryProperties(const QskScaleTickmarks& tickmarks, const qreal r1 = 0.0, const QVector3D& r2 = { 0.5, 0.75, 1.0 }, float lineWidth = 1.0) + { + auto dirty = false; + + if (dirty |= (m_geometry.lineWidth() != lineWidth)) + { + m_geometry.setLineWidth(lineWidth); + } + + dirty |= compare_exchange(m_r1, r1); + dirty |= compare_exchange(m_r2, r2); + dirty |= compare_exchange(m_tickmarksHash, tickmarks.hash()); + + if (dirty) + { + update(tickmarks); + } + } + +private: + void update(const QskScaleTickmarks& tickmarks) + { + if (m_geometry.vertexCount() != tickmarks.tickCount()) + { + m_geometry.allocate(tickmarks.tickCount() * 2); + } + + auto* vertexData = m_geometry.vertexDataAsPoint2D(); + + using T = QskScaleTickmarks::TickType; + for (auto type : { T::MinorTick, T::MediumTick, T::MajorTick }) + { + for (const auto tick : tickmarks.ticks(type)) + { + const auto i = static_cast(type); + const auto angleRad = qDegreesToRadians(tick); + const auto x1 = qFastCos(angleRad) * m_r1; + const auto y1 = qFastSin(angleRad) * m_r1; + const auto x2 = qFastCos(angleRad) * m_r2[i]; + const auto y2 = qFastSin(angleRad) * m_r2[i]; + + vertexData[0].set(x1, y1); + vertexData[1].set(x2, y2); + vertexData += 2; + } + } + + m_geometry.markVertexDataDirty(); + markDirty(QSGNode::DirtyGeometry); + } + + QSGGeometry m_geometry; + QSGFlatColorMaterial m_material; + qreal m_r1 = 0.0; + QVector3D m_r2 = { 0.5, 0.75, 1.0 }; + QskHashValue m_tickmarksHash{ 0 }; +}; + +class LinearTickmarksNode final : public QSGGeometryNode +{ +public: + LinearTickmarksNode() : m_geometry(QSGGeometry::defaultAttributes_Point2D(), 0) + { + m_geometry.setDrawingMode(QSGGeometry::DrawLines); + m_geometry.setVertexDataPattern(QSGGeometry::StaticPattern); + + setGeometry(&m_geometry); + setMaterial(&m_material); + } + + void setMaterialProperties(const QColor& color) + { + auto dirty = false; + + if (dirty |= (m_material.color() != color)) + { + m_material.setColor(color); + } + + if (dirty) + { + markDirty(QSGNode::DirtyMaterial); + } + } + + void setGeometryProperties(const QskScaleTickmarks& tickmarks, const QVector3D& tickmarkSize, const QVector2D& scale = { 1.0,0.0f }, const QVector2D& offset = {}, const float lineWidth = 1.0f, const bool forceDirty = false) + { + auto dirty = forceDirty; + + if (dirty |= !qFuzzyCompare(m_geometry.lineWidth(), lineWidth)) + { + m_geometry.setLineWidth(lineWidth); + } + + dirty |= m_geometry.vertexCount() != tickmarks.tickCount() * 2; + dirty |= compare_exchange(m_tickmarkSize, tickmarkSize); + dirty |= compare_exchange(m_scale, scale); + dirty |= compare_exchange(m_offset, offset); + + if (dirty) + { + update(tickmarks); + markDirty(QSGNode::DirtyGeometry); + } + } + +private: + void update(const QskScaleTickmarks& tickmarks) + { + if (m_geometry.vertexCount() != tickmarks.tickCount() * 2) + { + m_geometry.allocate(tickmarks.tickCount() * 2); + } + + auto* vertexData = m_geometry.vertexDataAsPoint2D(); + + using T = QskScaleTickmarks::TickType; + for (auto type : { T::MinorTick, T::MediumTick, T::MajorTick }) + { + for (const auto tick : tickmarks.ticks(type)) + { + const auto i = static_cast(type); + + const auto p = m_scale * tick; + const auto d = QVector2D(-m_scale.y(), m_scale.x()).normalized(); + + const auto p1 = m_tickmarkSize[i] * +1 * d + p + m_offset; + const auto p2 = m_tickmarkSize[i] * -1 * d + p + m_offset; + + vertexData[0].set(p1.x(), p1.y()); + vertexData[1].set(p2.x(), p2.y()); + vertexData += 2; + } + } + + m_geometry.markVertexDataDirty(); + } + + QSGGeometry m_geometry; + QSGFlatColorMaterial m_material; + QVector2D m_scale = { 1.0f, 0.0f }; + QVector2D m_offset = { 0.0f, 0.0f }; + QVector3D m_tickmarkSize = { 1.0, 2.0, 4.0 }; +}; + +class PolygonClipNode final : public QSGClipNode +{ +public: + PolygonClipNode() : m_geometry(QSGGeometry::defaultAttributes_Point2D(), 0) + { + m_geometry.setVertexDataPattern(QSGGeometry::DynamicPattern); + m_geometry.setDrawingMode(QSGGeometry::DrawTriangleFan); + setGeometry(&m_geometry); + setIsRectangular(false); + } + + void setGeometryProperties(const qreal radius = 1.0, const qreal cx = 0.0, const qreal cy = 0.0, const int count = 360) + { + auto dirty = false; + dirty |= compare_exchange(m_radius, radius); + dirty |= compare_exchange(m_cx, cx); + dirty |= compare_exchange(m_cy, cy); + dirty |= compare_exchange(m_count, count); + + if (dirty) + { + update(); + } + } + +private: + void update() + { + const auto step = 2.0 * M_PI / m_count; + + if (m_geometry.vertexCount() != m_count) + { + m_geometry.allocate(m_count); + } + + auto* vertices = m_geometry.vertexDataAsPoint2D(); + + for (int i = 0; i < m_count; ++i) + { + vertices[i].x = qFastCos(i * step) * m_radius + m_cx; + vertices[i].y = qFastSin(i * step) * m_radius + m_cy; + } + + m_geometry.markVertexDataDirty(); + markDirty(QSGNode::DirtyGeometry); + } + + using QSGClipNode::setIsRectangular; + using QSGClipNode::setClipRect; + + QSGGeometry m_geometry; + qreal m_radius = 1.0; + qreal m_cx = 0; + qreal m_cy = 0; + int m_count = 360; +}; + +template +struct TickmarksLabelsNode : public QSGNode +{ +public: + QVector2D value(const QVector2D& v, const QVector2D& s, const QVector2D& o) const + { + return static_cast(this)->value(v, s, o); + } + + void update(const QskSkinnable* const skinnable, const QskAspect::Subcontrol subControl, const QVector>& labels, const QVector2D& scale = { 1.0, 0.0 }, const QVector2D& offset = {}) + { + if (childCount() != labels.count()) + { + removeAllChildNodes(); + for (const auto& label : qAsConst(labels)) + { + appendChildNode(new QskTextNode); + } + } + + const QFontMetricsF metrics(skinnable->effectiveFont(subControl)); + const auto h = skinnable->effectiveFontHeight(subControl); + const auto a = skinnable->alignmentHint(subControl); + + auto* textNode = static_cast(firstChild()); + for (const auto& label : qAsConst(labels)) + { + const auto v = value({ (float)label.first, (float)label.first }, scale, offset); + auto x = v.x(); + auto y = v.y(); + + const auto w = metrics.horizontalAdvance(label.second); + + x -= a.testFlag(Qt::AlignRight) ? w : 0; + x -= a.testFlag(Qt::AlignHCenter) ? w / 2 : 0; + + y -= a.testFlag(Qt::AlignBottom) ? h : 0; + y -= a.testFlag(Qt::AlignVCenter) ? h / 2 : 0; + + QskSkinlet::updateTextNode(skinnable, textNode, { x,y,w,h }, a, label.second, subControl); + + textNode = static_cast(textNode->nextSibling()); + } + } +}; + +struct LinearTickmarksLabelsNode final : public TickmarksLabelsNode +{ +public: + QVector2D value(const QVector2D& v, const QVector2D& s, const QVector2D& o) const + { + return v * s + o; + } +}; + +struct RadialTickmarksLabelsNode final : public TickmarksLabelsNode +{ +public: + QVector2D value(const QVector2D& v, const QVector2D& s, const QVector2D& o) const + { + return QVector2D{ + (float)qFastCos(qDegreesToRadians(v.x())), + (float)qFastSin(qDegreesToRadians(v.y())) + } *s + o; + } +}; diff --git a/src/controls/LevelingSensor/QskLevelingSensorSkinlet.cpp b/src/controls/LevelingSensor/QskLevelingSensorSkinlet.cpp new file mode 100644 index 00000000..78bda401 --- /dev/null +++ b/src/controls/LevelingSensor/QskLevelingSensorSkinlet.cpp @@ -0,0 +1,415 @@ +#include "QskLevelingSensorSkinlet.h" +#include "QskLevelingSensor.h" +#include "QskLevelingSensorUtility.h" +#include "QskLevelingSensorNodes.h" +#include "QsgNodeUtility.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +using Q = LevelingSensor; +using R = LevelingSensorSkinlet::NodeRole; + +using namespace qsg; + +float LevelingSensorSkinlet::radius2(const QskSkinnable* const skinnable) +{ + // outer radius + const auto* const sensor = static_cast(skinnable); + const auto contentsRect = sensor->contentsRect(); + return 0.5f * qMin(contentsRect.width(), contentsRect.height()); +} + +float LevelingSensorSkinlet::radius1(const QskSkinnable* const skinnable) +{ + const auto scale = skinnable->strutSizeHint(Q::Horizon); + return radius2(skinnable) * scale.width(); +} + +QPointF LevelingSensorSkinlet::center(const QskSkinnable* const skinnable) +{ + const auto* const sensor = static_cast(skinnable); + return sensor->contentsRect().center(); +} + +LevelingSensorSkinlet::LevelingSensorSkinlet(QskSkin* skin) + : Inherited(skin) +{ + setNodeRoles({ + OuterDisk, + Horizon, + HorizonClip, + TickmarksX, + TickmarksXLabels, + TickmarksY, + TickmarksYLabels, + TickmarksZ, + TickmarksZLabels, }); +} + +template +Q_REQUIRED_RESULT QRectF LevelingSensorSkinlet::subControlRect(const LevelingSensor* const sensor, + const QRectF& contentsRect) const = delete; + +template<> +Q_REQUIRED_RESULT QRectF LevelingSensorSkinlet::subControlRect(const LevelingSensor* const sensor, + const QRectF& contentsRect) const +{ + const auto radius = radius2(sensor); + const auto scale = sensor->strutSizeHint(Q::OuterDisk); + const auto width = 2 * radius * scale.width(); + const auto height = width; + const auto x = contentsRect.center().x() - width / 2; + const auto y = contentsRect.center().y() - height / 2; + return { x, y, width, height }; +} + +template<> +Q_REQUIRED_RESULT QRectF LevelingSensorSkinlet::subControlRect(const LevelingSensor* const sensor, + const QRectF& contentsRect) const +{ + const auto scale = sensor->strutSizeHint(Q::Horizon); + const auto width = 2 * radius1(sensor) * scale.width(); + const auto height = width; + return { + center(sensor).x() - width / 2, + center(sensor).y() - height / 2, + width, + height + }; +} + +QRectF LevelingSensorSkinlet::subControlRect(const QskSkinnable* skinnable, + const QRectF& contentsRect, QskAspect::Subcontrol subControl) const +{ + const auto* const sensor = static_cast(skinnable); + + if (subControl == Q::OuterDisk) + { + return subControlRect(sensor, contentsRect); + } + if (subControl == Q::Horizon) + { + return subControlRect(sensor, contentsRect); + } + + return Inherited::subControlRect(skinnable, contentsRect, subControl); +} + +template +QSGNode* LevelingSensorSkinlet::updateSubNode(const LevelingSensor* const sensor, + const quint8 nodeRole, QSGNode* const node) const = delete; + +template<> +QSGNode* LevelingSensorSkinlet::updateSubNode(const LevelingSensor* const sensor, + const quint8 nodeRole, QSGNode* const node) const +{ + const auto subControl = Q::OuterDisk; + const auto contentsRect = sensor->contentsRect(); + const auto boxRect = subControlRect(sensor, contentsRect); + const auto boxShapeMetrics = QskBoxShapeMetrics{ boxRect.width() / 2 }; + const auto boxBorderMetrics = sensor->boxBorderMetricsHint(subControl); + const auto boxBorderColors = sensor->boxBorderColorsHint(subControl); + const auto boxGradient = sensor->gradientHint(subControl); + + auto* const root = ensure>::node(node); + auto* const bNode = static_cast(root->firstChild()); + + const auto size = radius2(sensor) * sensor->strutSizeHint(Q::OuterDisk).width(); + updateBoxNode(sensor, bNode, { 0, 0, 2 * size, 2 * size }, boxShapeMetrics, boxBorderMetrics, boxBorderColors, boxGradient); + + const auto cX = center(sensor).x(); + const auto cY = center(sensor).y(); + const auto rZ = sensor->arcMetricsHint(subControl).startAngle(); + + const auto matrix = + matrix_deg(0.0, 0.0, 0.0, cX, cY, 0) * + matrix_deg(0.0, 0.0, rZ, 0, 0, 0) * + matrix_deg(0.0, 0.0, 0.0, -size, -size, 0); + + root->setMatrix(matrix); + return root; +} + +template<> +QSGNode* LevelingSensorSkinlet::updateSubNode(const LevelingSensor* const sensor, + const quint8 nodeRole, QSGNode* const node) const +{ + const auto subControl = Q::Horizon; + const auto r1 = radius1(sensor); + const auto cX = center(sensor).x(); + const auto cY = center(sensor).y(); + const auto rX = sensor->rotation().x(); + const auto rZ = sensor->arcMetricsHint(subControl).startAngle(); + const auto dY = 2 * sensor->angle().y(); + const auto p = qBound(0.0, 0.5 + (-rX / dY), 1.0); + + const auto shape = QskBoxShapeMetrics{ r1 }; + const auto bmetrics = sensor->boxBorderMetricsHint(subControl); + const auto bcolors = sensor->boxBorderColorsHint(subControl); + + auto gradient = sensor->gradientHint(Q::Horizon); + gradient.setDirection(QskGradient::Linear); + gradient.setLinearDirection(Qt::Vertical); + gradient.setStops({ + {0.0, gradient.startColor()}, + {p, gradient.startColor()}, + {p, gradient.endColor()}, + {1.0, gradient.endColor()} + }); + + auto* const tNode = ensure>::node(node); + auto* const boxNode = static_cast(tNode->firstChild()); + updateBoxNode(sensor, boxNode, { 0, 0, 2 * r1, 2 * r1 }, shape, bmetrics, bcolors, gradient); + + const auto matrix = + matrix_deg(0, 0, 0, cX, cY, 0) * + matrix_deg(0, 0, rZ, 0, 0, 0) * + matrix_deg(0, 0, 0, -r1, -r1, 0); + + tNode->setMatrix(matrix); + return tNode; +} + +template<> +QSGNode* LevelingSensorSkinlet::updateSubNode(const LevelingSensor* const sensor, + const quint8 nodeRole, QSGNode* const node) const +{ + const auto subControl = Q::TickmarksX; + const auto color = sensor->color(subControl); + const auto scale = sensor->strutSizeHint(subControl); + + const auto cX = center(sensor).x(); + const auto cY = center(sensor).y(); + + const auto rX = sensor->rotation().x(); + const auto rY = sensor->rotation().y(); + const auto rZ = sensor->arcMetricsHint(subControl).startAngle(); + + const auto r1 = radius1(sensor); + const auto r3 = r1 * scale.height(); + + const auto sX = r1 / sensor->angle().x(); + const auto sY = r1 / sensor->angle().y(); + + const auto tX = static_cast(rY * sX); + const auto tY = 0.0; // static_cast(rX * sY); + + auto* const clipping = ensure>>::node(node); + auto* const transform = static_cast(clipping->firstChild()); + auto* const tickmarks = static_cast(transform->firstChild()); + + auto size = qvariant_cast(sensor->effectiveSkinHint(subControl)) * r3; + + clipping->setGeometryProperties(r1, cX, cY); + + tickmarks->setMaterialProperties(color); + tickmarks->setGeometryProperties(sensor->tickmarks(Qt::XAxis), size, {sX, 0.0f}, {tX, tY}); + + const auto matrix = matrix_deg(0, 0, rZ, cX, cY, 0); + transform->setMatrix(matrix); + return clipping; +} + +template<> +QSGNode* LevelingSensorSkinlet::updateSubNode(const LevelingSensor* const sensor, + const quint8 nodeRole, QSGNode* const node) const +{ + const auto subControl = Q::TickmarksY; + const auto color = sensor->color(subControl); + const auto scale = sensor->strutSizeHint(subControl); + + const auto r1 = radius1(sensor); + const auto r3 = r1 * scale.width(); + + const auto rX = 0.00; + const auto rY = 0.00; + const auto rZ = sensor->rotation().z(); + + const auto tX = center(sensor).x(); + const auto tY = center(sensor).y(); + const auto tZ = 0.0; + + auto* const cNode = ensure>>::node(node); + auto* const tNode = static_cast(cNode->firstChild()); + auto* const lNode = static_cast(tNode->firstChild()); + + auto size = qvariant_cast(sensor->effectiveSkinHint(subControl)) * r3; + + cNode->setGeometryProperties(r1, tX, tY); + + const auto sY = static_cast(r1 / sensor->angle().y()); + lNode->setMaterialProperties(color); +#ifdef USE_FILTERING + using TickType = QskScaleTickmarks::TickType; + const auto filter = [=](TickType, qreal v){ return rY - r1 / sY <= v && v <= rY + r1 / sY; }; + lNode->setGeometryProperties(filtered(sensor->tickmarks(Qt::YAxis), filter), size, {0.0f, sY}, {}, 1.0f, true); +#else + lNode->setGeometryProperties(sensor->tickmarks(Qt::YAxis), size, {0.0f, sY}); +#endif + + const auto matrix = matrix_deg(rX, rY, rZ, tX, tY, tZ); + tNode->setMatrix(matrix); + return cNode; +} + +template<> +QSGNode* LevelingSensorSkinlet::updateSubNode(const LevelingSensor* const sensor, + const quint8 nodeRole, QSGNode* const node) const +{ + const auto subControl = Q::TickmarksZ; + const auto color = sensor->color(subControl); + const auto scale = sensor->strutSizeHint(subControl); + + const auto r1 = radius1(sensor); + const auto r2 = radius2(sensor); + const auto r3 = qvariant_cast(sensor->effectiveSkinHint(subControl)) * (r2 - r1) + QVector3D{r1, r1, r1}; + + auto* const transform = ensure>::node(node); + auto* const tickmarksNode = static_cast(transform->firstChild()); + tickmarksNode->setMaterialProperties(color); + tickmarksNode->setGeometryProperties(sensor->tickmarks(Qt::ZAxis), r1, r3); + + const auto rZ = sensor->arcMetricsHint(subControl).startAngle(); + const auto tX = center(sensor).x(); + const auto tY = center(sensor).y(); + + const auto matrix = matrix_deg(0.0, 0.0, rZ, tX, tY); + transform->setMatrix(matrix); + return transform; +} + +template<> +QSGNode* LevelingSensorSkinlet::updateSubNode(const LevelingSensor* const sensor, + const quint8 nodeRole, QSGNode* const node) const +{ + const auto subControl = Q::TickmarksXLabels; + + const auto r1 = radius1(sensor); + const auto r3 = static_cast(r1 * sensor->strutSizeHint(Q::TickmarksX).height()); + const auto sX = r1 / sensor->angle().x(); + const auto sY = r1 / sensor->angle().y(); + const auto rZ = sensor->arcMetricsHint(subControl).startAngle(); + const auto cX = center(sensor).x(); + const auto cY = center(sensor).y(); + const auto tX = sensor->rotation().y() * sX; + const auto tY = r3; + + auto* const cNode = ensure>>::node(node); + auto* const tNode = static_cast(cNode->firstChild()); + auto* const lNode = static_cast(tNode->firstChild()); + tNode->setMatrix(matrix_deg(0.0, 0.0, rZ, cX, cY)); + cNode->setGeometryProperties(r1, center(sensor).x(), center(sensor).y()); + lNode->update(sensor, subControl, sensor->tickmarkLabels(Qt::XAxis), { sX , 0.0}, {tX, tY}); + return cNode; +} + +template<> +QSGNode* LevelingSensorSkinlet::updateSubNode(const LevelingSensor* const sensor, + const quint8 nodeRole, QSGNode* const node) const +{ + const auto subControl = Q::TickmarksYLabels; + const auto r1 = radius1(sensor); + const auto r3 = static_cast(r1 * sensor->strutSizeHint(Q::TickmarksY).width()); + const auto cX = static_cast(center(sensor).x()); + const auto cY = static_cast(center(sensor).y()); + const auto rZ = sensor->rotation().z(); + + auto* const cNode = ensure>>::node(node); + auto* const tNode = static_cast(cNode->firstChild()); + auto* const lNode = static_cast(tNode->firstChild()); + cNode->setGeometryProperties(r1, cX, cY); + tNode->setMatrix(matrix_deg(0.0, 0.0, 0, cX, cY)); + lNode->update(sensor, subControl, sensor->tickmarkLabels(Qt::YAxis), { 0.0, r1 / sensor->angle().y() }, {r3, 0.0}); + return cNode; +} + +template<> +QSGNode* LevelingSensorSkinlet::updateSubNode(const LevelingSensor* const sensor, + const quint8 nodeRole, QSGNode* const node) const +{ + const auto subControl = Q::TickmarksZLabels; + auto* const tNode = ensure>::node(node); + auto* const lNode = static_cast(tNode->firstChild()); + const auto r1 = radius1(sensor); + const auto r3 = static_cast(r1 * sensor->strutSizeHint(subControl).width()); + const auto cX = static_cast(center(sensor).x()); + const auto cY = static_cast(center(sensor).y()); + const auto rZ = sensor->arcMetricsHint(subControl).startAngle(); + lNode->update(sensor, subControl, sensor->tickmarkLabels(Qt::ZAxis), { r3, r3 }); + tNode->setMatrix(matrix_deg(0.0, 0.0, rZ, cX, cY)); + return tNode; +} + +template<> +QSGNode* LevelingSensorSkinlet::updateSubNode(const LevelingSensor* const sensor, + const quint8 nodeRole, QSGNode* const node) const +{ + const auto cX = center(sensor).x(); + const auto cY = center(sensor).y(); + const auto r1 = radius1(sensor); + + auto* const clipNode = ensure::node(node); + clipNode->setGeometryProperties(r1, cX, cY); + return clipNode; +} + +QSGNode* LevelingSensorSkinlet::updateSubNode( + const QskSkinnable* const skinnable, const quint8 nodeRole, QSGNode* const node) const +{ + const auto* const sensor = static_cast(skinnable); + + const auto subControl = [nodeRole, sensor](){ + switch(static_cast(nodeRole)) + { + case OuterDisk: return Q::OuterDisk; + case Horizon: return Q::Horizon; + case HorizonClip: return Q::Horizon; + case TickmarksX: return Q::TickmarksX; + case TickmarksXLabels: return Q::TickmarksXLabels; + case TickmarksY: return Q::TickmarksY; + case TickmarksYLabels: return Q::TickmarksYLabels; + case TickmarksZ: return Q::TickmarksZ; + case TickmarksZLabels: return Q::TickmarksZLabels; + default: return QskAspect::NoSubcontrol; + } + }(); + + if (qvariant_cast(sensor->effectiveSkinHint(subControl | QskAspect::Option))) + { + return nullptr; + } + + switch(static_cast(nodeRole)) + { + case OuterDisk: return updateSubNode(sensor, nodeRole, node); + case Horizon: return updateSubNode(sensor, nodeRole, node); + case HorizonClip: return updateSubNode(sensor, nodeRole, node); + case TickmarksX: return updateSubNode(sensor, nodeRole, node); + case TickmarksXLabels: return updateSubNode(sensor, nodeRole, node); + case TickmarksY: return updateSubNode(sensor, nodeRole, node); + case TickmarksYLabels: return updateSubNode(sensor, nodeRole, node); + case TickmarksZ: return updateSubNode(sensor, nodeRole, node); + case TickmarksZLabels: return updateSubNode(sensor, nodeRole, node); + default: return Inherited::updateSubNode(sensor, nodeRole, node); + } +} + +#include "moc_QskLevelingSensorSkinlet.cpp" diff --git a/src/controls/LevelingSensor/QskLevelingSensorSkinlet.h b/src/controls/LevelingSensor/QskLevelingSensorSkinlet.h new file mode 100644 index 00000000..6be5b9fc --- /dev/null +++ b/src/controls/LevelingSensor/QskLevelingSensorSkinlet.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include + +class LevelingSensor; + +class LevelingSensorSkinlet : public QskSkinlet +{ + Q_GADGET + + using Inherited = QskSkinlet; + +public: + enum NodeRole + { + OuterDisk, + Horizon, + HorizonClip, + TickmarksX, + TickmarksXLabels, + TickmarksY, + TickmarksYLabels, + TickmarksZ, + TickmarksZLabels, + TriangleBar, + TickmarksYIndicator, + RoleCount + }; + + Q_INVOKABLE LevelingSensorSkinlet(QskSkin* skin = nullptr); + ~LevelingSensorSkinlet() override = default; + + /// @returns Returns the inner radius of the @p skinnable + static Q_REQUIRED_RESULT float radius2(const QskSkinnable* const skinnable); + /// @returns Returns the outer radius of the @p skinnable + static Q_REQUIRED_RESULT float radius1(const QskSkinnable* const skinnable); + /// @returns Returns the center point of the control + static Q_REQUIRED_RESULT QPointF center(const QskSkinnable* const skinnable); + +protected: + + Q_REQUIRED_RESULT QRectF subControlRect(const QskSkinnable* skinnable, + const QRectF& contentsRect, QskAspect::Subcontrol subControl) const override; + + Q_REQUIRED_RESULT QSGNode* updateSubNode(const QskSkinnable* skinnable, + quint8 nodeRole, QSGNode* node) const override; + + template + Q_REQUIRED_RESULT QRectF subControlRect(const LevelingSensor* sensor, + const QRectF& contentsRect) const; + + template + Q_REQUIRED_RESULT QSGNode* updateSubNode(const LevelingSensor* sensor, + quint8 nodeRole, QSGNode* node) const; +}; diff --git a/src/controls/LevelingSensor/QskLevelingSensorUtility.cpp b/src/controls/LevelingSensor/QskLevelingSensorUtility.cpp new file mode 100644 index 00000000..e28b1e35 --- /dev/null +++ b/src/controls/LevelingSensor/QskLevelingSensorUtility.cpp @@ -0,0 +1 @@ +#include "QskLevelingSensorUtility.h" \ No newline at end of file diff --git a/src/controls/LevelingSensor/QskLevelingSensorUtility.h b/src/controls/LevelingSensor/QskLevelingSensorUtility.h new file mode 100644 index 00000000..cba2ea20 --- /dev/null +++ b/src/controls/LevelingSensor/QskLevelingSensorUtility.h @@ -0,0 +1,94 @@ +#pragma once + +#include +#include + +#include + +// create a homogenous transformation matrix +inline Q_REQUIRED_RESULT QMatrix4x4 matrix_deg( + float rX = 0.0f, + float rY = 0.0f, + float rZ = 0.0f, + float tX = 0.0f, + float tY = 0.0f, + float tZ = 0.0f +) +{ + // Convert rotation angles to radians + float rotationX = qDegreesToRadians(rX); + float rotationY = qDegreesToRadians(rY); + float rotationZ = qDegreesToRadians(rZ); + + // Calculate sin and cos of the rotation angles + float cosX = qCos(rotationX); + float sinX = qSin(rotationX); + float cosY = qCos(rotationY); + float sinY = qSin(rotationY); + float cosZ = qCos(rotationZ); + float sinZ = qSin(rotationZ); + + // Create the transform matrix + return QMatrix4x4( + cosY * cosZ, sinX * sinY * cosZ - cosX * sinZ, cosX * sinY * cosZ + sinX * sinZ, tX, + cosY * sinZ, sinX * sinY * sinZ + cosX * cosZ, cosX * sinY * sinZ - sinX * cosZ, tY, + -sinY, sinX * cosY, cosX * cosY, tZ, + 0, 0, 0, 1 + ); +} + +template +inline bool compare_exchange(T& dst, const T& src) +{ + if (dst != src) + { + dst = src; + return true; + } + return false; +} + +template<> +inline bool compare_exchange(float& dst, const float& src) +{ + if (!qFuzzyCompare(dst, src)) + { + dst = src; + return true; + } + return false; +} + +template<> +inline bool compare_exchange(qreal& dst, const qreal& src) +{ + if (!qFuzzyCompare(dst, src)) + { + dst = src; + return true; + } + return false; +} + +inline QskScaleTickmarks filtered(const QskScaleTickmarks& tickmarks, const std::function& predicate) +{ + QskScaleTickmarks result; + QVector ticks[3]; + + using T = QskScaleTickmarks::TickType; + for (auto type : { T::MinorTick, T::MediumTick, T::MajorTick }) + { + for (const auto tick : tickmarks.ticks(type)) + { + if (predicate(type, tick)) + { + ticks[type] << tick; + } + } + } + + result.setMinorTicks(ticks[QskScaleTickmarks::MinorTick]); + result.setMediumTicks(ticks[QskScaleTickmarks::MediumTick]); + result.setMajorTicks(ticks[QskScaleTickmarks::MajorTick]); + return result; +} \ No newline at end of file diff --git a/src/controls/LevelingSensor/QskLevelingSensorUtility_test.cpp b/src/controls/LevelingSensor/QskLevelingSensorUtility_test.cpp new file mode 100644 index 00000000..f8baa02b --- /dev/null +++ b/src/controls/LevelingSensor/QskLevelingSensorUtility_test.cpp @@ -0,0 +1,190 @@ +#include +#include +#include +#include + +#include + +#include "QskLevelingSensorUtility.h" + +#define ASSERT_TRUE(expr) if(!(expr)) { std::cerr << "ASSERT_TRUE(!" << #expr << "):" << __LINE__ << '\n'; return false; } + +class QSGNodeBase : public QSGNode +{ +public: + virtual std::string name() const = 0; +}; + +class QSGNodeA final : public QSGNodeBase{ public: QSGNodeA() { std::cout << name() << '\n'; } std::string name() const override { return "A"; }}; +class QSGNodeB final : public QSGNodeBase{ public: QSGNodeB() { std::cout << name() << '\n'; } std::string name() const override { return "B"; }}; +class QSGNodeC final : public QSGNodeBase{ public: QSGNodeC() { std::cout << name() << '\n'; } std::string name() const override { return "C"; }}; +class QSGNodeD final : public QSGNodeBase{ public: QSGNodeD() { std::cout << name() << '\n'; } std::string name() const override { return "D"; }}; +class QSGNodeE final : public QSGNodeBase{ public: QSGNodeE() { std::cout << name() << '\n'; } std::string name() const override { return "E"; }}; +class QSGNodeF final : public QSGNodeBase{ public: QSGNodeF() { std::cout << name() << '\n'; } std::string name() const override { return "F"; }}; +class QSGNodeG final : public QSGNodeBase{ public: QSGNodeG() { std::cout << name() << '\n'; } std::string name() const override { return "G"; }}; +class QSGNodeH final : public QSGNodeBase{ public: QSGNodeH() { std::cout << name() << '\n'; } std::string name() const override { return "H"; }}; + +using NodeType = QSGNode; + +bool testcase_ensure_node() +{ + NodeType* root = nullptr; + ASSERT_TRUE(root = ensure_node()); + ASSERT_TRUE(root->childCount() == 0); + delete root; + return true; +} + +// seq> + +/* + +bool testcase_seq() +{ + using namespace qsg; + { + auto* const root = ensure_node(); + seq, N, N>::append(root); + delete root; + } + { + auto* const root = ensure_node(); + ASSERT_TRUE(root); + ASSERT_TRUE(root->childCount() == 0); + qsg::seq<>::append(root); + ASSERT_TRUE(root->childCount() == 0); + delete root; + } + { + auto* const root = ensure_node(); + ASSERT_TRUE(root); + ASSERT_TRUE(root->childCount() == 0); + qsg::seq>::append(root); + ASSERT_TRUE(root->childCount() == 1); + delete root; + } + { + auto* const root = ensure_node(); + ASSERT_TRUE(root); + ASSERT_TRUE(root->childCount() == 0); + qsg::seq, N>::append(root); + ASSERT_TRUE(root->childCount() == 2); + delete root; + } + { + // - QSGNode + // - QSGNodeA + // - QSGNodeB + // - QSGNodeC + auto* const root = ensure_node(); + ASSERT_TRUE(root); + ASSERT_TRUE(root->childCount() == 0); + qsg::seq, N, N>::append(root); + ASSERT_TRUE(root->childCount() == 3); + delete root; + } + return true; +} + +*/ + +bool testcase_par() +{ + using namespace qsg; + { + std::cout << __func__ << 0 << '\n'; + //auto* const root = ensure_node(); + //ASSERT_TRUE(root->childCount() == 0); + //par<4, QSGNodeA, par<4, QSGNodeB>>::append(root); + //ASSERT_TRUE(root->childCount() == 4); + //for(int i = 0; i < 4; ++i) + //{ + // ASSERT_TRUE(root->childAtIndex(i)->childCount() == 4); + //} + //delete root; + } + { + std::cout << __func__ << 1 << '\n'; + // - QSGNode + // - QSGNodeA + // - QSGNodeB + // - QSGNodeC + // - QSGNodeD + // - QSGNodeA + // - QSGNodeB + // - QSGNodeC + // - QSGNodeD + // - QSGNodeA + // - QSGNodeB + // - QSGNodeC + // - QSGNodeD + // - QSGNodeA + // - QSGNodeB + // - QSGNodeC + // - QSGNodeD + auto* const root = ensure_node(); + ASSERT_TRUE(root->childCount() == 0); + par<4, QSGNodeA, + seq, + par<1, QSGNodeC>, + par<1, QSGNodeD>>>::append(root); + ASSERT_TRUE(root->childCount() == 4); + ASSERT_TRUE(root->childAtIndex(0)->childCount() == 3); + ASSERT_TRUE(root->childAtIndex(1)->childCount() == 3); + ASSERT_TRUE(root->childAtIndex(2)->childCount() == 3); + ASSERT_TRUE(root->childAtIndex(3)->childCount() == 3); + } + { + std::cout << __func__ << 3 << '\n'; + auto* const root = ensure_node(); + seq< + par<1, QSGNodeE, + seq< + par<1, QSGNodeA>, + par<1, QSGNodeB>, + par<1, QSGNodeC>>>, + par<1, QSGNodeF, + seq< + par<1, QSGNodeB>, + par<1, QSGNodeC>, + par<1, QSGNodeA>>>, + par<1, QSGNodeG, + seq< + par<1, QSGNodeC>, + par<1, QSGNodeA>, + par<1, QSGNodeB>>> + >::append(root); + ASSERT_TRUE(root->childCount() == 3); + for(int i = 0; i < root->childCount(); ++i) + { + ASSERT_TRUE(root->childAtIndex(i)->childCount() == 3); + for(int j = 0; j < root->childAtIndex(i)->childAtIndex(i)->childCount(); ++j) + { + ASSERT_TRUE(root->childAtIndex(i)->childCount() == 0); + } + } + ASSERT_TRUE(dynamic_cast(root->childAtIndex(0))); + ASSERT_TRUE(dynamic_cast(root->childAtIndex(0)->childAtIndex(0))); + ASSERT_TRUE(dynamic_cast(root->childAtIndex(0)->childAtIndex(1))); + ASSERT_TRUE(dynamic_cast(root->childAtIndex(0)->childAtIndex(2))); + ASSERT_TRUE(dynamic_cast(root->childAtIndex(1))); + ASSERT_TRUE(dynamic_cast(root->childAtIndex(1)->childAtIndex(0))); + ASSERT_TRUE(dynamic_cast(root->childAtIndex(1)->childAtIndex(1))); + ASSERT_TRUE(dynamic_cast(root->childAtIndex(1)->childAtIndex(2))); + ASSERT_TRUE(dynamic_cast(root->childAtIndex(2))); + ASSERT_TRUE(dynamic_cast(root->childAtIndex(2)->childAtIndex(0))); + ASSERT_TRUE(dynamic_cast(root->childAtIndex(2)->childAtIndex(1))); + ASSERT_TRUE(dynamic_cast(root->childAtIndex(2)->childAtIndex(2))); + } + return true; +} + +int main() +{ + bool result = true; + // result &= testcase_ensure_node(); + // result &= testcase_seq(); + result &= testcase_par(); + std::cout << (result ? "ok" : "failed") << std::endl; + return result ? EXIT_SUCCESS : EXIT_FAILURE; +} \ No newline at end of file