qskinny/examples/sliders/SliderSkinlet.cpp
Uwe Rathmann e6f0088ae4 All box subcontrols are displayd with vertex lists instead of
textures
    now. Implementation is almost complete beside of the not yet done Qt
    antialiasing mode. Not all sort of linear gradients ( see
    QLinearGradients ) are implemented - needs 1-2 days more.
    The aspect flags for box primitives have been substantially changed
from
    too atomic to more strutured units.
    The skins are currently incomplete - will be fixed later.
2017-10-17 17:34:00 +02:00

398 lines
11 KiB
C++

/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* This file may be used under the terms of the 3-clause BSD License
*****************************************************************************/
#include "SliderSkinlet.h"
#include "Slider.h"
#include <QskAspect.h>
#include <QskSlider.h>
#include <QskPlainTextRenderer.h>
#include <QskRgbValue.h>
#include <QSGTransformNode>
#include <QSGFlatColorMaterial>
#include <QSGSimpleRectNode>
#include <QFontMetricsF>
#include <cmath>
#if 1
// should be skin hints
const qreal qskMinorTick = 20;
const qreal qskMajorTick = 1.5 * qskMinorTick;
const qreal qskMargin = 20;
const qreal qskPeak = 10;
QFont qskLabelFont;
#endif
class TicksNode : public QSGGeometryNode
{
public:
TicksNode( const QColor& color ):
m_geometry( QSGGeometry::defaultAttributes_Point2D(), 0 )
{
m_geometry.setDrawingMode( GL_LINES );
m_geometry.setVertexDataPattern( QSGGeometry::StaticPattern );
m_material.setColor( color );
setGeometry( &m_geometry );
setMaterial( &m_material );
}
private:
QSGFlatColorMaterial m_material;
QSGGeometry m_geometry;
};
class HandleNode : public QSGGeometryNode
{
public:
HandleNode():
m_geometry( QSGGeometry::defaultAttributes_Point2D(), 8 * 2 )
{
setGeometry( &m_geometry );
setMaterial( &m_material );
}
void update( const QRectF& rect, qreal peak, const QColor& color )
{
if ( color != m_color )
{
m_material.setColor( color );
m_color = color;
markDirty( QSGNode::DirtyMaterial );
}
if ( rect != m_rect || peak != m_peak )
{
QSGGeometry::Point2D* p =
reinterpret_cast< QSGGeometry::Point2D* >( m_geometry.vertexData() );
const qreal y0 = rect.y() + qskPeak;
setLine( p, peak, peak, rect.y() );
setLine( p + 2, peak - 5, peak + 5, y0 );
// corners manually "rounded" by 3 pixels
setLine( p + 4, rect.left() + 2, rect.right() - 2, y0 );
setLine( p + 6, rect.left() + 1, rect.right() - 1, y0 + 1 );
setLine( p + 8, rect.left(), rect.right(), y0 + 2 );
setLine( p + 10, rect.left(), rect.right(), rect.bottom() - 1 );
setLine( p + 12, rect.left() + 1, rect.right() - 1, rect.bottom() - 1 );
setLine( p + 14, rect.left() + 2, rect.right() - 2, rect.bottom() );
m_rect = rect;
m_peak = peak;
markDirty( QSGNode::DirtyGeometry );
}
}
private:
inline void setLine( QSGGeometry::Point2D* points, float x1, float x2, qreal y )
{
points[0].x = x1;
points[0].y = y;
points[1].x = x2;
points[1].y = y;
}
QRectF m_rect;
qreal m_peak;
QColor m_color;
QSGFlatColorMaterial m_material;
QSGGeometry m_geometry;
};
SliderSkinlet::SliderSkinlet()
{
qskLabelFont = QFont();
setNodeRoles( { ScaleRole, FillRole, DecorationRole, HandleRole } );
}
SliderSkinlet::~SliderSkinlet()
{
}
QRectF SliderSkinlet::subControlRect( const QskSkinnable* skinnable,
QskAspect::Subcontrol subControl ) const
{
const auto slider = static_cast< const QskSlider* >( skinnable );
if ( subControl == QskSlider::Groove )
{
return QRectF(); // we don't have a groove
}
else if ( subControl == QskSlider::Fill )
{
return fillRect( slider );
}
else if ( subControl == QskSlider::Handle )
{
return handleRect( slider );
}
else if ( subControl == Slider::Scale )
{
return scaleRect( slider );
}
else if ( subControl == Slider::Decoration )
{
return decorationRect( slider );
}
return Inherited::subControlRect( skinnable, subControl );
}
QSGNode* SliderSkinlet::updateSubNode(
const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const
{
const auto slider = static_cast< const QskSlider* >( skinnable );
switch( nodeRole )
{
case ScaleRole:
return updateScaleNode( slider, node );
case DecorationRole:
return updateDecorationNode( slider, node );
case FillRole:
return updateFillNode( slider, node );
case HandleRole:
return updateHandleNode( slider, node );
default:
return nullptr;
}
}
QRectF SliderSkinlet::scaleRect( const QskSlider* slider ) const
{
auto r = slider->contentsRect();
r.setX( r.left() + qskMargin );
r.setBottom( r.center().y() );
r.setTop( r.bottom() - qskMajorTick );
r.setWidth( r.width() - qskMargin );
return r;
}
QRectF SliderSkinlet::fillRect( const QskSlider* slider ) const
{
auto r = subControlRect( slider, Slider::Scale );
r.setTop( r.bottom() - qskMinorTick );
r.setWidth( r.width() * slider->position() );
return r;
}
QRectF SliderSkinlet::decorationRect( const QskSlider* slider ) const
{
// decoration exceeds scale !!!!
auto r = subControlRect( slider, Slider::Scale );
r.setBottom( r.top() );
r.setTop( r.bottom() - QFontMetricsF( qskLabelFont ).height() );
return r;
}
QRectF SliderSkinlet::handleRect( const QskSlider* slider ) const
{
const QRectF contentsRect = slider->contentsRect();
const QRectF fillRect = subControlRect( slider, QskSlider::Fill );
const QRectF scaleRect = subControlRect( slider, Slider::Scale );
QRectF handleRect( 0, scaleRect.bottom(), 80, 50 );
handleRect.moveCenter( QPointF( fillRect.right(), handleRect.center().y() ) );
if ( handleRect.left() < contentsRect.left() )
handleRect.moveLeft( contentsRect.left() );
else if ( handleRect.right() > contentsRect.right() )
handleRect.moveRight( contentsRect.right() );
return handleRect;
}
QSGNode* SliderSkinlet::updateGrooveNode( const QskSlider*, QSGNode* ) const
{
// we don't have a groove
return nullptr;
}
QSGNode* SliderSkinlet::updateScaleNode(
const QskSlider* slider, QSGNode* node ) const
{
const QRectF scaleRect = subControlRect( slider, Slider::Scale );
if ( scaleRect.isEmpty() )
return nullptr;
auto ticksNode = static_cast< TicksNode* >( node );
if ( ticksNode == nullptr )
ticksNode = new TicksNode( slider->color( Slider::Scale ) );
const int tickCount = std::floor( slider->range() / slider->stepSize() ) + 1;
auto geometry = ticksNode->geometry();
geometry->allocate( tickCount * 2 );
auto vertexData = geometry->vertexDataAsPoint2D();
memset( vertexData, 0, geometry->vertexCount() );
auto stepStride = slider->stepSize() / slider->range() * scaleRect.width();
auto x = scaleRect.x();
const auto y = scaleRect.bottom();
// Create a series of tickmarks from minimum to maximum
for ( int i = 0; i < tickCount; ++i )
{
vertexData[0].set( x, y );
vertexData[1].set( x, y - ( ( i % 10 ) ? qskMinorTick : qskMajorTick ) );
vertexData += 2;
x += stepStride;
}
geometry->setLineWidth( 1 );
geometry->markVertexDataDirty();
ticksNode->markDirty( QSGNode::DirtyGeometry );
return ticksNode;
}
QSGNode* SliderSkinlet::updateDecorationNode(
const QskSlider* slider, QSGNode* node ) const
{
const QRectF decorationRect = subControlRect( slider, Slider::Decoration );
if ( decorationRect.isEmpty() )
return nullptr;
auto decorationNode = static_cast< QSGTransformNode* >( node );
if ( decorationNode == nullptr )
decorationNode = new QSGTransformNode();
const int tickCount = std::floor( slider->range() / slider->stepSize() ) + 1;
auto labelNode = static_cast< QSGTransformNode* >( decorationNode->firstChild() );
auto stepStride = slider->stepSize() / slider->range() * decorationRect.width();
auto x = decorationRect.x();
const auto y = decorationRect.y();
for ( int i = 0; i < tickCount; i += 100 )
{
if ( labelNode == nullptr )
{
labelNode = new QSGTransformNode;
decorationNode->appendChildNode( labelNode );
}
auto labelText = QString::number( slider->minimum() + slider->stepSize() * i, 'f', 0 );
QskPlainTextRenderer renderer;
renderer.setFont( qskLabelFont );
renderer.setAlignment( Qt::AlignHCenter );
renderer.updateNode( slider, QRectF(), labelText, labelNode,
QskRgbValue::Grey700, Qsk::Normal, Qt::transparent );
QMatrix4x4 matrix;
matrix.translate( x, y );
labelNode->setMatrix( matrix );
labelNode = static_cast< decltype( labelNode ) >( labelNode->nextSibling() );
x += 100 * stepStride;
}
// Remove unused labels
while ( labelNode )
{
auto sibling = static_cast< decltype( labelNode ) >( labelNode->nextSibling() );
decorationNode->removeChildNode( labelNode );
delete labelNode;
labelNode = sibling;
}
return decorationNode;
}
QSGNode* SliderSkinlet::updateFillNode(
const QskSlider* slider, QSGNode* node ) const
{
const QRectF fillRect = subControlRect( slider, QskSlider::Fill );
if ( fillRect.isEmpty() )
return nullptr;
QSGSimpleRectNode* fillNode = static_cast< QSGSimpleRectNode* >( node );
if ( fillNode == nullptr )
{
fillNode = new QSGSimpleRectNode;
fillNode->setFlags( QSGNode::OwnedByParent );
fillNode->setColor( slider->color( QskSlider::Fill ) );
}
fillNode->setRect( fillRect );
fillNode->markDirty( QSGNode::DirtyForceUpdate );
return fillNode;
}
QSGNode* SliderSkinlet::updateHandleNode(
const QskSlider* slider, QSGNode* node ) const
{
const QRectF handleRect = subControlRect( slider, QskSlider::Handle );
if ( handleRect.isEmpty() )
return nullptr;
const QRectF fillRect = subControlRect( slider, QskSlider::Fill );
auto handleNode = static_cast< HandleNode* >( node );
if ( handleNode == nullptr )
handleNode = new HandleNode();
handleNode->update( handleRect, fillRect.right(),
slider->color( QskSlider::Handle ) );
// finally the value label
auto labelNode = static_cast< QSGTransformNode* >( handleNode->firstChild() );
if ( labelNode == nullptr )
{
labelNode = new QSGTransformNode;
handleNode->appendChildNode( labelNode );
}
QFont font( QStringLiteral( "Roboto" ) );
font.setPixelSize( 26 );
QskPlainTextRenderer renderer;
renderer.setFont( font );
renderer.setAlignment( Qt::AlignHCenter );
renderer.updateNode( slider, handleRect.size(),
QString::number( slider->value(), 'f', 0 ),
labelNode, Qt::white, Qsk::Normal, Qt::transparent );
#if 1
const qreal h = QFontMetrics( font ).height();
qreal y = handleRect.bottom() - 0.5 * ( handleRect.height() - qskPeak + h );
QMatrix4x4 matrix;
matrix.translate( handleRect.x(), y );
labelNode->setMatrix( matrix );
#endif
return handleNode;
}