358 lines
9.9 KiB
C++
358 lines
9.9 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 <QskRgbValue.h>
|
|
#include <QskTextNode.h>
|
|
#include <QskTextOptions.h>
|
|
#include <QskTextColors.h>
|
|
|
|
#include <QSGFlatColorMaterial>
|
|
#include <QFontMetricsF>
|
|
|
|
#include <cmath>
|
|
|
|
#if 1
|
|
// should be skin hints
|
|
static const qreal qskMinorTick = 20;
|
|
static const qreal qskMajorTick = 1.5 * qskMinorTick;
|
|
static const qreal qskMargin = 20;
|
|
static const qreal qskPeak = 10;
|
|
|
|
static 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 peakPos, const QColor& color )
|
|
{
|
|
if ( color != m_color )
|
|
{
|
|
m_material.setColor( color );
|
|
|
|
m_color = color;
|
|
markDirty( QSGNode::DirtyMaterial );
|
|
}
|
|
|
|
if ( rect != m_rect || peakPos != m_peakPos )
|
|
{
|
|
QSGGeometry::Point2D* p =
|
|
reinterpret_cast< QSGGeometry::Point2D* >( m_geometry.vertexData() );
|
|
|
|
const qreal y0 = rect.y() + qskPeak;
|
|
|
|
setLine( p, peakPos, peakPos, rect.y() );
|
|
setLine( p + 2, peakPos - 5, peakPos + 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_peakPos = peakPos;
|
|
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_peakPos;
|
|
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 Inherited::updateSubNode( skinnable, nodeRole, 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::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< QskTextNode* >( 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 QskTextNode;
|
|
decorationNode->appendChildNode( labelNode );
|
|
}
|
|
|
|
auto labelText = QString::number( slider->minimum() + slider->stepSize() * i, 'f', 0 );
|
|
|
|
labelNode->setTextData( slider, labelText, QRectF( x, y, 0, 0 ),
|
|
qskLabelFont, QskTextOptions(), QskTextColors( QskRgbValue::Grey700 ),
|
|
Qt::AlignHCenter, Qsk::Normal );
|
|
|
|
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::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< QskTextNode* >( handleNode->firstChild() );
|
|
if ( labelNode == nullptr )
|
|
{
|
|
labelNode = new QskTextNode;
|
|
handleNode->appendChildNode( labelNode );
|
|
}
|
|
|
|
QFont font( QStringLiteral( "Roboto" ) );
|
|
font.setPixelSize( 26 );
|
|
|
|
const qreal h = QFontMetrics( font ).height();
|
|
|
|
auto textRect = handleRect;
|
|
textRect.setTop( textRect.bottom() - 0.5 * ( textRect.height() - qskPeak + h ) );
|
|
|
|
const QString text = QString::number( slider->value(), 'f', 0 );
|
|
|
|
labelNode->setTextData( slider, text, textRect,
|
|
font, QskTextOptions(), QskTextColors( Qt::white ),
|
|
Qt::AlignHCenter, Qsk::Normal );
|
|
|
|
return handleNode;
|
|
}
|