diff --git a/doc/images/iotdashboard.png b/doc/images/iotdashboard.png index d5952c28..b46e0374 100644 Binary files a/doc/images/iotdashboard.png and b/doc/images/iotdashboard.png differ diff --git a/examples/iotdashboard/LightDisplay.cpp b/examples/iotdashboard/LightDisplay.cpp new file mode 100644 index 00000000..e03d12c8 --- /dev/null +++ b/examples/iotdashboard/LightDisplay.cpp @@ -0,0 +1,157 @@ +/****************************************************************************** + * Copyright (C) 2021 Edelhirsch Software GmbH + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#include "LightDisplay.h" +#include "Skin.h" + +#include +#include + +#include + +QSK_SUBCONTROL( LightDisplay, Panel ) +QSK_SUBCONTROL( LightDisplay, Groove ) +QSK_SUBCONTROL( LightDisplay, ColdAndWarmArc ) +QSK_SUBCONTROL( LightDisplay, Tickmarks ) +QSK_SUBCONTROL( LightDisplay, ValueText ) +QSK_SUBCONTROL( LightDisplay, LeftLabel ) +QSK_SUBCONTROL( LightDisplay, RightLabel ) +QSK_SUBCONTROL( LightDisplay, Knob ) + +QSK_STATE( LightDisplay, Pressed, ( QskAspect::FirstUserState << 1 ) ) + +LightDisplay::LightDisplay( QQuickItem* parent ) + : QskBoundedValueInput( parent ) +{ + setAlignmentHint( LeftLabel, Qt::AlignRight ); + setAlignmentHint( ValueText, Qt::AlignCenter | Qt::AlignHCenter ); + + setBoundaries( 0, 100 ); + + // ### move to Skin: + setShadow( { 0, 20 } ); + setShadowColor( 0xe5e5e5 ); +} + +bool LightDisplay::isPressed() const +{ + return hasSkinState( Pressed ); +} + +void LightDisplay::setShadow( const QskShadowMetrics& shadow ) +{ + m_shadow = shadow; + update(); +} + +const QskShadowMetrics& LightDisplay::shadow() const +{ + return m_shadow; +} + +void LightDisplay::setGradient( const QskGradient& gradient ) +{ + m_gradient = gradient; + update(); +} + +const QskGradient& LightDisplay::gradient() const +{ + return m_gradient; +} + +void LightDisplay::setShadowColor( const QColor& color ) +{ + m_shadowColor = color; + update(); +} + +QColor LightDisplay::shadowColor() const +{ + return m_shadowColor; +} + +void LightDisplay::mousePressEvent( QMouseEvent* event ) +{ + QRectF handleRect = subControlRect( LightDisplay::Knob ); + + if ( handleRect.contains( event->pos() ) ) + { + setSkinStateFlag( Pressed ); + } + else + { + QskBoundedValueInput::mousePressEvent( event ); + } +} + +void LightDisplay::mouseMoveEvent( QMouseEvent* event ) +{ + if ( !isPressed() ) + return; + + const auto mousePos = qskMousePosition( event ); + const auto rect = subControlRect( ColdAndWarmArc ); + + bool arcContainsMousePos = arcContainsPoint( rect, mousePos ); + + if( !arcContainsMousePos ) + { + setSkinStateFlag( Pressed, false ); + return; + } + + const QskArcMetrics metrics = arcMetricsHint( ColdAndWarmArc ); + const qreal angle = angleFromPoint( rect, mousePos ); + const qreal ratio = ( metrics.spanAngle() - angle * 16 ) / metrics.spanAngle(); + setValueAsRatio( ratio ); +} + +void LightDisplay::mouseReleaseEvent( QMouseEvent* /*event*/ ) +{ + setSkinStateFlag( Pressed, false ); +} + +qreal LightDisplay::angleFromPoint( const QRectF& rect, const QPointF& point ) const +{ + QPointF circlePos( point.x() - rect.center().x(), + rect.center().y() - point.y() ); + + const qreal atan = qAtan2( circlePos.y(), circlePos.x() ); + const qreal angle = qRadiansToDegrees( atan ); + return angle; +} + +bool LightDisplay::arcContainsPoint( const QRectF& rect, const QPointF& point ) const +{ + // putting this in an own function just because it might be useful + // at other places in the future + + const QskArcMetrics metrics = arcMetricsHint( ColdAndWarmArc ); + const int tolerance = 20; + + // 1. check angle + QPointF circlePos( point.x() - rect.center().x(), + rect.center().y() - point.y() ); + + const qreal angle = angleFromPoint( rect, point ); + const bool angleWithinRange = ( angle + tolerance ) > metrics.startAngle() + && angle < ( metrics.startAngle() + metrics.spanAngle() - tolerance ); + + // 2. check whether point is on arc + const qreal radiusMax = rect.width() / 2; + const qreal arcWidth = metrics.width(); + const qreal radiusMin = radiusMax - arcWidth; + + const qreal polarRadius = qSqrt( qPow( circlePos.x(), 2 ) + qPow( circlePos.y(), 2 ) ); + const bool pointOnArc = ( polarRadius + tolerance ) > radiusMin + && ( polarRadius - tolerance ) < radiusMax; + + bool ret = angleWithinRange && pointOnArc; + + return ret; +} + +#include "moc_LightDisplay.cpp" diff --git a/examples/iotdashboard/LightDisplay.h b/examples/iotdashboard/LightDisplay.h new file mode 100644 index 00000000..9aa7f0b6 --- /dev/null +++ b/examples/iotdashboard/LightDisplay.h @@ -0,0 +1,47 @@ +/****************************************************************************** + * Copyright (C) 2021 Edelhirsch Software GmbH + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#pragma once + +#include +#include +#include + +class LightDisplay : public QskBoundedValueInput +{ + Q_OBJECT + + public: + QSK_SUBCONTROLS( Panel, Groove, ColdAndWarmArc, Tickmarks, ValueText, + LeftLabel, RightLabel, Knob ) // ### rename knob to handle? + QSK_STATES( Pressed ) + + LightDisplay( QQuickItem* parent = nullptr ); + + bool isPressed() const; + + void setShadow( const QskShadowMetrics& ); + const QskShadowMetrics& shadow() const; + + void setGradient( const QskGradient& ); + const QskGradient& gradient() const; + + void setShadowColor( const QColor& ); + QColor shadowColor() const; + + protected: + void mousePressEvent( QMouseEvent* e ) override; + void mouseMoveEvent( QMouseEvent* e ) override; + void mouseReleaseEvent( QMouseEvent* e ) override; + + private: + qreal angleFromPoint( const QRectF&, const QPointF& ) const; + bool arcContainsPoint( const QRectF&, const QPointF& ) const; + + QskShadowMetrics m_shadow; + QColor m_shadowColor = Qt::black; + + QskGradient m_gradient; +}; diff --git a/examples/iotdashboard/LightDisplaySkinlet.cpp b/examples/iotdashboard/LightDisplaySkinlet.cpp new file mode 100644 index 00000000..ca551f23 --- /dev/null +++ b/examples/iotdashboard/LightDisplaySkinlet.cpp @@ -0,0 +1,209 @@ +/****************************************************************************** + * Copyright (C) 2021 Edelhirsch Software GmbH + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#include "LightDisplaySkinlet.h" +#include "LightDisplay.h" + +#include "nodes/BoxShadowNode.h" +#include "nodes/RadialTickmarksNode.h" + +#include +#include + +#include +#include + +LightDisplaySkinlet::LightDisplaySkinlet( QskSkin* skin ) + : QskSkinlet( skin ) +{ + setNodeRoles( { GrooveRole, PanelRole, ColdAndWarmArcRole, TickmarksRole, + ValueTextRole, LeftLabelRole, RightLabelRole, KnobRole } ); +} + +LightDisplaySkinlet::~LightDisplaySkinlet() +{ +} + +QRectF LightDisplaySkinlet::subControlRect( const QskSkinnable* skinnable, + const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const +{ + auto* display = static_cast< const LightDisplay* >( skinnable ); + QRectF rect = contentsRect; + const qreal ticksSpacing = 4; // space between the ticks and the arc + + if( subControl == LightDisplay::Groove + || subControl == LightDisplay::Panel + || subControl == LightDisplay::ValueText ) + { + QSizeF textSize = textLabelsSize( display ); + QskArcMetrics arcMetrics = display->arcMetricsHint( LightDisplay::ColdAndWarmArc ); + const qreal ticksWidth = display->arcMetricsHint( LightDisplay::Tickmarks ).width() + ticksSpacing; + + const qreal x = textSize.width() + arcMetrics.width() + ticksWidth; + const qreal w = contentsRect.width() - ( 2 * ( textSize.width() + arcMetrics.width() + ticksWidth ) ); + const qreal y = arcMetrics.width() + ticksWidth; + const qreal h = contentsRect.height() - 2 * ( arcMetrics.width() + ticksWidth ); + + const qreal diameter = qMin( w, h ); + + rect = QRectF( x, y, diameter, diameter ); + return rect; + } + else if( subControl == LightDisplay::ColdAndWarmArc ) + { + const QRectF panelRect = subControlRect( skinnable, contentsRect, + LightDisplay::Panel ); + auto barWidth = display->arcMetricsHint( LightDisplay::ColdAndWarmArc ).width(); + auto rect = panelRect.marginsAdded( { barWidth, barWidth, barWidth, barWidth } ); + return rect; + } + else if( subControl == LightDisplay::Tickmarks ) + { + const QRectF arcRect = subControlRect( skinnable, contentsRect, + LightDisplay::ColdAndWarmArc ); + const qreal ticksWidth = display->arcMetricsHint( LightDisplay::Tickmarks ).width() + ticksSpacing; + const QRectF rect = arcRect.marginsAdded( { ticksWidth, ticksWidth, ticksWidth, ticksWidth } ); + return rect; + } + else if( subControl == LightDisplay::LeftLabel ) + { + const QRectF ticksRect = subControlRect( skinnable, contentsRect, LightDisplay::Tickmarks ); + QSizeF size = textLabelsSize( display ); + + rect.setWidth( size.width() ); + + rect.setY( ticksRect.y() + ( ticksRect.height() - size.height() ) / 2 ); + rect.setHeight( size.height() ); + + return rect; + } + else if( subControl == LightDisplay::RightLabel ) + { + QRectF ticksRect = subControlRect( skinnable, contentsRect, LightDisplay::Tickmarks ); + QSizeF size = textLabelsSize( display ); + + rect.setX( ticksRect.x() + ticksRect.width() ); + + rect.setY( ticksRect.y() + ( ticksRect.height() - size.height() ) / 2 ); + rect.setHeight( size.height() ); + + return rect; + } + else if( subControl == LightDisplay::Knob ) + { + QRectF arcRect = subControlRect( skinnable, contentsRect, LightDisplay::ColdAndWarmArc ); + QskArcMetrics arcMetrics = display->arcMetricsHint( LightDisplay::ColdAndWarmArc ); + QSizeF knobSize = display->strutSizeHint( LightDisplay::Knob ); + + const qreal radius = ( arcRect.width() - arcMetrics.width() ) / 2; + const qreal angle = display->valueAsRatio() * 180; + + const qreal cos = qFastCos( qDegreesToRadians( angle ) ); + const qreal sin = qFastSin( qDegreesToRadians( angle ) ); + + const auto x = arcRect.center().x() - knobSize.width() / 2 - radius * cos; + const auto y = arcRect.center().y() - knobSize.height() / 2 - radius * sin; + + rect = QRectF( x, y, knobSize.width(), knobSize.height() ); + return rect; + } + + return contentsRect; +} + +QSGNode* LightDisplaySkinlet::updateSubNode( + const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const +{ + auto* display = static_cast< const LightDisplay* >( skinnable ); + + + switch( nodeRole ) + { + case PanelRole: + { + return updateBoxNode( skinnable, node, LightDisplay::Panel ); + } + case GrooveRole: + { + const QRectF grooveRect = display->subControlRect( LightDisplay::Groove ); + if ( grooveRect.isEmpty() ) + return nullptr; + + auto shadowNode = static_cast< BoxShadowNode* >( node ); + if ( shadowNode == nullptr ) + shadowNode = new BoxShadowNode(); + + const auto& shadowMetrics = display->shadow(); + + shadowNode->setRect( shadowMetrics.shadowRect( grooveRect ) ); + shadowNode->setShape( grooveRect.width() / 2 ); + shadowNode->setBlurRadius( shadowMetrics.blurRadius() ); + shadowNode->setColor( display->shadowColor() ); + shadowNode->setClipRect( grooveRect ); + + shadowNode->updateGeometry(); + + return shadowNode; + } + case ColdAndWarmArcRole: + { + return updateArcNode( skinnable, node, LightDisplay::ColdAndWarmArc ); + } + case TickmarksRole: + { + auto ticksNode = static_cast< RadialTickmarksNode* >( node ); + if ( ticksNode == nullptr ) + ticksNode = new RadialTickmarksNode(); + + QColor color = display->color( LightDisplay::Tickmarks ); + QRectF ticksRect = display->subControlRect( LightDisplay::Tickmarks ); + QskArcMetrics arcMetrics = display->arcMetricsHint( LightDisplay::Tickmarks ); + QskIntervalF boundaries = display->boundaries(); + QskScaleTickmarks tickmarks; + tickmarks.setMajorTicks( {0, 22.5, 45, 67.5, 90, 112.5, 135, 157.5, 180 } ); + int tickLineWidth = display->metric( LightDisplay::Tickmarks ); + + ticksNode->update( color, ticksRect, arcMetrics, boundaries, + tickmarks, tickLineWidth, Qt::Horizontal ); + + return ticksNode; + } + case ValueTextRole: + { + const QString valueText = QString::number( display->value(), 'f', 0 ) + + QStringLiteral( " %" ); + return updateTextNode( skinnable, node, valueText, {}, + LightDisplay::ValueText ); + } + case LeftLabelRole: + { + return updateTextNode( skinnable, node, QStringLiteral( "0 " ), {}, + LightDisplay::LeftLabel ); + } + case RightLabelRole: + { + return updateTextNode( skinnable, node, QStringLiteral( " 100" ), {}, + LightDisplay::RightLabel ); + } + case KnobRole: + { + return updateBoxNode( skinnable, node, LightDisplay::Knob ); + } + } + + return Inherited::updateSubNode( skinnable, nodeRole, node ); +} + +QSizeF LightDisplaySkinlet::textLabelsSize( const LightDisplay* display ) const +{ + QFont font = display->effectiveFont( LightDisplay::LeftLabel ); + QFontMetricsF fm( font ); + + qreal w = fm.width( QStringLiteral( " 100" ) ); + qreal h = fm.height(); + return { w, h }; +} + +#include "moc_LightDisplaySkinlet.cpp" diff --git a/examples/iotdashboard/LightDisplaySkinlet.h b/examples/iotdashboard/LightDisplaySkinlet.h new file mode 100644 index 00000000..c384b2f4 --- /dev/null +++ b/examples/iotdashboard/LightDisplaySkinlet.h @@ -0,0 +1,46 @@ +/****************************************************************************** + * Copyright (C) 2021 Edelhirsch Software GmbH + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#pragma once + +#include + +class LightDisplay; + +class LightDisplaySkinlet : public QskSkinlet +{ + Q_GADGET + + using Inherited = QskSkinlet; + + public: + enum NodeRole + { + PanelRole, + GrooveRole, + ColdAndWarmArcRole, + WarmPartRole, + TickmarksRole, + ValueTextRole, + LeftLabelRole, + RightLabelRole, + KnobRole, + + RoleCount, + }; + + Q_INVOKABLE LightDisplaySkinlet( QskSkin* = nullptr ); + ~LightDisplaySkinlet() override; + + QRectF subControlRect( const QskSkinnable*, + const QRectF&, QskAspect::Subcontrol ) const override; + + protected: + QSGNode* updateSubNode( const QskSkinnable*, + quint8 nodeRole, QSGNode* ) const override; + + private: + QSizeF textLabelsSize( const LightDisplay* display ) const; +}; diff --git a/examples/iotdashboard/LightIntensity.cpp b/examples/iotdashboard/LightIntensity.cpp deleted file mode 100644 index 4e957fb8..00000000 --- a/examples/iotdashboard/LightIntensity.cpp +++ /dev/null @@ -1,248 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2021 Edelhirsch Software GmbH - * This file may be used under the terms of the 3-clause BSD License - *****************************************************************************/ - -#include "LightIntensity.h" -#include "Skin.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -QSK_SUBCONTROL( LightDisplay, Panel ) -QSK_SUBCONTROL( LightDisplay, ColdPart ) -QSK_SUBCONTROL( LightDisplay, WarmPart ) -QSK_SUBCONTROL( LightDisplay, ValueText ) - -namespace -{ - class LightDimmer; - - QColor invertedColor( const QColor& c ) - { - QColor ret = { 255 - c.red(), 255 - c.green(), 255 - c.blue()}; - return ret; - } - - class LightDimmer : public QQuickPaintedItem - { - public: - LightDimmer( const QskGradient& coldGradient, - const QskGradient& warmGradient, QQuickItem* parent ); - - double thickness() const - { - return m_thickness; - } - - void setThickness( double thickness ) - { - m_thickness = thickness; - } - - QColor backgroundColor() const - { - return m_backgroundColor; - } - - void setBackgroundColor( const QColor& color ) - { - m_backgroundColor = color; - } - - QRadialGradient ringGradient() const - { - return m_ringGradient; - } - - void setRingGradient( const QRadialGradient& gradient ) - { - m_ringGradient = gradient; - } - - QRectF ringRect() const - { - const qreal r = qMin( width(), height() ) - 4; - return QRectF( 0.0, 0.0, r, r ); - } - - private: - void paint( QPainter* ) override; - void updateGradient(); - - double m_thickness = 17.57; - QColor m_backgroundColor; - QRadialGradient m_ringGradient; - QskGradient m_coldGradient; - QskGradient m_warmGradient; - }; - - // ### There must be an easier way to do this - class DimmerAnimator : public QskAnimator - { - public: - DimmerAnimator( LightDisplay* display, LightDimmer* dimmer ) - : m_display( display ) - , m_dimmer( dimmer ) - { - QQuickWindow* w = static_cast< QQuickWindow* >( qGuiApp->allWindows().at( 0 ) ); - setWindow( w ); - setDuration( 500 ); - setEasingCurve( QEasingCurve::Linear ); - } - - void setup() override - { - m_backgroundColor = m_display->color( LightDisplay::Panel ); - m_ringGradient = m_dimmer->ringGradient(); - } - - void advance( qreal value ) override - { - const QColor c = m_backgroundColor; - const QColor c2 = invertedColor( c ); - const QColor newColor = QskRgb::interpolated( c2, c, value ); - m_dimmer->setBackgroundColor( newColor ); - - QRadialGradient gradient = m_ringGradient; - QRadialGradient newGradient = gradient; - - for( const QGradientStop& stop : gradient.stops() ) - { - QColor c = stop.second; - QColor c2 = invertedColor( c ); - const QColor newColor = QskRgb::interpolated( c, c2, value ); - newGradient.setColorAt( stop.first, newColor ); - } - - m_dimmer->setRingGradient( newGradient ); - m_dimmer->update(); - } - - private: - QColor m_backgroundColor; - QRadialGradient m_ringGradient; - LightDisplay* m_display; - LightDimmer* m_dimmer; - }; -} - -LightDimmer::LightDimmer( const QskGradient& coldGradient, - const QskGradient& warmGradient, QQuickItem* parent ) - : QQuickPaintedItem( parent ) - , m_coldGradient( coldGradient ) - , m_warmGradient( warmGradient ) -{ - connect( this, &QQuickPaintedItem::widthChanged, - this, &LightDimmer::updateGradient ); - - connect( this, &QQuickPaintedItem::heightChanged, - this, &LightDimmer::updateGradient ); -} - -void LightDimmer::updateGradient() -{ - const auto sz = ringRect().size(); - - QRadialGradient ringGradient( sz.width() / 2, sz.height() / 2, 110 ); - QGradientStop stop1( 0.0, "#c0c0c0" ); - QGradientStop stop2( 0.5, "#f0f0f0" ); - QGradientStop stop3( 1.0, "#c0c0c0" ); - ringGradient.setStops( {stop1, stop2, stop3} ); - - m_ringGradient = ringGradient; -} - -void LightDimmer::paint( QPainter* painter ) -{ - const auto sz = ringRect().size(); - - const qreal knobDiameter = 15.65; - const qreal offset = ( thickness() - knobDiameter ) + 2; - - painter->setRenderHint( QPainter::Antialiasing, true ); - - QRectF outerRect( 0, offset, sz.width(), sz.height() ); - - painter->setBrush( m_ringGradient ); - - painter->setPen( m_backgroundColor ); - painter->drawEllipse( outerRect ); - - int startAngle = 16 * 180; - int middleAngle = 16 * -90; - int endAngle = 16 * -90; - - QLinearGradient coldGradient( {thickness(), 0.0}, {thickness(), thickness()} ); - coldGradient.setColorAt( 0, m_coldGradient.colorAt( 0 ) ); - coldGradient.setColorAt( 1, m_coldGradient.colorAt( 1 ) ); - painter->setBrush( coldGradient ); - painter->setPen( Qt::transparent ); - painter->drawPie( outerRect, startAngle, middleAngle ); - - QLinearGradient warmGradient( {thickness(), 0.0}, {thickness(), thickness()} ); - warmGradient.setColorAt( 0, m_warmGradient.colorAt( 0 ) ); - warmGradient.setColorAt( 1, m_warmGradient.colorAt( 1 ) ); - painter->setBrush( warmGradient ); - painter->drawPie( outerRect, 16 * 90, endAngle ); - - painter->setBrush( m_backgroundColor ); - painter->setPen( m_backgroundColor ); - QRectF innerRect( thickness() / 2, thickness() / 2 + offset, sz.width() - thickness(), sz.height() - thickness() ); - painter->drawEllipse( innerRect ); - - painter->setBrush( m_backgroundColor ); - painter->setPen( "#c4c4c4" ); - QRectF knobRect( ( sz.width() - knobDiameter ) / 2, 1, knobDiameter, knobDiameter ); - painter->drawEllipse( knobRect ); -} - -LightDisplay::LightDisplay( QQuickItem* parent ) - : QskLinearBox( Qt::Horizontal, parent ) -{ - setSubcontrolProxy( QskBox::Panel, LightDisplay::Panel ); - - auto leftLabel = new QskTextLabel( QString::number( 0 ), this ); - leftLabel->setSizePolicy( QskSizePolicy::Fixed, QskSizePolicy::Fixed ); - - auto rightLabel = new QskTextLabel( QString::number( 100 ), this ); - rightLabel->setSizePolicy( QskSizePolicy::Fixed, QskSizePolicy::Fixed ); - - auto dimmer = new LightDimmer( gradientHint( ColdPart ), gradientHint( WarmPart ), this ); - dimmer->setBackgroundColor( color( Panel ) ); - - m_valueLabel = new QskTextLabel( QString::number( 50 ) + "%", dimmer ); - m_valueLabel->setSubcontrolProxy( QskTextLabel::Text, LightDisplay::ValueText ); - - addItem( leftLabel ); - addItem( dimmer ); - addItem( rightLabel ); - - auto animator = new DimmerAnimator( this, dimmer ); - connect( qskSetup, &QskSetup::skinChanged, - [animator]() { animator->start(); } ); -} - -void LightDisplay::updateLayout() -{ - QskLinearBox::updateLayout(); - - auto dimmer = static_cast< const LightDimmer* >( m_valueLabel->parentItem() ); - - QRectF r; - r.setSize( m_valueLabel->sizeConstraint() ); - r.moveCenter( dimmer->ringRect().center() + QPointF( 0, 4 ) ); - - m_valueLabel->setGeometry( r ); -} - -#include "moc_LightIntensity.cpp" diff --git a/examples/iotdashboard/LightIntensity.h b/examples/iotdashboard/LightIntensity.h deleted file mode 100644 index ccdb4755..00000000 --- a/examples/iotdashboard/LightIntensity.h +++ /dev/null @@ -1,26 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2021 Edelhirsch Software GmbH - * This file may be used under the terms of the 3-clause BSD License - *****************************************************************************/ - -#pragma once - -#include - -class QskTextLabel; - -class LightDisplay : public QskLinearBox -{ - Q_OBJECT - - public: - QSK_SUBCONTROLS( Panel, ColdPart, WarmPart, ValueText ) - - LightDisplay( QQuickItem* parent = nullptr ); - - protected: - void updateLayout() override; - - private: - QskTextLabel* m_valueLabel; -}; diff --git a/examples/iotdashboard/MainContent.cpp b/examples/iotdashboard/MainContent.cpp index 60df8234..b3133d6c 100644 --- a/examples/iotdashboard/MainContent.cpp +++ b/examples/iotdashboard/MainContent.cpp @@ -8,7 +8,7 @@ #include "Box.h" #include "BoxWithButtons.h" #include "UsageDiagram.h" -#include "LightIntensity.h" +#include "LightDisplay.h" #include "MyDevices.h" #include "PieChart.h" #include "TopBar.h" @@ -54,7 +54,9 @@ namespace LightIntensity( QQuickItem* parent = nullptr ) : Box( "Light intensity", parent ) { - new LightDisplay( this ); + addSpacer( 5 ); + auto* lightDisplay = new LightDisplay( this ); + lightDisplay->setValue( 50.0 ); } }; } diff --git a/examples/iotdashboard/Skin.cpp b/examples/iotdashboard/Skin.cpp index 049a2732..8856a7af 100644 --- a/examples/iotdashboard/Skin.cpp +++ b/examples/iotdashboard/Skin.cpp @@ -11,7 +11,8 @@ #include "CircularProgressBarSkinlet.h" #include "Diagram.h" #include "DiagramSkinlet.h" -#include "LightIntensity.h" +#include "LightDisplay.h" +#include "LightDisplaySkinlet.h" #include "MainContent.h" #include "MenuBar.h" #include "PieChartPainted.h" @@ -54,6 +55,7 @@ Skin::Skin( const Palette& palette, QObject* parent ) { declareSkinlet< CircularProgressBar, CircularProgressBarSkinlet >(); declareSkinlet< Diagram, DiagramSkinlet >(); + declareSkinlet< LightDisplay, LightDisplaySkinlet >(); initHints( palette ); } @@ -180,18 +182,36 @@ void Skin::initHints( const Palette& palette ) ed.setColor( Diagram::ChartArea3, "#66ff7d34" ); // light intensity: - ed.setGradient( LightDisplay::ColdPart, { Qt::Horizontal, "#a7b0ff", "#6776ff" } ); - ed.setGradient( LightDisplay::WarmPart, { Qt::Horizontal, "#feeeb7", "#ff3122" } ); + ed.setBoxShape( LightDisplay::Panel, 100, Qt::RelativeSize ); + + ed.setArcMetrics( LightDisplay::ColdAndWarmArc, 8.785, 0, 180 * 16 ); + QskGradient coldGradient( Qt::Horizontal, { { 0.0, 0xffff3122 }, + { 0.2, 0xfffeeeb7 }, + { 0.3, 0xffa7b0ff }, + { 0.5, 0xff6776ff }, + { 1.0, Qt::black } } ); + ed.setGradient( LightDisplay::ColdAndWarmArc, coldGradient ); + + ed.setMetric( LightDisplay::Tickmarks, 1 ); + ed.setArcMetrics( LightDisplay::Tickmarks, { 4.69, 0, 180 * 16 } ); + ed.setColor( LightDisplay::Tickmarks, 0x55929CB2 ); + ed.setFontRole( LightDisplay::ValueText, QskSkin::LargeFont ); ed.setColor( LightDisplay::ValueText, "#929cb2" ); + ed.setStrutSize( LightDisplay::Knob, { 20, 20 } ); + ed.setBoxBorderMetrics( LightDisplay::Knob, 1 ); + ed.setBoxBorderColors( LightDisplay::Knob, 0xffc4c4c4 ); + ed.setBoxShape( LightDisplay::Knob, 100, Qt::RelativeSize ); + // palette dependent skin hints: ed.setGradient( MenuBar::Panel, palette.menuBar ); ed.setGradient( MainContent::Panel, palette.mainContent ); ed.setGradient( Box::Panel, palette.box ); ed.setGradient( BoxWithButtons::Panel, palette.box ); ed.setGradient( UsageDiagramBox::Panel, palette.box ); - ed.setColor( LightDisplay::Panel, palette.lightDisplay ); + ed.setGradient( LightDisplay::Panel, palette.box ); + ed.setGradient( LightDisplay::Knob, palette.box ); ed.setGradient( RoundButton::Panel, palette.roundButton ); ed.setBoxBorderColors( UsageDiagramBox::DaysBox, palette.weekdayBox ); ed.setColor( QskTextLabel::Text, palette.text ); diff --git a/examples/iotdashboard/iotdashboard.pro b/examples/iotdashboard/iotdashboard.pro index 2064a376..1b6a87b7 100644 --- a/examples/iotdashboard/iotdashboard.pro +++ b/examples/iotdashboard/iotdashboard.pro @@ -10,7 +10,8 @@ SOURCES += \ Diagram.cpp \ DiagramSkinlet.cpp \ GraphicProvider.cpp \ - LightIntensity.cpp \ + LightDisplaySkinlet.cpp \ + LightDisplay.cpp \ MainContent.cpp \ MenuBar.cpp \ MyDevices.cpp \ @@ -28,9 +29,10 @@ SOURCES += \ main.cpp \ SOURCES += \ + nodes/BoxShadowNode.cpp \ nodes/DiagramDataNode.cpp \ nodes/DiagramSegmentsNode.cpp \ - nodes/BoxShadowNode.cpp + nodes/RadialTickmarksNode.cpp HEADERS += \ Box.h \ @@ -40,7 +42,8 @@ HEADERS += \ Diagram.h \ DiagramSkinlet.h \ GraphicProvider.h \ - LightIntensity.h \ + LightDisplaySkinlet.h \ + LightDisplay.h \ MainContent.h \ MainWindow.h \ MenuBar.h \ @@ -57,9 +60,10 @@ HEADERS += \ UsageDiagram.h HEADERS += \ + nodes/BoxShadowNode.h \ nodes/DiagramDataNode.h \ nodes/DiagramSegmentsNode.h \ - nodes/BoxShadowNode.h + nodes/RadialTickmarksNode.h RESOURCES += \ images.qrc \ diff --git a/examples/iotdashboard/nodes/RadialTickmarksNode.cpp b/examples/iotdashboard/nodes/RadialTickmarksNode.cpp new file mode 100644 index 00000000..13a86dbc --- /dev/null +++ b/examples/iotdashboard/nodes/RadialTickmarksNode.cpp @@ -0,0 +1,129 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#include "RadialTickmarksNode.h" + +#include +#include + +QSK_QT_PRIVATE_BEGIN +#include +QSK_QT_PRIVATE_END + +static constexpr inline qreal qskTickFactor( QskScaleTickmarks::TickType type ) +{ + using TM = QskScaleTickmarks; + return type == TM::MinorTick ? 0.7 : ( type == TM::MinorTick ? 0.85 : 1.0 ); +} + +class RadialTickmarksNodePrivate final : public QSGGeometryNodePrivate +{ + public: + RadialTickmarksNodePrivate() + : geometry( QSGGeometry::defaultAttributes_Point2D(), 0 ) + { +#if QT_VERSION >= QT_VERSION_CHECK( 5, 8, 0 ) + geometry.setDrawingMode( QSGGeometry::DrawLines ); +#else + geometry.setDrawingMode( GL_LINES ); +#endif + geometry.setVertexDataPattern( QSGGeometry::StaticPattern ); + } + + QSGGeometry geometry; + QSGFlatColorMaterial material; + + QskIntervalF boundaries; + QskScaleTickmarks tickmarks; + + QRectF rect; + int lineWidth = 0; + + uint hash = 0; +}; + +RadialTickmarksNode::RadialTickmarksNode() + : QSGGeometryNode( *new RadialTickmarksNodePrivate ) +{ + Q_D( RadialTickmarksNode ); + + setGeometry( &d->geometry ); + setMaterial( &d->material ); +} + +RadialTickmarksNode::~RadialTickmarksNode() +{ +} + +void RadialTickmarksNode::update(const QColor& color, const QRectF& rect, + const QskArcMetrics& arcMetrics, const QskIntervalF& /*boundaries*/, + const QskScaleTickmarks& tickmarks, int lineWidth, + Qt::Orientation /*orientation*/ ) +{ + Q_D( RadialTickmarksNode ); + + if( lineWidth != d->lineWidth ) + { + d->lineWidth = lineWidth; + d->geometry.setLineWidth( lineWidth ); + + markDirty( QSGNode::DirtyGeometry ); + } + + const uint hash = tickmarks.hash( 17435 ); + + if( ( hash != d->hash ) || ( rect != d->rect ) ) + { + d->hash = hash; + d->rect = rect; + + d->geometry.allocate( tickmarks.tickCount() * 2 ); + auto vertexData = d->geometry.vertexDataAsPoint2D(); + + const auto center = rect.center(); + const auto radius = 0.5 * rect.width(); + const auto needleRadius = radius - arcMetrics.width(); + + using TM = QskScaleTickmarks; + + for( int i = TM::MinorTick; i <= TM::MajorTick; i++ ) + { + const auto tickType = static_cast< TM::TickType >( i ); + const auto ticks = tickmarks.ticks( tickType ); + + const auto startAngle = arcMetrics.startAngle(); + const auto endAngle = startAngle + arcMetrics.spanAngle(); + + for( const auto tick : ticks ) + { + const qreal ratio = ( tick - startAngle ) / ( endAngle - startAngle ); + const qreal angle = ratio * ( endAngle - startAngle ); + + const qreal cos = qFastCos( qDegreesToRadians( angle ) ); + const qreal sin = qFastSin( qDegreesToRadians( angle ) ); + + const auto xStart = center.x() - radius * cos; + const auto yStart = center.y() - radius * sin; + + const auto xEnd = center.x() - needleRadius * cos; + const auto yEnd = center.y() - needleRadius * sin; + + vertexData[ 0 ].set( xStart, yStart ); + vertexData[ 1 ].set( xEnd, yEnd ); + + vertexData += 2; + } + } + + d->geometry.markVertexDataDirty(); + markDirty( QSGNode::DirtyGeometry ); + } + + if ( color != d->material.color() ) + { + d->material.setColor( color ); + markDirty( QSGNode::DirtyMaterial ); + } +} diff --git a/examples/iotdashboard/nodes/RadialTickmarksNode.h b/examples/iotdashboard/nodes/RadialTickmarksNode.h new file mode 100644 index 00000000..27fc82a9 --- /dev/null +++ b/examples/iotdashboard/nodes/RadialTickmarksNode.h @@ -0,0 +1,27 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#pragma once + +#include +#include +#include + +#include + +class RadialTickmarksNodePrivate; + +class RadialTickmarksNode : public QSGGeometryNode +{ + public: + RadialTickmarksNode(); + ~RadialTickmarksNode() override; + + void update( const QColor&, const QRectF&, const QskArcMetrics&, + const QskIntervalF&, const QskScaleTickmarks&, int, Qt::Orientation ); + + private: + Q_DECLARE_PRIVATE( RadialTickmarksNode ) +};