initial commit

This commit is contained in:
Vogel, Rick 2023-07-10 15:54:53 +02:00
parent 3fee4907c0
commit 3552c79aec
11 changed files with 1356 additions and 0 deletions

View File

@ -0,0 +1 @@
#include "QsgNodeUtility.h"

View File

@ -0,0 +1,100 @@
#pragma once
#include <QSGNode>
#include <type_traits>
namespace qsg
{
using NodeType = QSGNode;
template<typename ... Ts>
struct seq;
template<>
struct seq<>
{
static void append(NodeType* root)
{
}
};
template<typename T>
struct seq<T>
{
static void append(NodeType* root)
{
T::append(root);
}
};
template<typename T, typename ... Ts>
struct seq<T, Ts...>
{
static void append(NodeType* root)
{
T::append(root);
seq<Ts...>::append(root);
}
};
template<int N, typename ... Ts>
struct par;
template<int N, typename T>
struct par<N,T>
{
static void append(NodeType* root)
{
const auto n = N;
for (int i = 0; i < N; ++i)
{
root->appendChildNode(new T);
}
}
};
template<int N, typename T, typename U>
struct par<N,T,U>
{
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<typename ... Ts>
struct ensure;
template<typename Root>
struct ensure<Root>
{
static Q_REQUIRED_RESULT Root* node(NodeType* root = nullptr)
{
if(root == nullptr)
{
root = new Root;
}
return static_cast<Root*>(root);
}
};
template<typename Root, typename Append>
struct ensure<Root, Append>
{
static Q_REQUIRED_RESULT Root* node(NodeType* root = nullptr)
{
if(root == nullptr)
{
root = new Root;
Append::append(root);
}
return static_cast<Root*>(root);
}
};
}

View File

@ -0,0 +1,142 @@
#include "QskLevelingSensor.h"
#include <QskScaleTickmarks.h>
namespace
{
template<typename T>
bool compareExchange(T& dst, const T& src)
{
if (dst != src)
{
dst = src;
return true;
}
return false;
}
template<>
bool compareExchange<float>(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"

View File

@ -0,0 +1,60 @@
#pragma once
#include <QskControl.h>
#include <QskScaleTickmarks.h>
#include <QVector3D>
#include <QskAspect.h>
/// @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<QPair<qreal, QString>>;
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];
};

View File

@ -0,0 +1,297 @@
#pragma once
#include <QSGGeometry>
#include <QSGGeometryNode>
#include <QSGFlatColorMaterial>
#include <QFontMetricsF>
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<int>(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<int>(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<typename CRTP>
struct TickmarksLabelsNode : public QSGNode
{
public:
QVector2D value(const QVector2D& v, const QVector2D& s, const QVector2D& o) const
{
return static_cast<const CRTP*>(this)->value(v, s, o);
}
void update(const QskSkinnable* const skinnable, const QskAspect::Subcontrol subControl, const QVector<QPair<double, QString>>& 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<QskTextNode*>(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<QskTextNode*>(textNode->nextSibling());
}
}
};
struct LinearTickmarksLabelsNode final : public TickmarksLabelsNode<LinearTickmarksLabelsNode>
{
public:
QVector2D value(const QVector2D& v, const QVector2D& s, const QVector2D& o) const
{
return v * s + o;
}
};
struct RadialTickmarksLabelsNode final : public TickmarksLabelsNode<RadialTickmarksLabelsNode>
{
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;
}
};

View File

@ -0,0 +1,415 @@
#include "QskLevelingSensorSkinlet.h"
#include "QskLevelingSensor.h"
#include "QskLevelingSensorUtility.h"
#include "QskLevelingSensorNodes.h"
#include "QsgNodeUtility.h"
#include <QskArcMetrics.h>
#include <QskBoxBorderColors.h>
#include <QskBoxBorderMetrics.h>
#include <QskBoxNode.h>
#include <QskBoxShapeMetrics.h>
#include <QskGradient.h>
#include <QskGraphic.h>
#include <QskScaleEngine.h>
#include <QskScaleTickmarks.h>
#include <QskTextColors.h>
#include <QskTextNode.h>
#include <QskTextOptions.h>
#include <QskTickmarksNode.h>
#include <QFontMetrics>
#include <QSGClipNode>
#include <QSGFlatColorMaterial>
#include <QSGGeometryNode>
#include <qmath.h>
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<const Q*>(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<const Q*>(skinnable);
return sensor->contentsRect().center();
}
LevelingSensorSkinlet::LevelingSensorSkinlet(QskSkin* skin)
: Inherited(skin)
{
setNodeRoles({
OuterDisk,
Horizon,
HorizonClip,
TickmarksX,
TickmarksXLabels,
TickmarksY,
TickmarksYLabels,
TickmarksZ,
TickmarksZLabels, });
}
template<LevelingSensorSkinlet::NodeRole>
Q_REQUIRED_RESULT QRectF LevelingSensorSkinlet::subControlRect(const LevelingSensor* const sensor,
const QRectF& contentsRect) const = delete;
template<>
Q_REQUIRED_RESULT QRectF LevelingSensorSkinlet::subControlRect<R::OuterDisk>(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<R::Horizon>(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<const Q*>(skinnable);
if (subControl == Q::OuterDisk)
{
return subControlRect<OuterDisk>(sensor, contentsRect);
}
if (subControl == Q::Horizon)
{
return subControlRect<Horizon>(sensor, contentsRect);
}
return Inherited::subControlRect(skinnable, contentsRect, subControl);
}
template<LevelingSensorSkinlet::NodeRole>
QSGNode* LevelingSensorSkinlet::updateSubNode(const LevelingSensor* const sensor,
const quint8 nodeRole, QSGNode* const node) const = delete;
template<>
QSGNode* LevelingSensorSkinlet::updateSubNode<R::OuterDisk>(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<OuterDisk>(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<QSGTransformNode, par<1, QskBoxNode>>::node(node);
auto* const bNode = static_cast<QskBoxNode*>(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<R::Horizon>(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<QSGTransformNode, par<1, QskBoxNode>>::node(node);
auto* const boxNode = static_cast<QskBoxNode*>(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<R::TickmarksX>(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<float>(rY * sX);
const auto tY = 0.0; // static_cast<float>(rX * sY);
auto* const clipping = ensure<PolygonClipNode, par<1, QSGTransformNode, par<1, LinearTickmarksNode>>>::node(node);
auto* const transform = static_cast<QSGTransformNode*>(clipping->firstChild());
auto* const tickmarks = static_cast<LinearTickmarksNode*>(transform->firstChild());
auto size = qvariant_cast<QVector3D>(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<R::TickmarksY>(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<PolygonClipNode, par<1, QSGTransformNode, par<1, LinearTickmarksNode>>>::node(node);
auto* const tNode = static_cast<QSGTransformNode*>(cNode->firstChild());
auto* const lNode = static_cast<LinearTickmarksNode*>(tNode->firstChild());
auto size = qvariant_cast<QVector3D>(sensor->effectiveSkinHint(subControl)) * r3;
cNode->setGeometryProperties(r1, tX, tY);
const auto sY = static_cast<float>(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<R::TickmarksZ>(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<QVector3D>(sensor->effectiveSkinHint(subControl)) * (r2 - r1) + QVector3D{r1, r1, r1};
auto* const transform = ensure<QSGTransformNode, par<1,RadialTickmarksNode>>::node(node);
auto* const tickmarksNode = static_cast<RadialTickmarksNode*>(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<R::TickmarksXLabels>(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<float>(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<PolygonClipNode, par<1, QSGTransformNode, par<1, LinearTickmarksLabelsNode>>>::node(node);
auto* const tNode = static_cast<QSGTransformNode*>(cNode->firstChild());
auto* const lNode = static_cast<LinearTickmarksLabelsNode*>(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<R::TickmarksYLabels>(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<float>(r1 * sensor->strutSizeHint(Q::TickmarksY).width());
const auto cX = static_cast<float>(center(sensor).x());
const auto cY = static_cast<float>(center(sensor).y());
const auto rZ = sensor->rotation().z();
auto* const cNode = ensure<PolygonClipNode, par<1, QSGTransformNode, par<1, LinearTickmarksLabelsNode>>>::node(node);
auto* const tNode = static_cast<QSGTransformNode*>(cNode->firstChild());
auto* const lNode = static_cast<LinearTickmarksLabelsNode*>(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<R::TickmarksZLabels>(const LevelingSensor* const sensor,
const quint8 nodeRole, QSGNode* const node) const
{
const auto subControl = Q::TickmarksZLabels;
auto* const tNode = ensure<QSGTransformNode, par<1,RadialTickmarksLabelsNode>>::node(node);
auto* const lNode = static_cast<RadialTickmarksLabelsNode*>(tNode->firstChild());
const auto r1 = radius1(sensor);
const auto r3 = static_cast<float>(r1 * sensor->strutSizeHint(subControl).width());
const auto cX = static_cast<float>(center(sensor).x());
const auto cY = static_cast<float>(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<R::HorizonClip>(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<PolygonClipNode>::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<const Q*>(skinnable);
const auto subControl = [nodeRole, sensor](){
switch(static_cast<R>(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<bool>(sensor->effectiveSkinHint(subControl | QskAspect::Option)))
{
return nullptr;
}
switch(static_cast<R>(nodeRole))
{
case OuterDisk: return updateSubNode<OuterDisk>(sensor, nodeRole, node);
case Horizon: return updateSubNode<Horizon>(sensor, nodeRole, node);
case HorizonClip: return updateSubNode<HorizonClip>(sensor, nodeRole, node);
case TickmarksX: return updateSubNode<TickmarksX>(sensor, nodeRole, node);
case TickmarksXLabels: return updateSubNode<TickmarksXLabels>(sensor, nodeRole, node);
case TickmarksY: return updateSubNode<TickmarksY>(sensor, nodeRole, node);
case TickmarksYLabels: return updateSubNode<TickmarksYLabels>(sensor, nodeRole, node);
case TickmarksZ: return updateSubNode<TickmarksZ>(sensor, nodeRole, node);
case TickmarksZLabels: return updateSubNode<TickmarksZLabels>(sensor, nodeRole, node);
default: return Inherited::updateSubNode(sensor, nodeRole, node);
}
}
#include "moc_QskLevelingSensorSkinlet.cpp"

View File

@ -0,0 +1,56 @@
#pragma once
#include <QskSkinlet.h>
#include <QSGNode>
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<NodeRole>
Q_REQUIRED_RESULT QRectF subControlRect(const LevelingSensor* sensor,
const QRectF& contentsRect) const;
template<NodeRole>
Q_REQUIRED_RESULT QSGNode* updateSubNode(const LevelingSensor* sensor,
quint8 nodeRole, QSGNode* node) const;
};

View File

@ -0,0 +1 @@
#include "QskLevelingSensorUtility.h"

View File

@ -0,0 +1,94 @@
#pragma once
#include <qmath.h>
#include <qmatrix4x4.h>
#include <QskScaleTickmarks.h>
// 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<typename T>
inline bool compare_exchange(T& dst, const T& src)
{
if (dst != src)
{
dst = src;
return true;
}
return false;
}
template<>
inline bool compare_exchange<float>(float& dst, const float& src)
{
if (!qFuzzyCompare(dst, src))
{
dst = src;
return true;
}
return false;
}
template<>
inline bool compare_exchange<qreal>(qreal& dst, const qreal& src)
{
if (!qFuzzyCompare(dst, src))
{
dst = src;
return true;
}
return false;
}
inline QskScaleTickmarks filtered(const QskScaleTickmarks& tickmarks, const std::function<bool(QskScaleTickmarks::TickType, qreal)>& predicate)
{
QskScaleTickmarks result;
QVector<qreal> 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;
}

View File

@ -0,0 +1,190 @@
#include <cstdlib>
#include <type_traits>
#include <iostream>
#include <string>
#include <QSGNode>
#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<NodeType>());
ASSERT_TRUE(root->childCount() == 0);
delete root;
return true;
}
// seq<par<seq<seq<N<QSGNodeD, 4>>
/*
bool testcase_seq()
{
using namespace qsg;
{
auto* const root = ensure_node<NodeType>();
seq<N<QSGNodeA>, N<QSGNodeB>, N<QSGNodeC>>::append(root);
delete root;
}
{
auto* const root = ensure_node<NodeType>();
ASSERT_TRUE(root);
ASSERT_TRUE(root->childCount() == 0);
qsg::seq<>::append(root);
ASSERT_TRUE(root->childCount() == 0);
delete root;
}
{
auto* const root = ensure_node<NodeType>();
ASSERT_TRUE(root);
ASSERT_TRUE(root->childCount() == 0);
qsg::seq<N<QSGNodeA>>::append(root);
ASSERT_TRUE(root->childCount() == 1);
delete root;
}
{
auto* const root = ensure_node<NodeType>();
ASSERT_TRUE(root);
ASSERT_TRUE(root->childCount() == 0);
qsg::seq<N<QSGNodeA>, N<QSGNodeB>>::append(root);
ASSERT_TRUE(root->childCount() == 2);
delete root;
}
{
// - QSGNode
// - QSGNodeA
// - QSGNodeB
// - QSGNodeC
auto* const root = ensure_node<NodeType>();
ASSERT_TRUE(root);
ASSERT_TRUE(root->childCount() == 0);
qsg::seq<N<QSGNodeA>, N<QSGNodeB>, N<QSGNodeC>>::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<QSGNode>();
//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<QSGNode>();
ASSERT_TRUE(root->childCount() == 0);
par<4, QSGNodeA,
seq<par<1, QSGNodeB>,
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<QSGNode>();
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<QSGNodeE*>(root->childAtIndex(0)));
ASSERT_TRUE(dynamic_cast<QSGNodeA*>(root->childAtIndex(0)->childAtIndex(0)));
ASSERT_TRUE(dynamic_cast<QSGNodeB*>(root->childAtIndex(0)->childAtIndex(1)));
ASSERT_TRUE(dynamic_cast<QSGNodeC*>(root->childAtIndex(0)->childAtIndex(2)));
ASSERT_TRUE(dynamic_cast<QSGNodeF*>(root->childAtIndex(1)));
ASSERT_TRUE(dynamic_cast<QSGNodeB*>(root->childAtIndex(1)->childAtIndex(0)));
ASSERT_TRUE(dynamic_cast<QSGNodeC*>(root->childAtIndex(1)->childAtIndex(1)));
ASSERT_TRUE(dynamic_cast<QSGNodeA*>(root->childAtIndex(1)->childAtIndex(2)));
ASSERT_TRUE(dynamic_cast<QSGNodeG*>(root->childAtIndex(2)));
ASSERT_TRUE(dynamic_cast<QSGNodeC*>(root->childAtIndex(2)->childAtIndex(0)));
ASSERT_TRUE(dynamic_cast<QSGNodeA*>(root->childAtIndex(2)->childAtIndex(1)));
ASSERT_TRUE(dynamic_cast<QSGNodeB*>(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;
}