diff --git a/playground/CMakeLists.txt b/playground/CMakeLists.txt index 7fc3449a..19ac9d4e 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(nodes) if (BUILD_INPUTCONTEXT) add_subdirectory(inputpanel) diff --git a/playground/nodes/CMakeLists.txt b/playground/nodes/CMakeLists.txt new file mode 100644 index 00000000..9583d592 --- /dev/null +++ b/playground/nodes/CMakeLists.txt @@ -0,0 +1,12 @@ +############################################################################ +# QSkinny - Copyright (C) 2016 Uwe Rathmann +# SPDX-License-Identifier: BSD-3-Clause +############################################################################ + +qsk_add_example(nodes + main.cpp + RadialNodes.h + RadialNodes.cpp + RadialNodesSkinlet.h + RadialNodesSkinlet.cpp +) diff --git a/playground/nodes/RadialNodes.cpp b/playground/nodes/RadialNodes.cpp new file mode 100644 index 00000000..fc2bf160 --- /dev/null +++ b/playground/nodes/RadialNodes.cpp @@ -0,0 +1,23 @@ +#include "RadialNodes.h" + +QSK_SUBCONTROL(RadialTickmarks, Lines) + +RadialTickmarks::RadialTickmarks( QQuickItem* const parent ) : QskControl( parent ) +{ + // TODO move into your skin + setColor(Lines, Qt::red); + setStrutSizeHint( Lines, 2, 10 ); +} + +void RadialTickmarks::setTickmarks( const QskScaleTickmarks& tickmarks ) +{ + m_tickmarks = tickmarks; + update(); +} + +QskScaleTickmarks RadialTickmarks::tickmarks() const +{ + return m_tickmarks; +} + +#include "moc_RadialNodes.cpp" \ No newline at end of file diff --git a/playground/nodes/RadialNodes.h b/playground/nodes/RadialNodes.h new file mode 100644 index 00000000..e5f50aab --- /dev/null +++ b/playground/nodes/RadialNodes.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +class RadialTickmarks : public QskControl +{ +public: + QSK_SUBCONTROLS(Lines) + explicit RadialTickmarks( QQuickItem* parent = nullptr ); + void setTickmarks(const QskScaleTickmarks& tickmarks); + QskScaleTickmarks tickmarks() const; + +private: + QskScaleTickmarks m_tickmarks; +}; \ No newline at end of file diff --git a/playground/nodes/RadialNodesSkinlet.cpp b/playground/nodes/RadialNodesSkinlet.cpp new file mode 100644 index 00000000..6458ff9e --- /dev/null +++ b/playground/nodes/RadialNodesSkinlet.cpp @@ -0,0 +1,217 @@ +#include "RadialNodesSkinlet.h" +#include "RadialNodes.h" + +#include +#include +#include + +#include +#include +#include + +namespace +{ + 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; + } + + class QskRadialTickmarksNode final : public QSGGeometryNode + { + public: + QskRadialTickmarksNode() + : 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 QVector2D& r1 = { 1.0f, 1.0f }, const QVector3D& height = { 1.0f, 1.0f, 1.0f }, + const QVector2D& center = {}, Qt::Alignment alignment = Qt::AlignVCenter, + float lineWidth = 1.0 ) + { + auto dirty = false; + + if ( dirty |= ( m_geometry.lineWidth() != lineWidth ) ) + { + m_geometry.setLineWidth( lineWidth ); + } + + dirty |= compareExchange( m_radius, r1 ); + dirty |= compareExchange( m_height, height ); + dirty |= compareExchange( m_center, center ); + dirty |= compareExchange( m_alignment, alignment ); + dirty |= compareExchange( m_tickmarksHash, tickmarks.hash() ); + + if ( dirty ) + { + update( tickmarks ); + } + } + + private: + void update( const QskScaleTickmarks& tickmarks ) + { + using T = QskScaleTickmarks::TickType; + + if ( m_geometry.vertexCount() != tickmarks.tickCount() ) + { + m_geometry.allocate( tickmarks.tickCount() * 2 ); + } + + QVector2D r1[ 3 ]; + QVector2D r2[ 3 ]; + + if ( m_alignment.testFlag( Qt::AlignBottom ) || m_alignment.testFlag( Qt::AlignLeft )) + { + for ( int i : { T::MinorTick, T::MediumTick, T::MajorTick } ) + { + r1[ i ] = m_radius; + r2[ i ] = m_radius.normalized() * ( m_radius.length() + m_height[ i ] ); + } + } + else if ( m_alignment.testFlag( Qt::AlignTop ) || m_alignment.testFlag( Qt::AlignRight )) + { + for ( int i : { T::MinorTick, T::MediumTick, T::MajorTick } ) + { + r1[ i ] = m_radius.normalized() * ( m_radius.length() - m_height[ i ] ); + r2[ i ] = m_radius; + } + } + else + { + for ( int i : { T::MinorTick, T::MediumTick, T::MajorTick } ) + { + r1[ i ] = m_radius.normalized() * ( m_radius.length() - m_height[ i ] / 2 ); + r2[ i ] = m_radius.normalized() * ( m_radius.length() + m_height[ i ] / 2 ); + } + } + + auto* vertexData = m_geometry.vertexDataAsPoint2D(); + + 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 rad = qDegreesToRadians( tick ); + + const auto p1 = + r1[ i ] * QVector2D{ ( float ) qFastCos( rad ), ( float ) qFastSin( rad ) }; + + const auto p2 = + r2[ i ] * QVector2D{ ( float ) qFastCos( rad ), ( float ) qFastSin( rad ) }; + + vertexData[ 0 ].set( p1.x() + m_center.x(), p1.y() + m_center.y() ); + vertexData[ 1 ].set( p2.x() + m_center.x(), p2.y() + m_center.y() ); + vertexData += 2; + } + } + + m_geometry.markVertexDataDirty(); + markDirty( QSGNode::DirtyGeometry ); + } + + QSGGeometry m_geometry; + QSGFlatColorMaterial m_material; + + QVector2D m_radius = { 1.0f, 1.0f }; + QVector2D m_center = { 0.0f, 0.0f }; + QVector3D m_height = { 1.0f, 1.0f, 1.0f }; + Qt::Alignment m_alignment = Qt::AlignVCenter; + QskHashValue m_tickmarksHash{ 0 }; + }; +} + +RadialTickmarksSkinlet::RadialTickmarksSkinlet( QskSkin* const skin ) + : QskSkinlet( skin ) +{ + setNodeRoles( { Lines } ); +} + +QRectF RadialTickmarksSkinlet::subControlRect( const QskSkinnable* const skinnable, + const QRectF& contentsRect, const QskAspect::Subcontrol subControl ) const +{ + if ( subControl == RadialTickmarks::Lines ) + { + const auto a = skinnable->strutSizeHint(RadialTickmarks::Lines).height() / 2; + return contentsRect.adjusted(+a, +a, -a, -a); + } + return QskSkinlet::subControlRect( skinnable, contentsRect, subControl ); +} + +QSGNode* RadialTickmarksSkinlet::updateSubNode( + const QskSkinnable* const skinnable, const quint8 nodeRole, QSGNode* const node ) const +{ + using Q = RadialTickmarks; + + switch ( static_cast< NodeRole >( nodeRole ) ) + { + case Lines: { + const auto* const control = static_cast< const Q* >( skinnable ); + const auto subControlRect = control->subControlContentsRect( Q::Lines ); + const auto alignment = control->alignmentHint( Q::Lines ); + const auto size = control->strutSizeHint( Q::Lines ); + + const auto rX = static_cast< float >( subControlRect.width() / 2 ); + const auto rY = static_cast< float >( subControlRect.height() / 2 ); + const auto radius = QVector2D{ rX, rY }; + const auto height = + QVector3D{ + ( float ) size.height() * 0.50f, + ( float ) size.height() * 0.75f, ( float ) size.height() * 1.0f }; + const auto c = QVector2D{ ( float ) subControlRect.center().x(), + ( float ) subControlRect.center().y() }; + + auto* const root = QskSGNode::ensureNode< QskRadialTickmarksNode >( node ); + root->setMaterialProperties( skinnable->color( Q::Lines ) ); + root->setGeometryProperties( control->tickmarks(), radius, height, c, alignment, size.width() ); + return root; + } + default: + return QskSkinlet::updateSubNode( skinnable, nodeRole, node ); + } +} + +#include "moc_RadialNodesSkinlet.cpp" \ No newline at end of file diff --git a/playground/nodes/RadialNodesSkinlet.h b/playground/nodes/RadialNodesSkinlet.h new file mode 100644 index 00000000..a9e0a672 --- /dev/null +++ b/playground/nodes/RadialNodesSkinlet.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +class RadialTickmarksSkinlet : public QskSkinlet +{ + Q_GADGET +public: + enum NodeRole + { + Lines, + RoleCount + }; + + Q_INVOKABLE RadialTickmarksSkinlet( QskSkin* skin = nullptr ); + +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; +}; \ No newline at end of file diff --git a/playground/nodes/main.cpp b/playground/nodes/main.cpp new file mode 100644 index 00000000..1513c476 --- /dev/null +++ b/playground/nodes/main.cpp @@ -0,0 +1,121 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "RadialNodes.h" +#include "RadialNodesSkinlet.h" + +int main( int argc, char* argv[] ) +{ +#ifdef ITEM_STATISTICS + QskObjectCounter counter( true ); +#endif + + QGuiApplication app( argc, argv ); + + SkinnyShortcut::enable( SkinnyShortcut::AllShortcuts ); + + qskSkinManager->setPluginPaths( { R"(C:\repositories\qskinny_rveh-install-win\plugins)" } ); + qskSetup->setItemUpdateFlag( QskQuickItem::PreferRasterForTextures, true ); + + QskWindow window; + window.resize( 800, 600 ); + + auto* const layout = new QskLinearBox( Qt::Horizontal, window.contentItem() ); + auto* const left = new QskLinearBox( Qt::Vertical, layout ); + auto* const right = new QskLinearBox( Qt::Vertical, layout ); + auto* const control = new RadialTickmarks( left ); + auto* const skinlet = new RadialTickmarksSkinlet; + { + ( void ) new QskTextLabel( "Tickmark Alignment", right ); + auto* const alignment = new QskSegmentedBar( right ); + alignment->setOptions( { "Center", "Bottom", "Top" } ); + QObject::connect( + alignment, &QskSegmentedBar::selectedIndexChanged, control, [ = ]( const int i ) { + static const Qt::Alignment a[ 3 ]{ Qt::AlignVCenter, Qt::AlignBottom, + Qt::AlignTop }; + control->setAlignmentHint( RadialTickmarks::Lines, a[ i ] ); + } ); + alignment->setSelectedIndex(0); + + ( void ) new QskTextLabel( "Tickmark Size W / H", right ); + auto* const sliderW = new QskSlider( right ); + auto* const sliderH = new QskSlider( right ); + + sliderW->setMinimum( 1.0 ); + sliderW->setMaximum( 4.0 ); + sliderW->setValue( 1.0 ); + sliderH->setValue( 0.5 ); + + auto updateStrutSizeHint = [ = ]( const qreal ) { + const auto width = sliderW->value(); + const auto height = sliderH->value() * qMin(control->height(), control->width()) / 2; + control->setStrutSizeHint( RadialTickmarks::Lines, width, height ); + }; + + QObject::connect( sliderW, &QskSlider::valueChanged, control, updateStrutSizeHint ); + QObject::connect( sliderH, &QskSlider::valueChanged, control, updateStrutSizeHint ); + + ( void ) new QskTextLabel( "Tickmarks [min,max]", right ); + + auto* const tickmarksMin = new QskSlider( right ); + tickmarksMin->setMinimum( 0 ); + tickmarksMin->setMaximum( 360 ); + + auto* const tickmarksMax = new QskSlider( right ); + tickmarksMax->setMinimum( 0 ); + tickmarksMax->setMaximum( 360 ); + + auto updateTickmark = [ control, tickmarksMin, tickmarksMax ]( const qreal v ) { + QskScaleTickmarks tickmarks; + QVector< qreal > major, medium, minor; + + for ( int deg = tickmarksMin->value(); deg < tickmarksMax->value(); deg += 1 ) + { + if ( deg % 10 == 0 ) + major << deg; + else if ( deg % 5 == 0 ) + medium << deg; + else + minor << deg; + } + + tickmarks.setMajorTicks( major ); + tickmarks.setMediumTicks( medium ); + tickmarks.setMinorTicks( minor ); + control->setTickmarks( tickmarks ); + }; + + QObject::connect( tickmarksMin, &QskSlider::valueChanged, control, updateTickmark ); + QObject::connect( tickmarksMax, &QskSlider::valueChanged, control, updateTickmark ); + + tickmarksMin->setValue( 0 ); + tickmarksMax->setValue( 270 ); + + } + control->setSkinlet( skinlet ); + skinlet->setOwnedBySkinnable( true ); + + window.show(); + return app.exec(); +}