qskinny/examples/gallery/slider/CustomSliderSkinlet.cpp

384 lines
11 KiB
C++
Raw Normal View History

2017-07-21 18:21:34 +02:00
/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* This file may be used under the terms of the 3-clause BSD License
*****************************************************************************/
2020-08-11 17:56:53 +02:00
#include "CustomSliderSkinlet.h"
#include "CustomSlider.h"
2017-07-21 18:21:34 +02:00
#include <QskAspect.h>
#include <QskRgbValue.h>
2018-08-03 08:15:28 +02:00
#include <QskSlider.h>
#include <QskTextColors.h>
2017-10-20 20:26:39 +02:00
#include <QskTextNode.h>
#include <QskTextOptions.h>
2017-07-21 18:21:34 +02:00
#include <QFontMetricsF>
2018-08-03 08:15:28 +02:00
#include <QSGFlatColorMaterial>
2017-07-21 18:21:34 +02:00
#include <cmath>
#if 1
// should be skin hints
2017-12-07 14:58:46 +01:00
static const qreal qskMinorTick = 20;
static const qreal qskMajorTick = 1.5 * qskMinorTick;
static const qreal qskMargin = 20;
static const qreal qskPeak = 10;
2017-07-21 18:21:34 +02:00
2017-12-07 14:58:46 +01:00
static QFont qskLabelFont;
2020-08-11 17:56:53 +02:00
2017-07-21 18:21:34 +02:00
#endif
2020-08-11 17:56:53 +02:00
namespace
2017-07-21 18:21:34 +02:00
{
2020-08-11 17:56:53 +02:00
class TicksNode : public QSGGeometryNode
2017-07-21 18:21:34 +02:00
{
2020-08-11 17:56:53 +02:00
public:
TicksNode( const QColor& color )
: m_geometry( QSGGeometry::defaultAttributes_Point2D(), 0 )
{
2020-10-23 12:51:31 +02:00
#if QT_VERSION >= QT_VERSION_CHECK( 5, 8, 0 )
m_geometry.setDrawingMode( QSGGeometry::DrawLines );
#else
2020-08-11 17:56:53 +02:00
m_geometry.setDrawingMode( GL_LINES );
2020-10-23 12:51:31 +02:00
#endif
2020-08-11 17:56:53 +02:00
m_geometry.setVertexDataPattern( QSGGeometry::StaticPattern );
2017-07-21 18:21:34 +02:00
2020-08-11 17:56:53 +02:00
m_material.setColor( color );
2017-07-21 18:21:34 +02:00
2020-08-11 17:56:53 +02:00
setGeometry( &m_geometry );
setMaterial( &m_material );
}
2017-07-21 18:21:34 +02:00
2020-08-11 17:56:53 +02:00
private:
QSGFlatColorMaterial m_material;
QSGGeometry m_geometry;
};
2017-07-21 18:21:34 +02:00
2020-08-11 17:56:53 +02:00
class HandleNode : public QSGGeometryNode
2017-07-21 18:21:34 +02:00
{
2020-08-11 17:56:53 +02:00
public:
HandleNode()
: m_geometry( QSGGeometry::defaultAttributes_Point2D(), 8 * 2 )
2017-07-21 18:21:34 +02:00
{
2020-08-11 17:56:53 +02:00
setGeometry( &m_geometry );
setMaterial( &m_material );
2017-07-21 18:21:34 +02:00
}
2020-08-11 17:56:53 +02:00
void update( const QRectF& rect, qreal peakPos, const QColor& color )
2017-07-21 18:21:34 +02:00
{
2020-08-11 17:56:53 +02:00
if ( color != m_color )
{
m_material.setColor( color );
2017-07-21 18:21:34 +02:00
2020-08-11 17:56:53 +02:00
m_color = color;
markDirty( QSGNode::DirtyMaterial );
}
2017-07-21 18:21:34 +02:00
2020-08-11 17:56:53 +02:00
if ( rect != m_rect || peakPos != m_peakPos )
{
auto p = reinterpret_cast< QSGGeometry::Point2D* >( m_geometry.vertexData() );
2017-07-21 18:21:34 +02:00
2020-08-11 17:56:53 +02:00
const qreal y0 = rect.y() + qskPeak;
2017-07-21 18:21:34 +02:00
2020-08-11 17:56:53 +02:00
setLine( p, peakPos, peakPos, rect.y() );
setLine( p + 2, peakPos - 5, peakPos + 5, y0 );
2017-07-21 18:21:34 +02:00
2020-08-11 17:56:53 +02:00
// 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_peakPos = peakPos;
markDirty( QSGNode::DirtyGeometry );
}
2017-07-21 18:21:34 +02:00
}
2020-08-11 17:56:53 +02:00
private:
inline void setLine( QSGGeometry::Point2D* points, float x1, float x2, qreal y )
{
points[ 0 ].x = x1;
points[ 0 ].y = y;
2017-07-21 18:21:34 +02:00
2020-08-11 17:56:53 +02:00
points[ 1 ].x = x2;
points[ 1 ].y = y;
}
2017-07-21 18:21:34 +02:00
2020-08-11 17:56:53 +02:00
QRectF m_rect;
qreal m_peakPos;
QColor m_color;
2017-07-21 18:21:34 +02:00
2020-08-11 17:56:53 +02:00
QSGFlatColorMaterial m_material;
QSGGeometry m_geometry;
};
}
2017-07-21 18:21:34 +02:00
2020-08-11 17:56:53 +02:00
CustomSliderSkinlet::CustomSliderSkinlet()
2017-07-21 18:21:34 +02:00
{
qskLabelFont = QFont();
setNodeRoles( { ScaleRole, FillRole, DecorationRole, HandleRole } );
}
2020-08-11 17:56:53 +02:00
QRectF CustomSliderSkinlet::subControlRect( const QskSkinnable* skinnable,
const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const
2017-07-21 18:21:34 +02:00
{
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, contentsRect );
2017-07-21 18:21:34 +02:00
}
else if ( subControl == QskSlider::Handle )
{
return handleRect( slider, contentsRect );
2017-07-21 18:21:34 +02:00
}
2020-08-11 17:56:53 +02:00
else if ( subControl == CustomSlider::Scale )
2017-07-21 18:21:34 +02:00
{
return scaleRect( contentsRect );
2017-07-21 18:21:34 +02:00
}
2020-08-11 17:56:53 +02:00
else if ( subControl == CustomSlider::Decoration )
2017-07-21 18:21:34 +02:00
{
return decorationRect( slider, contentsRect );
2017-07-21 18:21:34 +02:00
}
return Inherited::subControlRect( skinnable, contentsRect, subControl );
2017-07-21 18:21:34 +02:00
}
2020-08-11 17:56:53 +02:00
QSGNode* CustomSliderSkinlet::updateSubNode(
2017-07-21 18:21:34 +02:00
const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const
{
const auto slider = static_cast< const QskSlider* >( skinnable );
2018-08-03 08:15:28 +02:00
switch ( nodeRole )
2017-07-21 18:21:34 +02:00
{
case ScaleRole:
return updateScaleNode( slider, node );
case DecorationRole:
return updateDecorationNode( slider, node );
case FillRole:
2017-10-20 20:26:39 +02:00
return Inherited::updateSubNode( skinnable, nodeRole, node );
2017-07-21 18:21:34 +02:00
case HandleRole:
return updateHandleNode( slider, node );
default:
return nullptr;
}
}
2020-08-11 17:56:53 +02:00
QRectF CustomSliderSkinlet::scaleRect( const QRectF& contentsRect ) const
2017-07-21 18:21:34 +02:00
{
auto r = contentsRect;
2017-07-21 18:21:34 +02:00
r.setX( r.left() + qskMargin );
r.setBottom( r.center().y() );
r.setTop( r.bottom() - qskMajorTick );
r.setWidth( r.width() - qskMargin );
return r;
}
2020-08-11 17:56:53 +02:00
QRectF CustomSliderSkinlet::fillRect(
const QskSlider* slider, const QRectF& contentsRect ) const
2017-07-21 18:21:34 +02:00
{
2020-08-11 17:56:53 +02:00
auto r = subControlRect( slider, contentsRect, CustomSlider::Scale );
2017-07-21 18:21:34 +02:00
r.setTop( r.bottom() - qskMinorTick );
r.setWidth( r.width() * slider->valueAsRatio() );
2017-07-21 18:21:34 +02:00
return r;
}
2020-08-11 17:56:53 +02:00
QRectF CustomSliderSkinlet::decorationRect(
const QskSlider* slider, const QRectF& contentsRect ) const
2017-07-21 18:21:34 +02:00
{
// decoration exceeds scale !!!!
2020-08-11 17:56:53 +02:00
auto r = subControlRect( slider, contentsRect, CustomSlider::Scale );
2017-07-21 18:21:34 +02:00
r.setBottom( r.top() );
r.setTop( r.bottom() - QFontMetricsF( qskLabelFont ).height() );
return r;
}
2020-08-11 17:56:53 +02:00
QRectF CustomSliderSkinlet::handleRect(
const QskSlider* slider, const QRectF& contentsRect ) const
2017-07-21 18:21:34 +02:00
{
const QRectF fillRect = subControlRect( slider, contentsRect, QskSlider::Fill );
2020-08-11 17:56:53 +02:00
const QRectF scaleRect = subControlRect( slider, contentsRect, CustomSlider::Scale );
2017-07-21 18:21:34 +02:00
QRectF handleRect( 0, scaleRect.bottom(), 80, 50 );
handleRect.moveCenter( QPointF( fillRect.right(), handleRect.center().y() ) );
if ( handleRect.left() < contentsRect.left() )
2020-12-17 16:14:56 +01:00
{
2017-07-21 18:21:34 +02:00
handleRect.moveLeft( contentsRect.left() );
2020-12-17 16:14:56 +01:00
}
2017-07-21 18:21:34 +02:00
else if ( handleRect.right() > contentsRect.right() )
2020-12-17 16:14:56 +01:00
{
2017-07-21 18:21:34 +02:00
handleRect.moveRight( contentsRect.right() );
2020-12-17 16:14:56 +01:00
}
2017-07-21 18:21:34 +02:00
return handleRect;
}
2020-08-11 17:56:53 +02:00
QSGNode* CustomSliderSkinlet::updateScaleNode(
2017-07-21 18:21:34 +02:00
const QskSlider* slider, QSGNode* node ) const
{
const auto scaleRect = subControlRect(
2020-08-11 17:56:53 +02:00
slider, slider->contentsRect(), CustomSlider::Scale );
2017-07-21 18:21:34 +02:00
if ( scaleRect.isEmpty() )
return nullptr;
auto ticksNode = static_cast< TicksNode* >( node );
if ( ticksNode == nullptr )
2020-08-11 17:56:53 +02:00
ticksNode = new TicksNode( slider->color( CustomSlider::Scale ) );
2017-07-21 18:21:34 +02:00
const int tickCount = std::floor( slider->boundaryLength() / slider->stepSize() ) + 1;
2017-07-21 18:21:34 +02:00
auto geometry = ticksNode->geometry();
geometry->allocate( tickCount * 2 );
auto vertexData = geometry->vertexDataAsPoint2D();
memset( vertexData, 0, geometry->vertexCount() );
auto stepStride = slider->stepSize() / slider->boundaryLength() * scaleRect.width();
2017-07-21 18:21:34 +02:00
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 )
{
2018-08-03 08:15:28 +02:00
vertexData[ 0 ].set( x, y );
vertexData[ 1 ].set( x, y - ( ( i % 10 ) ? qskMinorTick : qskMajorTick ) );
2017-07-21 18:21:34 +02:00
vertexData += 2;
x += stepStride;
}
geometry->setLineWidth( 1 );
geometry->markVertexDataDirty();
ticksNode->markDirty( QSGNode::DirtyGeometry );
return ticksNode;
}
2020-08-11 17:56:53 +02:00
QSGNode* CustomSliderSkinlet::updateDecorationNode(
2017-07-21 18:21:34 +02:00
const QskSlider* slider, QSGNode* node ) const
{
const QRectF decorationRect = subControlRect(
2020-08-11 17:56:53 +02:00
slider, slider->contentsRect(), CustomSlider::Decoration );
2017-07-21 18:21:34 +02:00
if ( decorationRect.isEmpty() )
return nullptr;
auto decorationNode = static_cast< QSGTransformNode* >( node );
if ( decorationNode == nullptr )
decorationNode = new QSGTransformNode();
const int tickCount = std::floor( slider->boundaryLength() / slider->stepSize() ) + 1;
2017-07-21 18:21:34 +02:00
2017-10-20 20:26:39 +02:00
auto labelNode = static_cast< QskTextNode* >( decorationNode->firstChild() );
2017-07-21 18:21:34 +02:00
auto stepStride = slider->stepSize() / slider->boundaryLength() * decorationRect.width();
2017-07-21 18:21:34 +02:00
auto x = decorationRect.x();
const auto y = decorationRect.y();
for ( int i = 0; i < tickCount; i += 100 )
{
if ( labelNode == nullptr )
{
2017-10-20 20:26:39 +02:00
labelNode = new QskTextNode;
2017-07-21 18:21:34 +02:00
decorationNode->appendChildNode( labelNode );
}
auto labelText = QString::number( slider->minimum() + slider->stepSize() * i, 'f', 0 );
2017-10-20 20:26:39 +02:00
labelNode->setTextData( slider, labelText, QRectF( x, y, 0, 0 ),
2020-08-15 13:29:17 +02:00
qskLabelFont, QskTextOptions(), QskTextColors( QskRgb::Grey700 ),
2017-10-20 20:26:39 +02:00
Qt::AlignHCenter, Qsk::Normal );
2017-07-21 18:21:34 +02:00
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;
}
2020-08-11 17:56:53 +02:00
QSGNode* CustomSliderSkinlet::updateHandleNode(
2017-07-21 18:21:34 +02:00
const QskSlider* slider, QSGNode* node ) const
{
2020-12-17 16:14:56 +01:00
const auto cr = slider->contentsRect();
const auto handleRect = subControlRect( slider, cr, QskSlider::Handle );
2017-07-21 18:21:34 +02:00
if ( handleRect.isEmpty() )
return nullptr;
2020-12-17 16:14:56 +01:00
const auto fillRect = subControlRect( slider, cr, QskSlider::Fill );
2017-07-21 18:21:34 +02:00
auto handleNode = static_cast< HandleNode* >( node );
if ( handleNode == nullptr )
handleNode = new HandleNode();
2020-12-17 16:14:56 +01:00
const auto handleColor = slider->color( QskSlider::Handle );
handleNode->update( handleRect, fillRect.right(), handleColor );
2017-07-21 18:21:34 +02:00
// finally the value label
2017-10-20 20:26:39 +02:00
auto labelNode = static_cast< QskTextNode* >( handleNode->firstChild() );
2018-08-03 08:15:28 +02:00
if ( labelNode == nullptr )
2017-07-21 18:21:34 +02:00
{
2017-10-20 20:26:39 +02:00
labelNode = new QskTextNode;
2017-07-21 18:21:34 +02:00
handleNode->appendChildNode( labelNode );
}
QFont font( QStringLiteral( "Roboto" ) );
font.setPixelSize( 26 );
const qreal h = QFontMetrics( font ).height();
2017-10-20 20:26:39 +02:00
auto textRect = handleRect;
textRect.setTop( textRect.bottom() - 0.5 * ( textRect.height() - qskPeak + h ) );
2020-12-17 16:14:56 +01:00
const auto text = QString::number( slider->value(), 'f', 0 );
2017-10-20 20:26:39 +02:00
2018-08-03 08:15:28 +02:00
labelNode->setTextData( slider, text, textRect, font, QskTextOptions(),
QskTextColors( Qt::white ), Qt::AlignHCenter, Qsk::Normal );
2017-07-21 18:21:34 +02:00
return handleNode;
}
QSizeF CustomSliderSkinlet::sizeHint( const QskSkinnable* skinnable,
Qt::SizeHint which, const QSizeF& constraint ) const
{
auto size = Inherited::sizeHint( skinnable, which, constraint );
if ( which == Qt::PreferredSize && size.height() >= 0 )
size.rheight() += 40;
return size;
}