qskinny/playground/dials/DialSkinlet.cpp

275 lines
7.3 KiB
C++
Raw Normal View History

2019-06-20 12:02:28 +02:00
/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* This file may be used under the terms of the 3-clause BSD License
*****************************************************************************/
2022-07-14 18:50:17 +02:00
#include "DialSkinlet.h"
#include "Dial.h"
#include <QskBoxBorderColors.h>
#include <QskBoxBorderMetrics.h>
#include <QskBoxShapeMetrics.h>
#include <QskTextColors.h>
2020-03-13 13:32:22 +01:00
#include <QskTextOptions.h>
#include <QskFunctions.h>
#include <QFontMetrics>
2020-12-18 16:32:54 +01:00
#include <QLineF>
#include <QSGFlatColorMaterial>
2022-12-05 13:56:15 +01:00
#include <QSGGeometryNode>
#include <QtMath>
namespace
{
2020-12-18 16:32:54 +01:00
class LinesNode : public QSGGeometryNode
{
2018-08-03 08:15:28 +02:00
public:
2020-12-18 16:32:54 +01:00
LinesNode( int lineCount = 0 )
: m_geometry( QSGGeometry::defaultAttributes_Point2D(), 2 * lineCount )
2018-08-03 08:15:28 +02:00
{
2020-10-23 12:51:31 +02:00
m_geometry.setDrawingMode( QSGGeometry::DrawLines );
2018-08-03 08:15:28 +02:00
m_geometry.setVertexDataPattern( QSGGeometry::StaticPattern );
2018-08-03 08:15:28 +02:00
setGeometry( &m_geometry );
setMaterial( &m_material );
}
2018-08-03 08:15:28 +02:00
void setColor( const QColor& color )
{
2020-12-18 16:32:54 +01:00
if ( color != m_material.color() )
{
m_material.setColor( color );
markDirty( QSGNode::DirtyMaterial );
}
2018-08-03 08:15:28 +02:00
}
2018-08-03 08:15:28 +02:00
private:
QSGFlatColorMaterial m_material;
QSGGeometry m_geometry;
};
2020-12-18 16:32:54 +01:00
class TicksNode : public LinesNode
{
public:
TicksNode()
{
}
};
class NeedleNode : public LinesNode
{
public:
NeedleNode()
: LinesNode( 1 )
{
}
void setData( const QLineF& line, qreal width )
{
auto vertexData = geometry()->vertexDataAsPoint2D();
vertexData[ 0 ].set( line.x1(), line.y1() );
vertexData[ 1 ].set( line.x2(), line.y2() );
2021-08-04 09:31:16 +02:00
2020-12-18 16:32:54 +01:00
geometry()->setLineWidth( width );
geometry()->markVertexDataDirty();
markDirty( QSGNode::DirtyGeometry );
}
};
}
2022-07-14 18:50:17 +02:00
DialSkinlet::DialSkinlet( QskSkin* skin )
2018-08-03 08:15:28 +02:00
: QskSkinlet( skin )
{
2020-12-18 16:32:54 +01:00
setNodeRoles( { PanelRole, NeedleRole, KnobRole, LabelsRole } );
}
2022-07-14 18:50:17 +02:00
QRectF DialSkinlet::subControlRect( const QskSkinnable* skinnable,
2020-12-18 16:32:54 +01:00
const QRectF& contentsRect, QskAspect::Subcontrol subcontrol ) const
{
2020-12-18 16:32:54 +01:00
QRectF r;
2020-12-13 19:38:46 +01:00
2022-07-14 18:50:17 +02:00
if ( subcontrol == Dial::Knob )
2020-12-18 16:32:54 +01:00
{
2022-07-14 18:50:17 +02:00
const auto size = skinnable->strutSizeHint( Dial::Knob );
2020-12-18 16:32:54 +01:00
r.setSize( size );
}
else
{
const auto extent = qMin( contentsRect.width(), contentsRect.height() );
r.setSize( QSizeF( extent, extent ) );
}
2021-08-04 09:31:16 +02:00
2020-12-13 19:38:46 +01:00
r.moveCenter( contentsRect.center() );
return r;
}
2022-07-14 18:50:17 +02:00
QSGNode* DialSkinlet::updateSubNode(
2018-08-03 08:15:28 +02:00
const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const
{
2022-07-14 18:50:17 +02:00
const auto speedometer = static_cast< const Dial* >( skinnable );
2018-08-03 08:15:28 +02:00
switch ( nodeRole )
{
case PanelRole:
2022-07-14 18:50:17 +02:00
return updateBoxNode( speedometer, node, Dial::Panel );
2020-12-18 16:32:54 +01:00
case KnobRole:
2022-07-14 18:50:17 +02:00
return updateBoxNode( speedometer, node, Dial::Knob );
case NeedleRole:
return updateNeedleNode( speedometer, node );
2020-12-18 16:32:54 +01:00
case LabelsRole:
return updateLabelsNode( speedometer, node );
default:
return nullptr;
}
}
2022-07-14 18:50:17 +02:00
QSGNode* DialSkinlet::updateLabelsNode(
const Dial* dial, QSGNode* node ) const
{
2022-07-14 18:50:17 +02:00
using Q = Dial;
2022-07-14 18:50:17 +02:00
const auto labels = dial->tickLabels();
// ### actually, we could draw labels with only one entry
2020-12-18 16:32:54 +01:00
if ( labels.count() <= 1 )
return nullptr;
auto ticksNode = static_cast< TicksNode* >( node );
if ( ticksNode == nullptr )
ticksNode = new TicksNode();
2022-07-14 18:50:17 +02:00
const auto color = dial->color( Q::TickLabels );
ticksNode->setColor( color );
2022-07-14 18:50:17 +02:00
const auto startAngle = dial->minimum();
const auto endAngle = dial->maximum();
2020-12-18 16:32:54 +01:00
const auto step = ( endAngle - startAngle ) / ( labels.count() - 1 );
auto geometry = ticksNode->geometry();
2020-12-18 16:32:54 +01:00
geometry->allocate( labels.count() * 2 );
auto vertexData = geometry->vertexDataAsPoint2D();
2022-07-14 18:50:17 +02:00
auto scaleRect = this->scaleRect( dial );
2019-01-07 09:13:53 +01:00
2020-12-18 16:32:54 +01:00
const auto center = scaleRect.center();
const auto radius = 0.5 * scaleRect.width();
2022-07-14 18:50:17 +02:00
const auto spacing = dial->spacingHint( Q::TickLabels );
2022-07-14 18:50:17 +02:00
const auto font = dial->effectiveFont( Q::TickLabels );
const QFontMetricsF fontMetrics( font );
auto angle = startAngle;
2022-07-14 18:50:17 +02:00
const auto tickSize = dial->strutSizeHint( Q::TickLabels );
const auto needleRadius = radius - tickSize.height();
// Create a series of tickmarks from minimum to maximum
auto labelNode = ticksNode->firstChild();
2020-12-18 16:32:54 +01:00
for ( int i = 0; i < labels.count(); ++i, angle += step )
{
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;
2018-08-03 08:15:28 +02:00
vertexData[ 0 ].set( xStart, yStart );
vertexData[ 1 ].set( xEnd, yEnd );
vertexData += 2;
const auto& text = labels.at( i );
if ( !text.isEmpty() )
{
const auto w = qskHorizontalAdvance( fontMetrics, text );
const auto h = fontMetrics.height();
const auto adjustX = ( -0.5 * cos - 0.5 ) * w;
const auto adjustY = ( -0.5 * sin - 0.5 ) * h;
const auto numbersX = xEnd - ( spacing * cos ) + adjustX;
const auto numbersY = yEnd - ( spacing * sin ) + adjustY;
2020-12-18 16:32:54 +01:00
const QRectF numbersRect( numbersX, numbersY, w, h );
labelNode = QskSkinlet::updateTextNode(
2022-07-14 18:50:17 +02:00
dial, labelNode, numbersRect, Qt::AlignCenter | Qt::AlignHCenter,
text, font, QskTextOptions(), QskTextColors( color ), Qsk::Normal );
if ( labelNode )
{
if ( labelNode->parent() != ticksNode )
ticksNode->appendChildNode( labelNode );
labelNode = labelNode->nextSibling();
}
}
}
geometry->setLineWidth( tickSize.width() );
geometry->markVertexDataDirty();
ticksNode->markDirty( QSGNode::DirtyGeometry );
return ticksNode;
}
2022-07-14 18:50:17 +02:00
QSGNode* DialSkinlet::updateNeedleNode(
const Dial* dial, QSGNode* node ) const
{
2022-07-14 18:50:17 +02:00
using Q = Dial;
2020-12-18 16:32:54 +01:00
auto needleNode = static_cast< NeedleNode* >( node );
if ( needleNode == nullptr )
2020-12-18 16:32:54 +01:00
needleNode = new NeedleNode();
2022-07-14 18:50:17 +02:00
const auto line = needlePoints( dial );
const auto width = dial->metric( Q::Needle | QskAspect::Size );
2020-12-18 16:32:54 +01:00
needleNode->setData( line, width * 2 );
2022-07-14 18:50:17 +02:00
needleNode->setColor( dial->color( Q::Needle ) );
2020-12-18 16:32:54 +01:00
return needleNode;
}
2022-07-14 18:50:17 +02:00
QRectF DialSkinlet::scaleRect( const Dial* dial ) const
2020-12-18 16:32:54 +01:00
{
2022-07-14 18:50:17 +02:00
using Q = Dial;
2022-07-14 18:50:17 +02:00
const auto margins = dial->marginHint( Q::Panel );
2022-07-14 18:50:17 +02:00
auto r = dial->subControlRect( Q::Panel );
2020-12-18 16:32:54 +01:00
r = r.marginsRemoved( margins );
2020-12-18 16:32:54 +01:00
return r;
}
2022-07-14 18:50:17 +02:00
QLineF DialSkinlet::needlePoints( const Dial* dial ) const
2020-12-18 16:32:54 +01:00
{
2022-07-14 18:50:17 +02:00
const auto r = scaleRect( dial );
const auto margin = dial->metric( Dial::Needle | QskAspect::Margin );
2020-12-18 16:32:54 +01:00
QLineF line;
line.setP1( r.center() );
line.setLength( 0.5 * r.width() - margin );
2022-07-14 18:50:17 +02:00
line.setAngle( -dial->value() );
2020-12-18 16:32:54 +01:00
return line;
}
2022-07-14 18:50:17 +02:00
#include "moc_DialSkinlet.cpp"