initial commit
This commit is contained in:
parent
3fee4907c0
commit
3552c79aec
|
|
@ -0,0 +1 @@
|
||||||
|
#include "QsgNodeUtility.h"
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -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"
|
||||||
|
|
@ -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];
|
||||||
|
};
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -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"
|
||||||
|
|
@ -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;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
#include "QskLevelingSensorUtility.h"
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue