diff --git a/playground/CMakeLists.txt b/playground/CMakeLists.txt index 7fc3449a..3b997bf1 100644 --- a/playground/CMakeLists.txt +++ b/playground/CMakeLists.txt @@ -6,6 +6,7 @@ add_subdirectory(invoker) add_subdirectory(shadows) add_subdirectory(shapes) add_subdirectory(charts) +add_subdirectory(levelingsensor) if (BUILD_INPUTCONTEXT) add_subdirectory(inputpanel) diff --git a/playground/levelingsensor/CMakeLists.txt b/playground/levelingsensor/CMakeLists.txt new file mode 100644 index 00000000..b397f548 --- /dev/null +++ b/playground/levelingsensor/CMakeLists.txt @@ -0,0 +1,11 @@ +############################################################################ +# QSkinny - Copyright (C) 2016 Uwe Rathmann +# SPDX-License-Identifier: BSD-3-Clause +############################################################################ + +set(SOURCES + SkinFactory.h SkinFactory.cpp + main.cpp +) + +qsk_add_example(levelingsensor ${SOURCES}) diff --git a/playground/levelingsensor/SkinFactory.cpp b/playground/levelingsensor/SkinFactory.cpp new file mode 100644 index 00000000..04fbf08b --- /dev/null +++ b/playground/levelingsensor/SkinFactory.cpp @@ -0,0 +1,170 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#include "SkinFactory.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include + +namespace +{ + class Skin : public QskSkin + { + template< typename T > + void style( QskSkinHintTableEditor& editor ); + + template< typename Skinnable, typename Skinlet > + void declareSkinlet() + { + QskSkin::declareSkinlet< Skinnable, Skinlet >(); + } + + template< typename Skinnable, typename Skinlet > + void declareSkinlet( QskSkinHintTableEditor& editor ) + { + QskSkin::declareSkinlet< Skinnable, Skinlet >(); + style< Skinnable >( editor ); + } + + public: + Skin() + { + QskSkinHintTableEditor editor( &hintTable() ); + declareSkinlet< QskSlider, QskSliderSkinlet >( editor ); + declareSkinlet< QskTextLabel, QskTextLabelSkinlet >(); + declareSkinlet< QskLevelingSensor, QskLevelingSensorSkinlet >( editor ); + } + }; + + template<> + void Skin::style< QskSlider >( QskSkinHintTableEditor& editor ) + { + using A = QskAspect; + using Q = QskSlider; + + const qreal extent = 40; + + // Panel + + for ( auto variation : { A::Horizontal, A::Vertical } ) + { + const auto aspect = Q::Panel | variation; + + editor.setMetric( aspect | A::Size, extent ); + editor.setBoxBorderMetrics( aspect, 0 ); + editor.setBoxShape( aspect, 0 ); + editor.setGradient( aspect, QskGradient() ); + } + + // Groove, Fill + + for ( auto variation : { A::Horizontal, A::Vertical } ) + { + for ( auto subControl : { Q::Groove, Q::Fill } ) + { + const auto aspect = subControl | variation; + + editor.setMetric( aspect | A::Size, 0.3 * extent ); + + editor.setBoxBorderMetrics( aspect, 0 ); + editor.setBoxShape( aspect, 0.1 * extent ); + } + + editor.setGradient( Q::Groove | variation, Qt::lightGray ); + editor.setGradient( Q::Fill | variation, Qt::darkGray ); + } + + // Handle + + for ( auto variation : { A::Horizontal, A::Vertical } ) + { + const auto aspect = Q::Handle | variation; + editor.setColor( aspect, Qt::black ); + + editor.setBoxShape( aspect, 20.0, Qt::RelativeSize ); + + const qreal sz = 0.75 * extent; + editor.setStrutSize( aspect, sz, sz ); + } + } + + template<> + void Skin::style< QskLevelingSensor >( QskSkinHintTableEditor& editor ) + { + using Q = QskLevelingSensor; + + static constexpr auto r1 = 0.9; + static constexpr auto r2 = 1.0; + + QskGradient gradient{ { + { 0.5, Qt::lightGray }, + { 0.5, Qt::lightGray }, + { 0.5, Qt::darkGray }, + { 1.0, Qt::darkGray }, + } }; + gradient.setLinearDirection( Qt::Vertical ); + + editor.setColor( Q::Background, "dimgray" ); + + editor.setStrutSize( Q::OuterDisk, { r2, r2 } ); + editor.setColor( Q::OuterDisk, Qt::white ); + + editor.setGradient( Q::Horizon, gradient ); + editor.setStrutSize( Q::Horizon, { r1, r1 } ); + + editor.setColor( Q::TickmarksX, Qt::black ); + editor.setStrutSize( Q::TickmarksX, { r1, 0.2 } ); // w %, h % + editor.setHint( Q::TickmarksX, QVector3D{ 0.50, 0.75, 1.0 } ); // % + editor.setAlignment( Q::TickmarksX, Qt::AlignCenter ); + + editor.setStrutSize( Q::TickmarksXLabels, { r1, 0.15 } ); // w %, h % + editor.setAlignment( Q::TickmarksXLabels, Qt::AlignTop | Qt::AlignHCenter ); + + editor.setColor( Q::TickmarksY, Qt::black ); + editor.setStrutSize( Q::TickmarksY, { 0.1, r1 } ); // w %, h % + editor.setHint( Q::TickmarksY, QVector3D{ 0.50, 0.75, 1.00 } ); // % + editor.setAlignment( Q::TickmarksY, Qt::AlignCenter ); + + editor.setStrutSize( Q::TickmarksYLabels, { 0.15, r1 } ); // w %, h % + editor.setAlignment( Q::TickmarksYLabels, Qt::AlignCenter ); + + editor.setColor( Q::TickmarksZ, "silver" ); + editor.setStrutSize( Q::TickmarksZ, { 0.90, 0.95 } ); + editor.setHint( Q::TickmarksZ, QVector3D{ 0.50, 0.75, 1.00 } ); // % + + editor.setStrutSize( Q::TickmarksZLabels, { 0.9, 0.0 } ); // r1 %, r2 % + editor.setAlignment( Q::TickmarksZLabels, Qt::AlignCenter ); + } +} + +QStringList SkinFactory::skinNames() const +{ + return { "Skin" }; +} + +QskSkin* SkinFactory::createSkin( const QString& skinName ) +{ + if ( skinName == "Skin" ) + return new Skin(); + + return nullptr; +} + +#include "moc_SkinFactory.cpp" diff --git a/playground/levelingsensor/SkinFactory.h b/playground/levelingsensor/SkinFactory.h new file mode 100644 index 00000000..6bb0f2fa --- /dev/null +++ b/playground/levelingsensor/SkinFactory.h @@ -0,0 +1,17 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#pragma once + +#include + +class SkinFactory : public QskSkinFactory +{ + Q_OBJECT + + public: + QStringList skinNames() const override; + QskSkin* createSkin( const QString& ) override; +}; diff --git a/playground/levelingsensor/main.cpp b/playground/levelingsensor/main.cpp new file mode 100644 index 00000000..0182edf1 --- /dev/null +++ b/playground/levelingsensor/main.cpp @@ -0,0 +1,213 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#include "SkinFactory.h" + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace +{ + void updateTickmarks( const Qt::Axis axis, const QskIntervalF& intervalA, + const QskIntervalF& intervalB, QskLevelingSensor* const sensor ) + { + QskScaleTickmarks tickmarks; + QVector< qreal > major; + QVector< qreal > medium; + QVector< qreal > minor; + + for ( const auto& interval : { intervalA, intervalB } ) + { + for ( int deg = ( int ) interval.lowerBound(); deg < ( int ) interval.upperBound(); + ++deg ) + { + if ( deg % 45 == 0 ) + { + major << deg; + } + if ( deg % 45 != 0 && deg % 5 == 0 ) + { + medium << deg; + } + if ( deg % 45 != 0 && deg % 5 != 0 ) + { + minor << deg; + } + } + } + + tickmarks.setMajorTicks( major ); + tickmarks.setMediumTicks( medium ); + tickmarks.setMinorTicks( minor ); + sensor->setTickmarks( axis, tickmarks ); + } + + void updateTickmarksLabels( const Qt::Axis axis, const QskIntervalF& intervalA, + const QskIntervalF& intervalB, int step, QskLevelingSensor* const sensor ) + { + QskLevelingSensor::TickmarksLabels labels; + for ( const auto& interval : { intervalA, intervalB } ) + { + for ( int deg = ( ( int ) interval.lowerBound() / step ) * step; + deg < ( int ) interval.upperBound(); deg += step ) + { + labels << QskLevelingSensor::TickmarksLabels::value_type{ ( float ) deg, + QString( deg > 0 ? "+" : "" ) + QString::number( deg ) }; + } + } + sensor->setTickmarksLabels( axis, labels ); + } + + Q_REQUIRED_RESULT QskSlider* makeAngleSlider( const Qt::Axis axis, + QskLevelingSensor* const sensor, double min = 0, double max = 90, QQuickItem* const parent = nullptr) + { + auto* const slider = new QskSlider( Qt::Horizontal, parent ); + slider->setMinimum( min ); + slider->setMaximum( max ); + slider->setValue( sensor->angles()[ axis ] ); + + QObject::connect(slider, &QskSlider::valueChanged, sensor, [axis, sensor](const qreal v){ + auto angles = sensor->angles(); + angles[axis] = v; + sensor->setAngle(angles); + }); + + return slider; + } + + Q_REQUIRED_RESULT QskSlider* makeTickmarksSlider( const Qt::Axis axis, + QskLevelingSensor* const sensor, int min, int max, + std::function< QskIntervalF( qreal ) > intervalA, + std::function< QskIntervalF( qreal ) > intervalB, QQuickItem* const parent = nullptr ) + { + auto* const slider = new QskSlider( Qt::Horizontal, parent ); + slider->setMinimum( min ); + slider->setMaximum( max ); + + QObject::connect( slider, &QskSlider::valueChanged, sensor, [ = ]( const qreal degree ) { + updateTickmarks( axis, intervalA( degree ), intervalB( degree ), sensor ); + updateTickmarksLabels( axis, intervalA( degree ), intervalB( degree ), 10, sensor ); + } ); + + return slider; + } + + Q_REQUIRED_RESULT QskSlider* makeRotationSlider( const Qt::Axis axis, + QskLevelingSensor* const sensor, const QskAspect::Subcontrol subControl, + QQuickItem* const parent = nullptr ) + { + auto* const slider = new QskSlider( Qt::Horizontal, parent ); + slider->setMinimum( -360 ); + slider->setMaximum( +360 ); + + QObject::connect( sensor, &QskLevelingSensor::subControlRotationChanged, slider, + [ = ]( const QskAspect::Subcontrol control, const QVector3D& degree ) { + if ( control == subControl ) + { + slider->setValue( degree[ axis ] ); + } + } ); + + QObject::connect( slider, &QskSlider::valueChanged, sensor, [ = ]( const qreal degree ) { + auto d = sensor->subControlRotation( subControl ); + d[ axis ] = degree; + sensor->setSubControlRotation( subControl, d ); + } ); + + return slider; + } + + class Window : public QskWindow + { + public: + Window() + { + auto* const root = new QskLinearBox( Qt::Horizontal, contentItem() ); + root->setSpacing( 8 ); + root->setMargins( 8 ); + auto* const left = new QskLinearBox( Qt::Vertical, root ); + auto* const right = new QskLinearBox( Qt::Vertical, root ); + auto* const sensor = new QskLevelingSensor( left ); + + auto linearIntervalA = []( const qreal degree ) -> QskIntervalF { + return { -degree, +degree }; + }; + auto linearIntervalB = []( const qreal /*degree*/ ) -> QskIntervalF { return {}; }; + + auto radialIntervalA = []( const qreal degree ) -> QskIntervalF { + return { -degree, +degree }; + }; + auto radialIntervalB = []( const qreal degree ) -> QskIntervalF { + return { 180 - degree, 180 + degree }; + }; + + ( void ) new QskTextLabel( "Angles XYZ", right ); + (void) makeAngleSlider(Qt::XAxis, sensor, 0, 45, right); + (void) makeAngleSlider(Qt::YAxis, sensor, 0, 45, right); + (void) makeAngleSlider(Qt::ZAxis, sensor, 0, 45, right); + + ( void ) new QskTextLabel( "Tickmarks XXZ", right ); + auto* const sliderTickmarksX = makeTickmarksSlider( + Qt::XAxis, sensor, 0, 90, linearIntervalA, linearIntervalB, right ); + auto* const sliderTickmarksY = makeTickmarksSlider( + Qt::YAxis, sensor, 0, 90, linearIntervalA, linearIntervalB, right ); + auto* const sliderTickmarksZ = makeTickmarksSlider( + Qt::ZAxis, sensor, 0, 90, radialIntervalA, radialIntervalB, right ); + + ( void ) new QskTextLabel( "Rotation X Plane", right ); + ( void ) makeRotationSlider( Qt::XAxis, sensor, QskLevelingSensor::TickmarksX, right ); + ( void ) makeRotationSlider( Qt::YAxis, sensor, QskLevelingSensor::TickmarksX, right ); + ( void ) makeRotationSlider( Qt::ZAxis, sensor, QskLevelingSensor::TickmarksX, right ); + ( void ) new QskTextLabel( "Rotation Y Plane", right ); + ( void ) makeRotationSlider( Qt::XAxis, sensor, QskLevelingSensor::TickmarksY, right ); + ( void ) makeRotationSlider( Qt::YAxis, sensor, QskLevelingSensor::TickmarksY, right ); + ( void ) makeRotationSlider( Qt::ZAxis, sensor, QskLevelingSensor::TickmarksY, right ); + ( void ) new QskTextLabel( "Rotation Z Plane", right ); + ( void ) makeRotationSlider( Qt::XAxis, sensor, QskLevelingSensor::TickmarksZ, right ); + ( void ) makeRotationSlider( Qt::YAxis, sensor, QskLevelingSensor::TickmarksZ, right ); + ( void ) makeRotationSlider( Qt::ZAxis, sensor, QskLevelingSensor::TickmarksZ, right ); + ( void ) new QskTextLabel( "Horizon", right ); + ( void ) makeRotationSlider( Qt::XAxis, sensor, QskLevelingSensor::Horizon, right ); + ( void ) makeRotationSlider( Qt::YAxis, sensor, QskLevelingSensor::Horizon, right ); + ( void ) makeRotationSlider( Qt::ZAxis, sensor, QskLevelingSensor::Horizon, right ); + + sliderTickmarksX->setValue( 15 ); + sliderTickmarksY->setValue( 15 ); + sliderTickmarksZ->setValue( 30 ); + } + }; +} + +int main( int argc, char** argv ) +{ +#ifdef ITEM_STATISTICS + QskObjectCounter counter( true ); +#endif + + qskSkinManager->setPluginPaths( QStringList() ); // no skin plugins + qskSkinManager->registerFactory( QStringLiteral( "sample" ), new SkinFactory() ); + + QGuiApplication app( argc, argv ); + + SkinnyShortcut::enable( SkinnyShortcut::AllShortcuts ); + + Window window; + window.showMaximized(); + + return app.exec(); +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b626e406..a3ddaac1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -440,6 +440,22 @@ list(APPEND SOURCES inputpanel/QskVirtualKeyboard.cpp ) +list(APPEND HEADERS + controls/QskLevelingSensor.h + controls/QskLevelingSensorSkinlet.h +) + +list(APPEND PRIVATE_HEADERS + controls/private/QskSGNodeUtility.h + controls/private/QskLevelingSensorNodes.h + controls/private/QskLevelingSensorUtility.h +) + +list(APPEND SOURCES + controls/private/QskLevelingSensor.cpp + controls/private/QskLevelingSensorSkinlet.cpp +) + if(ENABLE_PINYIN) list(APPEND HEADERS inputpanel/QskPinyinTextPredictor.h) list(APPEND SOURCES inputpanel/QskPinyinTextPredictor.cpp) @@ -499,7 +515,8 @@ set_target_properties(${target} list(TRANSFORM HEADERS PREPEND "${CMAKE_CURRENT_LIST_DIR}/") set_target_properties(${target} PROPERTIES PUBLIC_HEADER "${HEADERS}") + set_target_properties(${target} PROPERTIES VERSION ${CMAKE_PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} ) -install(TARGETS ${target} ) +install(TARGETS ${target} ) \ No newline at end of file diff --git a/src/controls/QskLevelingSensor.h b/src/controls/QskLevelingSensor.h new file mode 100644 index 00000000..7ac11653 --- /dev/null +++ b/src/controls/QskLevelingSensor.h @@ -0,0 +1,49 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#ifndef QSK_LEVELING_SENSOR_H +#define QSK_LEVELING_SENSOR_H + +#include +#include +#include + +class QSK_EXPORT QskLevelingSensor : 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 QskLevelingSensor( QQuickItem* parent = nullptr ); + ~QskLevelingSensor(); + + public Q_SLOTS: + void setTickmarks( Qt::Axis axis, Tickmarks tickmarks ); + void setTickmarksLabels( Qt::Axis axis, TickmarksLabels labels ); + void setAngle( const QVector3D& degrees ); + void setSubControlRotation( QskAspect::Subcontrol subControl, const QVector3D& degrees ); + + Q_SIGNALS: + void anglesChanged( const QVector3D& degree ); + void subControlRotationChanged( QskAspect::Subcontrol subControl, const QVector3D& degrees ); + + public: + Q_REQUIRED_RESULT Tickmarks tickmarks( Qt::Axis axis ) const; + Q_REQUIRED_RESULT TickmarksLabels tickmarkLabels( Qt::Axis axis ) const; + Q_REQUIRED_RESULT QVector3D angles() const; + Q_REQUIRED_RESULT QVector3D subControlRotation( QskAspect::Subcontrol subControl ) const; + + private: + class PrivateData; + std::unique_ptr< PrivateData > m_data; +}; + +#endif \ No newline at end of file diff --git a/src/controls/QskLevelingSensorSkinlet.h b/src/controls/QskLevelingSensorSkinlet.h new file mode 100644 index 00000000..2b53c0d7 --- /dev/null +++ b/src/controls/QskLevelingSensorSkinlet.h @@ -0,0 +1,60 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#ifndef QSK_LEVELING_SENSOR_SKINLET_H +#define QSK_LEVELING_SENSOR_SKINLET_H + +#include +#include + +class QskLevelingSensor; + +class QSK_EXPORT QskLevelingSensorSkinlet : public QskSkinlet +{ + Q_GADGET + + using Inherited = QskSkinlet; + + public: + enum NodeRole + { + OuterDisk, + Horizon, + HorizonClip, + TickmarksX, + TickmarksXLabels, + TickmarksY, + TickmarksYLabels, + TickmarksZ, + TickmarksZLabels, + TriangleBar, + TickmarksYIndicator, + RoleCount + }; + + Q_INVOKABLE QskLevelingSensorSkinlet( QskSkin* skin = nullptr ); + ~QskLevelingSensorSkinlet() override = default; + + Q_REQUIRED_RESULT static float outerRadius( const QskSkinnable* const skinnable ); + Q_REQUIRED_RESULT static float innerRadius( const QskSkinnable* const skinnable ); + Q_REQUIRED_RESULT static 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 QskLevelingSensor* sensor, const QRectF& contentsRect ) const; + + template< NodeRole > + Q_REQUIRED_RESULT QSGNode* updateSubNode( + const QskLevelingSensor* sensor, quint8 nodeRole, QSGNode* node ) const; +}; + +#endif \ No newline at end of file diff --git a/src/controls/private/QskLevelingSensor.cpp b/src/controls/private/QskLevelingSensor.cpp new file mode 100644 index 00000000..1be277a3 --- /dev/null +++ b/src/controls/private/QskLevelingSensor.cpp @@ -0,0 +1,141 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#include +#include +#include + +#include +#include + +#include +#include + +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 ( !qskFuzzyCompare( dst, src ) ) + { + dst = src; + return true; + } + return false; + } + + template<> + bool compareExchange< QVector3D >( QVector3D& dst, const QVector3D& src ) + { + auto dirty = false; + dirty |= compareExchange( dst[ Qt::XAxis ], src[ Qt::XAxis ] ); + dirty |= compareExchange( dst[ Qt::YAxis ], src[ Qt::YAxis ] ); + dirty |= compareExchange( dst[ Qt::ZAxis ], src[ Qt::ZAxis ] ); + return dirty; + } + + inline bool isAxis( const Qt::Axis axis ) + { + return axis == Qt::XAxis || axis == Qt::YAxis || axis == Qt::ZAxis; + } +} + +QSK_SUBCONTROL( QskLevelingSensor, OuterDisk ) +QSK_SUBCONTROL( QskLevelingSensor, Horizon ) +QSK_SUBCONTROL( QskLevelingSensor, TickmarksX ) +QSK_SUBCONTROL( QskLevelingSensor, TickmarksXLabels ) +QSK_SUBCONTROL( QskLevelingSensor, TickmarksY ) +QSK_SUBCONTROL( QskLevelingSensor, TickmarksYLabels ) +QSK_SUBCONTROL( QskLevelingSensor, TickmarksZ ) +QSK_SUBCONTROL( QskLevelingSensor, TickmarksZLabels ) + +#define RETURN_IF_FALSE( expr ) \ + if ( !( expr ) ) \ + return; + +class QskLevelingSensor::PrivateData +{ + public: + QVector3D m_angles = { 45, 45, 45 }; + Tickmarks m_tickmarks[ 3 ]; + TickmarksLabels m_tickmarksLabels[ 3 ]; + std::unordered_map< QskAspect::Subcontrol, QVector3D > m_subControlRotation; +}; + +QskLevelingSensor::QskLevelingSensor( QQuickItem* const parent ) + : Inherited( parent ) + , m_data( new QskLevelingSensor::PrivateData ) +{ +} + +QskLevelingSensor::~QskLevelingSensor() = default; + +void QskLevelingSensor::setTickmarks( const Qt::Axis axis, QskScaleTickmarks tickmarks ) +{ + RETURN_IF_FALSE( isAxis( axis ) ); + m_data->m_tickmarks[ axis ] = std::move( tickmarks ); + update(); +} + +void QskLevelingSensor::setTickmarksLabels( const Qt::Axis axis, TickmarksLabels labels ) +{ + RETURN_IF_FALSE( isAxis( axis ) ); + m_data->m_tickmarksLabels[ axis ] = std::move( labels ); + update(); +} + +void QskLevelingSensor::setAngle( const QVector3D& degrees ) +{ + if ( compareExchange( m_data->m_angles, degrees ) ) + { + update(); + Q_EMIT anglesChanged( m_data->m_angles ); + } +} + +QskScaleTickmarks QskLevelingSensor::tickmarks( const Qt::Axis axis ) const +{ + return isAxis( axis ) ? m_data->m_tickmarks[ axis ] : QskScaleTickmarks{}; +} + +QskLevelingSensor::TickmarksLabels QskLevelingSensor::tickmarkLabels( const Qt::Axis axis ) const +{ + return isAxis( axis ) ? m_data->m_tickmarksLabels[ axis ] + : QskLevelingSensor::TickmarksLabels{}; +} + +QVector3D QskLevelingSensor::angles() const +{ + return m_data->m_angles; +} + +QVector3D QskLevelingSensor::subControlRotation( const QskAspect::Subcontrol subControl ) const +{ + const auto found = m_data->m_subControlRotation.find( subControl ); + return found != m_data->m_subControlRotation.end() ? found->second : QVector3D{}; +} + +void QskLevelingSensor::setSubControlRotation( + const QskAspect::Subcontrol subControl, const QVector3D& degrees ) +{ + if ( compareExchange( m_data->m_subControlRotation[ subControl ], degrees ) ) + { + update(); + Q_EMIT subControlRotationChanged( subControl, degrees ); + } +} + +#include "moc_QskLevelingSensor.cpp" \ No newline at end of file diff --git a/src/controls/private/QskLevelingSensorNodes.h b/src/controls/private/QskLevelingSensorNodes.h new file mode 100644 index 00000000..50e713b2 --- /dev/null +++ b/src/controls/private/QskLevelingSensorNodes.h @@ -0,0 +1,238 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#ifndef QSK_LEVELING_SENSOR_NODES_H +#define QSK_LEVELING_SENSOR_NODES_H + +#include "QskLevelingSensorUtility.h" + +#include +#include +#include +#include +#include +#include + +#include +#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 |= compareExchange( m_r1, r1 ); + dirty |= compareExchange( m_r2, r2 ); + dirty |= compareExchange( 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 RadialClipNode final : public QSGClipNode +{ + public: + RadialClipNode() + : 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 |= compareExchange( m_radius, radius ); + dirty |= compareExchange( m_cx, cx ); + dirty |= compareExchange( m_cy, cy ); + dirty |= compareExchange( 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::setClipRect; + using QSGClipNode::setIsRectangular; + + 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 = {} ) + { + const auto count = labels.count(); + + for ( int i = childCount(); i > count; --i ) + { + removeChildNode( lastChild() ); + } + + for ( int i = childCount(); i < count; ++i ) + { + 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; + } +}; + +#endif \ No newline at end of file diff --git a/src/controls/private/QskLevelingSensorSkinlet.cpp b/src/controls/private/QskLevelingSensorSkinlet.cpp new file mode 100644 index 00000000..301014df --- /dev/null +++ b/src/controls/private/QskLevelingSensorSkinlet.cpp @@ -0,0 +1,492 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#include "QskLevelingSensorNodes.h" +#include "QskLevelingSensorUtility.h" +#include "QskSGNodeUtility.h" +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace +{ + template< typename T > + struct State; + + template<> + struct State< QskLevelingSensor > + { + qreal r1 = 0.0; + qreal r2 = 0.0; + qreal cX = 0.0; + qreal cY = 0.0; + qreal sX = 0.0; + qreal sY = 0.0; + qreal sZ = 0.0; + + explicit State( const QskLevelingSensor* const sensor ) + : r1( QskLevelingSensorSkinlet::innerRadius( sensor ) ) + , r2( QskLevelingSensorSkinlet::outerRadius( sensor ) ) + , cX( QskLevelingSensorSkinlet::center( sensor ).x() ) + , cY( QskLevelingSensorSkinlet::center( sensor ).y() ) + , sX( r1 / sensor->angles().x() ) + , sY( r1 / sensor->angles().y() ) + , sZ( r1 / sensor->angles().z() ) + { + } + + Q_REQUIRED_RESULT QVector2D center() const noexcept + { + return { ( float ) cX, ( float ) cY }; + } + + Q_REQUIRED_RESULT QVector3D scale() const noexcept + { + return { ( float ) sX, ( float ) sY, ( float ) sZ }; + } + }; + + template<> + struct State< QskAspect::Subcontrol > : State< QskLevelingSensor > + { + qreal rX = 0.0; + qreal rY = 0.0; + qreal rZ = 0.0; + qreal tX = 0.0; + qreal tY = 0.0; + qreal tZ = 0.0; + + State( const QskLevelingSensor* const sensor, QskAspect::Subcontrol subcontrol ) + : State< QskLevelingSensor >( sensor ) + , rX( sensor->subControlRotation( subcontrol ).x() ) + , rY( sensor->subControlRotation( subcontrol ).y() ) + , rZ( sensor->subControlRotation( subcontrol ).z() ) + , tX( rY * sX ) + , tY( rX * sY ) + , tZ( 0.0 ) + { + } + + Q_REQUIRED_RESULT QVector3D translation() const noexcept + { + return { ( float ) tX, ( float ) tY, ( float ) tZ }; + } + + Q_REQUIRED_RESULT QMatrix4x4 matrix() const noexcept + { + return matrix_deg( rZ, cX, cY ); + } + }; + + Q_REQUIRED_RESULT std::optional< std::pair< qreal, qreal > > minmax( + const QVector< qreal >& tickmarks ) + { + if ( tickmarks.empty() ) + { + return {}; + } + + const auto [ pmin, pmax ] = std::minmax_element( tickmarks.begin(), tickmarks.end() ); + return std::make_pair( *pmin, *pmax ); + } + + Q_REQUIRED_RESULT std::optional< std::pair< qreal, qreal > > minmax( + const QskScaleTickmarks& tickmarks ) + { + if ( tickmarks.tickCount() == 0 ) + { + return {}; + } + + const auto majorTicks = tickmarks.majorTicks(); + const auto mediumTicks = tickmarks.mediumTicks(); + const auto minorTicks = tickmarks.minorTicks(); + + const auto item = !majorTicks.empty() ? majorTicks[ 0 ] + : !mediumTicks.empty() ? mediumTicks[ 0 ] + : !minorTicks.empty() ? minorTicks[ 0 ] + : std::numeric_limits< qreal >::quiet_NaN(); + + Q_ASSERT_X( !std::isnan( item ), __func__, "NaN should not be possible!" ); + + std::array< qreal, 6 > local = { item }; + + if ( const auto opt = minmax( tickmarks.majorTicks() ) ) + { + local[ 0 ] = opt->first; + local[ 1 ] = opt->second; + } + if ( const auto opt = minmax( tickmarks.mediumTicks() ) ) + { + local[ 2 ] = opt->first; + local[ 3 ] = opt->second; + } + if ( const auto opt = minmax( tickmarks.minorTicks() ) ) + { + local[ 4 ] = opt->first; + local[ 5 ] = opt->second; + } + + const auto [ pmin, pmax ] = std::minmax_element( local.begin(), local.end() ); + return std::make_pair( *pmin, *pmax ); + } + +} + +using Q = QskLevelingSensor; +using R = QskLevelingSensorSkinlet::NodeRole; + +using namespace QskSGNode; + +template< typename Root, typename... Children > +Q_REQUIRED_RESULT inline Root* ensureNodes( QSGNode* root = nullptr ) +{ + return ensureNodes< AppendMode::Recursive, Root, Children... >( root ); +} + +float QskLevelingSensorSkinlet::outerRadius( const QskSkinnable* const skinnable ) +{ + const auto* const sensor = static_cast< const Q* >( skinnable ); + const auto contentsRect = sensor->contentsRect(); + return 0.5f * qMin( contentsRect.width(), contentsRect.height() ); +} + +float QskLevelingSensorSkinlet::innerRadius( const QskSkinnable* const skinnable ) +{ + const auto scale = skinnable->strutSizeHint( Q::Horizon ); + return outerRadius( skinnable ) * scale.width(); +} + +QPointF QskLevelingSensorSkinlet::center( const QskSkinnable* const skinnable ) +{ + const auto* const sensor = static_cast< const Q* >( skinnable ); + return sensor->contentsRect().center(); +} + +QskLevelingSensorSkinlet::QskLevelingSensorSkinlet( QskSkin* skin ) + : Inherited( skin ) +{ + setNodeRoles( { + OuterDisk, + Horizon, + HorizonClip, + TickmarksX, + TickmarksXLabels, + TickmarksY, + TickmarksYLabels, + TickmarksZ, + TickmarksZLabels, + } ); +} + +template<> +Q_REQUIRED_RESULT QRectF QskLevelingSensorSkinlet::subControlRect< R::OuterDisk >( + const QskLevelingSensor* const sensor, const QRectF& contentsRect ) const +{ + const auto radius = outerRadius( 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 QskLevelingSensorSkinlet::subControlRect< R::Horizon >( + const QskLevelingSensor* const sensor, const QRectF& contentsRect ) const +{ + Q_UNUSED( contentsRect ) + const auto scale = sensor->strutSizeHint( Q::Horizon ); + const auto width = 2 * innerRadius( sensor ) * scale.width(); + const auto height = width; + return { center( sensor ).x() - width / 2, center( sensor ).y() - height / 2, width, height }; +} + +QRectF QskLevelingSensorSkinlet::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<> +QSGNode* QskLevelingSensorSkinlet::updateSubNode< R::OuterDisk >( + const QskLevelingSensor* const sensor, const quint8 nodeRole, QSGNode* const node ) const +{ + Q_UNUSED( nodeRole ) + + 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 = ensureNodes< QSGTransformNode, QskBoxNode >( node ); + auto* const bNode = static_cast< QskBoxNode* >( root->firstChild() ); + + const auto size = outerRadius( 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 = 0.0; + + const auto matrix = + matrix_deg( 0.0, cX, cY ) * matrix_deg( rZ, 0, 0 ) * matrix_deg( 0.0, -size, -size ); + + root->setMatrix( matrix ); + return root; +} + +template<> +QSGNode* QskLevelingSensorSkinlet::updateSubNode< R::Horizon >( + const QskLevelingSensor* const sensor, const quint8 nodeRole, QSGNode* const node ) const +{ + Q_UNUSED( nodeRole ) + + const auto subControl = Q::Horizon; + const State< QskAspect::Subcontrol > state( sensor, subControl ); + + const auto dY = 2 * sensor->angles().y(); + const auto pY = qBound( 0.0, 0.5 + ( -state.rX / dY ), 1.0 ); + + const auto shape = QskBoxShapeMetrics{ state.r1 }; + const auto metrics = sensor->boxBorderMetricsHint( subControl ); + const auto colors = sensor->boxBorderColorsHint( subControl ); + + auto gradient = sensor->gradientHint( Q::Horizon ); + gradient.setDirection( QskGradient::Linear ); + gradient.setLinearDirection( Qt::Vertical ); + gradient.setStops( { { 0.0, gradient.startColor() }, { pY, gradient.startColor() }, + { pY, gradient.endColor() }, { 1.0, gradient.endColor() } } ); + + auto* const tNode = ensureNodes< QSGTransformNode, QskBoxNode >( node ); + auto* const boxNode = static_cast< QskBoxNode* >( tNode->firstChild() ); + updateBoxNode( + sensor, boxNode, { 0, 0, 2 * state.r1, 2 * state.r1 }, shape, metrics, colors, gradient ); + + const auto matrix = matrix_deg( 0, state.cX, state.cY ) * matrix_deg( state.rZ, 0, 0 ) * + matrix_deg( 0, -state.r1, -state.r1 ); + + tNode->setMatrix( matrix ); + return tNode; +} + +Q_REQUIRED_RESULT QSGNode* updateLinearTickmarksNode( const QskLevelingSensor* const sensor, + const QskAspect::Subcontrol subControl, const QskScaleTickmarks& tickmarks, + const Qt::Orientation orientation, QSGNode* const node ) +{ + const auto state = State< QskAspect::Subcontrol >( sensor, subControl ); + const auto color = sensor->color( subControl ); + const auto scale = sensor->strutSizeHint( subControl ); + const auto width = state.r1 * scale.width(); + const auto height = state.r1 * scale.height(); + const auto alignment = sensor->alignmentHint( subControl ); + + const auto opt = minmax( tickmarks ); + const auto min = opt ? opt->first : 0.0; + const auto max = opt ? opt->second : 0.0; + const auto interval = QskIntervalF{ min, max }; + + const auto rect = + orientation == Qt::Horizontal + ? QRectF{ QPointF{ min * state.sX, -height }, QPointF{ max * state.sX, +height } } + : QRectF{ QPointF{ -width, min * state.sY }, QPointF{ +width, max * state.sY } }; + + const auto translation = QPointF{ state.tX + state.cX, state.tY + state.cY }; + + auto* const cNode = ensureNodes< RadialClipNode, QSGTransformNode, QskTickmarksNode >( node ); + auto* const tNode = static_cast< QSGTransformNode* >( cNode->firstChild() ); + auto* const qNode = static_cast< QskTickmarksNode* >( tNode->firstChild() ); + + cNode->setGeometryProperties( state.r1, state.cX, state.cY ); + tNode->setMatrix( matrix_deg( state.rZ, translation.x(), translation.y() ) ); + qNode->update( color, rect, interval, tickmarks, 1, orientation, alignment ); + + return cNode; +} + +template<> +QSGNode* QskLevelingSensorSkinlet::updateSubNode< R::TickmarksX >( + const QskLevelingSensor* const sensor, const quint8 nodeRole, QSGNode* const node ) const +{ + Q_UNUSED( nodeRole ) + return updateLinearTickmarksNode( + sensor, Q::TickmarksX, sensor->tickmarks( Qt::XAxis ), Qt::Horizontal, node ); +} + +template<> +QSGNode* QskLevelingSensorSkinlet::updateSubNode< R::TickmarksY >( + const QskLevelingSensor* const sensor, const quint8 nodeRole, QSGNode* const node ) const +{ + Q_UNUSED( nodeRole ) + return updateLinearTickmarksNode( + sensor, Q::TickmarksY, sensor->tickmarks( Qt::YAxis ), Qt::Vertical, node ); +} + +template<> +QSGNode* QskLevelingSensorSkinlet::updateSubNode< R::TickmarksZ >( + const QskLevelingSensor* const sensor, const quint8 nodeRole, QSGNode* const node ) const +{ + Q_UNUSED( nodeRole ) + const auto subControl = Q::TickmarksZ; + const State< QskAspect::Subcontrol > state( sensor, subControl ); + const auto color = sensor->color( subControl ); + + const auto r3 = qvariant_cast< QVector3D >( sensor->effectiveSkinHint( subControl ) ) * + ( state.r2 - state.r1 ) + + QVector3D{ ( float ) state.r1, ( float ) state.r1, ( float ) state.r1 }; + + auto* const transform = ensureNodes< QSGTransformNode, RadialTickmarksNode >( node ); + auto* const tickmarksNode = static_cast< RadialTickmarksNode* >( transform->firstChild() ); + tickmarksNode->setMaterialProperties( color ); + tickmarksNode->setGeometryProperties( sensor->tickmarks( Qt::ZAxis ), state.r1, r3 ); + + const auto matrix = matrix_deg( state.rZ, state.cX, state.cY ); + transform->setMatrix( matrix ); + return transform; +} + +template<> +QSGNode* QskLevelingSensorSkinlet::updateSubNode< R::TickmarksXLabels >( + const QskLevelingSensor* const sensor, const quint8 nodeRole, QSGNode* const node ) const +{ + Q_UNUSED( nodeRole ) + const auto subControl = Q::TickmarksXLabels; + const State< QskAspect::Subcontrol > state( sensor, subControl ); + const auto r3 = state.r1 * sensor->strutSizeHint( Q::TickmarksX ).height(); + const auto dX = qFastCos( qDegreesToRadians( 90 + state.rZ ) ) * r3; + const auto dY = qFastSin( qDegreesToRadians( 90 + state.rZ ) ) * r3; + + auto* const cNode = + ensureNodes< RadialClipNode, QSGTransformNode, LinearTickmarksLabelsNode >( node ); + auto* const tNode = static_cast< QSGTransformNode* >( cNode->firstChild() ); + auto* const lNode = static_cast< LinearTickmarksLabelsNode* >( tNode->firstChild() ); + cNode->setGeometryProperties( state.r1, state.cX, state.cY ); + tNode->setMatrix( matrix_deg( state.rZ, state.cX + state.tX + dX, state.cY + state.tY + dY ) ); + lNode->update( + sensor, subControl, sensor->tickmarkLabels( Qt::XAxis ), { state.scale().x(), 0.0 } ); + return cNode; +} + +template<> +QSGNode* QskLevelingSensorSkinlet::updateSubNode< R::TickmarksYLabels >( + const QskLevelingSensor* const sensor, const quint8 nodeRole, QSGNode* const node ) const +{ + Q_UNUSED( nodeRole ) + const auto subControl = Q::TickmarksYLabels; + const State< QskAspect::Subcontrol > state( sensor, subControl ); + const auto r3 = state.r1 * sensor->strutSizeHint( Q::TickmarksY ).width(); + const auto dX = qFastCos( qDegreesToRadians( state.rZ ) ) * r3; + const auto dY = qFastSin( qDegreesToRadians( state.rZ ) ) * r3; + + auto* const cNode = + ensureNodes< RadialClipNode, QSGTransformNode, LinearTickmarksLabelsNode >( node ); + auto* const tNode = static_cast< QSGTransformNode* >( cNode->firstChild() ); + auto* const lNode = static_cast< LinearTickmarksLabelsNode* >( tNode->firstChild() ); + cNode->setGeometryProperties( state.r1, state.cX, state.cY ); + tNode->setMatrix( matrix_deg( state.rZ, state.cX + state.tX + dX, state.cY + state.tY + dY ) ); + lNode->update( + sensor, subControl, sensor->tickmarkLabels( Qt::YAxis ), { 0.0, state.scale().y() } ); + return cNode; +} + +template<> +QSGNode* QskLevelingSensorSkinlet::updateSubNode< R::TickmarksZLabels >( + const QskLevelingSensor* const sensor, const quint8 nodeRole, QSGNode* const node ) const +{ + Q_UNUSED( nodeRole ) + const auto subControl = Q::TickmarksZLabels; + const State< QskAspect::Subcontrol > state( sensor, subControl ); + auto* const tNode = ensureNodes< QSGTransformNode, RadialTickmarksLabelsNode >( node ); + auto* const lNode = static_cast< RadialTickmarksLabelsNode* >( tNode->firstChild() ); + const auto r3 = static_cast< float >( state.r1 * sensor->strutSizeHint( subControl ).width() ); + lNode->update( sensor, subControl, sensor->tickmarkLabels( Qt::ZAxis ), { r3, r3 } ); + tNode->setMatrix( state.matrix() ); + return tNode; +} + +template<> +QSGNode* QskLevelingSensorSkinlet::updateSubNode< R::HorizonClip >( + const QskLevelingSensor* const sensor, const quint8 nodeRole, QSGNode* const node ) const +{ + Q_UNUSED( nodeRole ) + const auto cX = center( sensor ).x(); + const auto cY = center( sensor ).y(); + const auto r1 = innerRadius( sensor ); + + auto* const clipNode = ensureNodes< RadialClipNode >( node ); + clipNode->setGeometryProperties( r1, cX, cY ); + return clipNode; +} + +QSGNode* QskLevelingSensorSkinlet::updateSubNode( + const QskSkinnable* const skinnable, const quint8 nodeRole, QSGNode* const node ) const +{ + const auto* const sensor = static_cast< const Q* >( skinnable ); + + 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" diff --git a/src/controls/private/QskLevelingSensorUtility.h b/src/controls/private/QskLevelingSensorUtility.h new file mode 100644 index 00000000..faecbca2 --- /dev/null +++ b/src/controls/private/QskLevelingSensorUtility.h @@ -0,0 +1,81 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#ifndef QSK_LEVELING_SENSOR_UTILITY_H +#define QSK_LEVELING_SENSOR_UTILITY_H + +#include +#include +#include + +#include +#include + +Q_REQUIRED_RESULT inline QMatrix4x4 matrix_deg( float rZ = 0.0f, float tX = 0.0f, float tY = 0.0f ) +{ + QTransform transform; + transform.translate( tX, tY ); + transform.rotate( rZ, Qt::ZAxis ); + return transform; +} + +template< typename T > +Q_REQUIRED_RESULT inline bool compareExchange( T& dst, const T& src ) +{ + if ( dst != src ) + { + dst = src; + return true; + } + return false; +} + +template<> +Q_REQUIRED_RESULT inline bool compareExchange< float >( float& dst, const float& src ) +{ + if ( !qskFuzzyCompare( dst, src ) ) + { + dst = src; + return true; + } + return false; +} + +template<> +Q_REQUIRED_RESULT inline bool compareExchange< qreal >( qreal& dst, const qreal& src ) +{ + if ( !qskFuzzyCompare( dst, src ) ) + { + dst = src; + return true; + } + return false; +} + +Q_REQUIRED_RESULT 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; +} + +#endif \ No newline at end of file diff --git a/src/controls/private/QskSGNodeUtility.h b/src/controls/private/QskSGNodeUtility.h new file mode 100644 index 00000000..8ff007bb --- /dev/null +++ b/src/controls/private/QskSGNodeUtility.h @@ -0,0 +1,47 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#ifndef QSK_SGNODE_UTILITY_H +#define QSK_SGNODE_UTILITY_H + +#include + +namespace QskSGNode +{ + enum AppendMode + { + Sequential, + Recursive + }; + + template< AppendMode mode, typename Root, typename... Children > + Q_REQUIRED_RESULT inline Root* ensureNodes( QSGNode* root = nullptr ) + { + if ( root == nullptr ) + { + root = new Root; + } + + if constexpr ( mode == Recursive ) + { + QSGNode* current = root; + Q_UNUSED( current ) + ( + [ ¤t ]( QSGNode* const child ) mutable { + current->appendChildNode( child ); + current = child; + }( new Children ), + ... ); + } + else + { + ( root->appendChildNode( new Children ), ... ); + } + + return static_cast< Root* >( root ); + } +} + +#endif \ No newline at end of file