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..bc711741 --- /dev/null +++ b/playground/nodes/RadialNodes.cpp @@ -0,0 +1,17 @@ +#include "RadialNodes.h" + +QSK_SUBCONTROL(RadialNodes, Foreground) +QSK_SUBCONTROL(RadialNodes, Text) +QSK_SUBCONTROL(RadialNodes, Lines) + +RadialNodes::RadialNodes( QQuickItem* const parent ) : QskControl( parent ) +{ + // TODO move into your skin + setColor(Background, Qt::lightGray); + setColor(Foreground, Qt::black); + setColor(Text, Qt::white); + setColor(Lines, Qt::red); + setStrutSizeHint( Lines, 2, 10 ); +} + +#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..11741a35 --- /dev/null +++ b/playground/nodes/RadialNodes.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +class RadialNodes : public QskControl +{ +public: + QSK_SUBCONTROLS(Foreground, Text, Lines) + explicit RadialNodes( QQuickItem* parent = nullptr ); +}; \ No newline at end of file diff --git a/playground/nodes/RadialNodesSkinlet.cpp b/playground/nodes/RadialNodesSkinlet.cpp new file mode 100644 index 00000000..c84e261b --- /dev/null +++ b/playground/nodes/RadialNodesSkinlet.cpp @@ -0,0 +1,244 @@ +#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 }; + }; +} + +RadialNodesSkinlet::RadialNodesSkinlet( QskSkin* const skin ) + : QskSkinlet( skin ) +{ + setNodeRoles( { Lines } ); +} + +QRectF RadialNodesSkinlet::subControlRect( const QskSkinnable* const skinnable, + const QRectF& contentsRect, const QskAspect::Subcontrol subControl ) const +{ + if ( subControl == RadialNodes::Text ) + { + return contentsRect.adjusted( +20, +20, -20, -20 ); + } + else if ( subControl == RadialNodes::Foreground ) + { + return contentsRect.adjusted( +10, +10, -10, -10 ); + } + else if ( subControl == RadialNodes::Lines ) + { + return contentsRect; + } + return QskSkinlet::subControlRect( skinnable, contentsRect, subControl ); +} + +QSGNode* RadialNodesSkinlet::updateSubNode( + const QskSkinnable* const skinnable, const quint8 nodeRole, QSGNode* const node ) const +{ + using Q = RadialNodes; + + switch ( static_cast< NodeRole >( nodeRole ) ) + { + case Text: + return updateTextNode( skinnable, node, "RadialNodes", RadialNodes::Text ); + case Foreground: + return updateBoxNode( skinnable, node, RadialNodes::Foreground ); + case Lines: { + const auto* const control = static_cast< const Q* >( skinnable ); + + QskScaleTickmarks tickmarks; + QVector< qreal > major, medium, minor; + for ( int deg = 0; deg < 360; 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 ); + + 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( 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..0714d255 --- /dev/null +++ b/playground/nodes/RadialNodesSkinlet.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +class RadialNodesSkinlet : public QskSkinlet +{ + Q_GADGET +public: + enum NodeRole + { + Text, + Foreground, + Lines, + RoleCount + }; + + Q_INVOKABLE RadialNodesSkinlet( 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..e23a53df --- /dev/null +++ b/playground/nodes/main.cpp @@ -0,0 +1,83 @@ +/****************************************************************************** + * 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 RadialNodes( left ); + auto* const skinlet = new RadialNodesSkinlet; + { + (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(RadialNodes::Lines, a[i]); + }); + + (void) new QskTextLabel("Tickmark Size", 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() * control->height(); + control->setStrutSizeHint(RadialNodes::Lines, width, height ); + }; + + QObject::connect( sliderW, &QskSlider::valueChanged, control, updateStrutSizeHint ); + QObject::connect( sliderH, &QskSlider::valueChanged, control, updateStrutSizeHint ); + } + control->setSkinlet(skinlet); + skinlet->setOwnedBySkinnable( true ); + + window.show(); + return app.exec(); +}