/****************************************************************************** * QSkinny - Copyright (C) The authors * SPDX-License-Identifier: BSD-3-Clause *****************************************************************************/ #include "QskSliderSkinlet.h" #include "QskSlider.h" #include "QskFunctions.h" #include "QskIntervalF.h" #include #include #include // the color of graduation ticks might different, when being on top of the filling QSK_SYSTEM_STATE( QskSliderSkinlet, Filled, QskAspect::FirstUserState >> 1 ) using Q = QskSlider; static inline qreal qskSubcontrolExtent( const QskSkinnable* skinnable, QskAspect::Subcontrol subControl ) { return skinnable->metric( subControl | QskAspect::Size, -1.0 ); } static inline bool qskHasFilling( const QskSlider* slider ) { const auto policy = slider->flagHint< Qsk::Policy >( Q::Fill | QskAspect::Option, Qsk::Always ); switch( policy ) { case Qsk::Never: return false; case Qsk::Maybe: return qskFuzzyCompare( slider->origin(), slider->minimum() ); default: return true; } } static QRectF qskInnerRect( const QskSlider* slider, const QRectF& contentsRect, QskAspect::Subcontrol subControl ) { auto r = slider->subControlContentsRect( contentsRect, Q::Panel ); const qreal extent = qskSubcontrolExtent( slider, subControl ); if ( extent >= 0.0 ) { if ( slider->orientation() == Qt::Horizontal ) { if ( extent < r.height() ) { r.setTop( r.center().y() - 0.5 * extent ); r.setHeight( extent ); } } else { if ( extent < r.width() ) { r.setLeft( r.center().x() - 0.5 * extent ); r.setWidth( extent ); } } } return r; } static inline QPair< qreal, qreal > qskTickSpan( qreal min, qreal max, qreal length ) { if ( length >= 0.0 ) { // using the center of [min,max] min += 0.5 * ( max - min - length ); max = min + length; } return { min, max }; } QskSliderSkinlet::QskSliderSkinlet( QskSkin* skin ) : Inherited( skin ) { setNodeRoles( { PanelRole, GrooveRole, FillRole, TicksRole, HandleRole } ); } QskSliderSkinlet::~QskSliderSkinlet() { } QRectF QskSliderSkinlet::subControlRect( const QskSkinnable* skinnable, const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const { const auto slider = static_cast< const QskSlider* >( skinnable ); if ( subControl == Q::Panel ) return panelRect( slider, contentsRect ); if ( subControl == Q::Groove ) return qskInnerRect( slider, contentsRect, Q::Groove ); if ( subControl == Q::Fill ) return fillRect( slider, contentsRect ); if ( subControl == Q::Scale ) return subControlRect( skinnable, contentsRect, Q::Groove ); if ( subControl == Q::Handle ) return handleRect( slider, contentsRect ); return Inherited::subControlRect( skinnable, contentsRect, subControl ); } QSGNode* QskSliderSkinlet::updateSubNode( const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const { const auto slider = static_cast< const QskSlider* >( skinnable ); switch ( nodeRole ) { case PanelRole: return updateBoxNode( slider, node, Q::Panel ); case GrooveRole: return updateBoxNode( slider, node, Q::Groove ); case FillRole: return updateBoxNode( slider, node, Q::Fill ); case HandleRole: return updateBoxNode( slider, node, Q::Handle ); case TicksRole: return updateSeriesNode( slider, Q::Tick, node ); } return Inherited::updateSubNode( skinnable, nodeRole, node ); } int QskSliderSkinlet::sampleCount( const QskSkinnable* skinnable, QskAspect::Subcontrol subControl ) const { if ( subControl == Q::Tick ) { const auto slider = static_cast< const QskSlider* >( skinnable ); return graduation( slider ).count(); } return Inherited::sampleCount( skinnable, subControl ); } QVariant QskSliderSkinlet::sampleAt( const QskSkinnable* skinnable, QskAspect::Subcontrol subControl, int index ) const { if ( subControl == Q::Tick ) { const auto slider = static_cast< const QskSlider* >( skinnable ); return graduation( slider ).value( index ); } return Inherited::sampleAt( skinnable, subControl, index ); } QskAspect::States QskSliderSkinlet::sampleStates( const QskSkinnable* skinnable, QskAspect::Subcontrol subControl, int index ) const { auto states = Inherited::sampleStates( skinnable, subControl, index ); const auto slider = static_cast< const QskSlider* >( skinnable ); if ( subControl == Q::Tick && qskHasFilling( slider ) ) { const auto tickValue = sampleAt( skinnable, subControl, index ); if ( tickValue.canConvert< qreal >() ) { const auto intv = QskIntervalF::normalized( slider->origin(), slider->value() ); if ( intv.contains( tickValue.value< qreal >() ) ) states |= Filled; } } return states; } QRectF QskSliderSkinlet::sampleRect( const QskSkinnable* skinnable, const QRectF& contentsRect, QskAspect::Subcontrol subControl, int index ) const { if ( subControl == Q::Tick ) { const auto slider = static_cast< const QskSlider* >( skinnable ); return tickRect( slider, contentsRect, index ); } return Inherited::sampleRect( skinnable, contentsRect, subControl, index ); } QSGNode* QskSliderSkinlet::updateSampleNode( const QskSkinnable* skinnable, QskAspect::Subcontrol subControl, int index, QSGNode* node ) const { if ( subControl == Q::Tick ) { const auto slider = static_cast< const QskSlider* >( skinnable ); const auto rect = sampleRect( slider, slider->contentsRect(), subControl, index ); return updateBoxNode( skinnable, node, rect, subControl ); } return Inherited::updateSampleNode( skinnable, subControl, index, node ); } QRectF QskSliderSkinlet::panelRect( const QskSlider* slider, const QRectF& contentsRect ) const { auto r = contentsRect; const qreal extent = qskSubcontrolExtent( slider, Q::Panel ); if ( extent >= 0 && extent < r.height() ) { const auto alignment = slider->alignmentHint( Q::Panel ); if ( slider->orientation() == Qt::Horizontal ) { r = qskAlignedRectF( r, r.width(), extent, alignment & Qt::AlignVertical_Mask ); } else { r = qskAlignedRectF( r, extent, r.height(), alignment & Qt::AlignHorizontal_Mask ); } } return r; } QRectF QskSliderSkinlet::fillRect( const QskSlider* slider, const QRectF& contentsRect ) const { if ( !qskHasFilling( slider ) ) return QRectF(); auto pos1 = slider->valueAsRatio( slider->origin() ); auto pos2 = qBound( 0.0, slider->handlePosition(), 1.0 ); if ( pos1 > pos2 ) qSwap( pos1, pos2 ); auto r = qskInnerRect( slider, contentsRect, QskSlider::Fill ); auto scaleRect = subControlRect( slider, contentsRect, Q::Scale ); if ( slider->orientation() == Qt::Horizontal ) { if ( !qFuzzyIsNull( pos1 ) ) r.setLeft( scaleRect.left() + pos1 * scaleRect.width() ); r.setRight( scaleRect.left() + pos2 * scaleRect.width() ); } else { if ( !qFuzzyIsNull( pos1 ) ) r.setBottom( scaleRect.bottom() - pos1 * scaleRect.height() ); r.setTop( scaleRect.bottom() - pos2 * scaleRect.height() ); } return r; } QRectF QskSliderSkinlet::handleRect( const QskSlider* slider, const QRectF& contentsRect ) const { auto handleSize = slider->strutSizeHint( Q::Handle ); const auto pos = qBound( 0.0, slider->handlePosition(), 1.0 ); const auto r = subControlRect( slider, contentsRect, Q::Scale ); auto center = r.center(); if ( slider->orientation() == Qt::Horizontal ) { if ( handleSize.height() < 0.0 ) handleSize.setHeight( r.height() ); if ( handleSize.width() < 0.0 ) handleSize.setWidth( handleSize.height() ); center.setX( r.left() + pos * r.width() ); } else { if ( handleSize.width() < 0.0 ) handleSize.setWidth( r.width() ); if ( handleSize.height() < 0.0 ) handleSize.setHeight( handleSize.width() ); center.setY( r.bottom() - pos * r.height() ); } QRectF handleRect( 0, 0, handleSize.width(), handleSize.height() ); handleRect.moveCenter( center ); return handleRect; } QRectF QskSliderSkinlet::tickRect( const QskSlider* slider, const QRectF& contentsRect, int index ) const { const auto tickValue = sampleAt( slider, Q::Tick, index ); if ( !tickValue.canConvert< qreal >() ) return QRectF(); const auto tickPos = slider->valueAsRatio( tickValue.value< qreal >() ); const auto r = subControlRect( slider, contentsRect, Q::Scale ); const auto padding = slider->paddingHint( Q::Scale ); const auto size = slider->strutSizeHint( Q::Tick ); if( slider->orientation() == Qt::Horizontal ) { const auto x = tickPos * r.width() - 0.5 * size.width(); const auto span = qskTickSpan( padding.top(), r.height() - padding.bottom(), size.height() ); return QRectF( r.x() + x, r.y() + span.first, size.width(), span.second - span.first ); } else { const auto y = tickPos * r.height() + 0.5 * size.height(); const auto span = qskTickSpan( padding.left(), r.width() - padding.right(), size.width() ); return QRectF( r.x() + span.first, r.bottom() - y, span.second - span.first, size.height() ); } } QSizeF QskSliderSkinlet::sizeHint( const QskSkinnable* skinnable, Qt::SizeHint which, const QSizeF& ) const { if ( which != Qt::PreferredSize ) return QSizeF(); auto extent = qskSubcontrolExtent( skinnable, Q::Panel ); extent = qMax( extent, qskSubcontrolExtent( skinnable, Q::Groove ) ); extent = qMax( extent, qskSubcontrolExtent( skinnable, Q::Fill ) ); const auto slider = static_cast< const QskSlider* >( skinnable ); auto hint = skinnable->strutSizeHint( Q::Handle ); if ( slider->orientation() == Qt::Horizontal ) hint.setHeight( qMax( hint.height(), extent ) ); else hint.setWidth( qMax( hint.width(), extent ) ); return hint; } bool QskSliderSkinlet::hasGraduation( const QskSlider* slider ) const { if ( slider->stepSize() ) { const auto policy = slider->flagHint< Qsk::Policy >( Q::Tick | QskAspect::Option, Qsk::Never ); switch( policy ) { case Qsk::Always: return true; case Qsk::Maybe: return slider->isSnapping(); case Qsk::Never: return false; } } return false; } QVector< qreal > QskSliderSkinlet::graduation( const QskSlider* slider ) const { QVector< qreal > graduation; if ( hasGraduation( slider ) ) { const auto from = slider->minimum(); const auto to = slider->maximum(); auto step = slider->stepSize(); if ( from > to ) step = -step; const auto n = qCeil( ( to - from ) / step ) - 1; graduation.reserve( n ); for ( int i = 1; i <= n; i++ ) graduation += from + i * step; } return graduation; } #include "moc_QskSliderSkinlet.cpp"