QskSlider: Add stop indicators

... as required by M3

Resolves #391
This commit is contained in:
Peter Hartmann 2024-05-13 16:59:47 +02:00
parent 3fdd72c5a3
commit f04dedd30a
6 changed files with 211 additions and 56 deletions

View File

@ -865,8 +865,14 @@ void Editor::setupSlider()
setBoxBorderMetrics( Q::Handle, 0 );
const QSizeF handleSize( 4_dp, 44_dp );
const QSizeF handleSizeFocusedPressed( 2_dp, 44_dp );
setStrutSize( Q::Handle | A::Horizontal, handleSize );
setStrutSize( Q::Handle | A::Horizontal, handleSizeFocusedPressed,
{ QskStateCombination::Combination, Q::Focused | Q::Pressed } );
setStrutSize( Q::Handle | A::Vertical, handleSize.transposed() );
setStrutSize( Q::Handle | A::Vertical, handleSizeFocusedPressed.transposed(),
{ QskStateCombination::Combination, Q::Focused | Q::Pressed } );
setGradient( Q::Handle, m_pal.primary );
setGradient( Q::Handle | Q::Pressed, m_pal.primary );
@ -874,6 +880,23 @@ void Editor::setupSlider()
const auto disabledColor = flattenedColor( m_pal.onSurface, m_pal.background, 0.38 );
setGradient( Q::Handle | Q::Disabled, disabledColor );
for( auto indicator : { Q::GrooveStopIndicators, Q::FillStopIndicators } )
{
setStrutSize( indicator, { 4_dp, 4_dp } );
setBoxShape( indicator, 100, Qt::RelativeSize );
}
const auto p = 6_dp;
setPadding( Q::GrooveStopIndicators | A::Horizontal, { p, 0, p, 0 } );
setPadding( Q::GrooveStopIndicators | A::Vertical, { 0, p, 0, p } );
setPadding( Q::FillStopIndicators | A::Horizontal, { p, 0, p, 0 } );
setPadding( Q::FillStopIndicators | A::Vertical, { 0, p, 0, p } );
setGradient( Q::GrooveStopIndicators, m_pal.primary );
setGradient( Q::GrooveStopIndicators | Q::Disabled, m_pal.onSurface );
setGradient( Q::FillStopIndicators, m_pal.secondaryContainer );
setGradient( Q::FillStopIndicators | Q::Disabled, m_pal.inverseOnSurface );
for( const auto state : { Q::Focused, Q::Pressed } )
{
setStrutSize( Q::LabelContainer | state, 48_dp, 44_dp,

View File

@ -74,8 +74,19 @@ InputPage::InputPage( QQuickItem* parent )
: Page( Qt::Horizontal, parent )
{
auto sliderH = new Slider( Qt::Horizontal );
auto discreteSliderH = new Slider( Qt::Horizontal );
discreteSliderH->setSnap( true );
discreteSliderH->setStepSize( 10 );
discreteSliderH->setPageSize( 1 );
auto sliderV = new Slider( Qt::Vertical );
auto discreteSliderV = new Slider( Qt::Vertical );
discreteSliderV->setSnap( true );
discreteSliderV->setStepSize( 10 );
discreteSliderV->setPageSize( 2 );
auto inputBox = new InputBox();
auto gridBox = new QskGridBox( this );
@ -83,8 +94,10 @@ InputPage::InputPage( QQuickItem* parent )
gridBox->setMargins( 30 );
gridBox->addItem( sliderV, 0, 0, -1, 1 );
gridBox->addItem( sliderH, 0, 1, 1, -1 );
gridBox->addItem( inputBox, 1, 1, -1, -1 );
gridBox->addItem( discreteSliderV, 0, 1, -1, 1 );
gridBox->addItem( sliderH, 0, 2, 1, -1 );
gridBox->addItem( discreteSliderH, 1, 2, 1, -1 );
gridBox->addItem( inputBox, 2, 2, -1, -1 );
auto inputs = findChildren< QskBoundedValueInput* >();

View File

@ -15,6 +15,8 @@ QSK_SUBCONTROL( QskSlider, Fill )
QSK_SUBCONTROL( QskSlider, Scale )
QSK_SUBCONTROL( QskSlider, Handle )
QSK_SUBCONTROL( QskSlider, Ripple )
QSK_SUBCONTROL( QskSlider, GrooveStopIndicators )
QSK_SUBCONTROL( QskSlider, FillStopIndicators )
QSK_SUBCONTROL( QskSlider, LabelContainer )
QSK_SUBCONTROL( QskSlider, LabelText )

View File

@ -25,7 +25,8 @@ class QSK_EXPORT QskSlider : public QskBoundedValueInput
using Inherited = QskBoundedValueInput;
public:
QSK_SUBCONTROLS( Panel, Groove, Fill, Scale, Handle, Ripple, LabelContainer, LabelText )
QSK_SUBCONTROLS( Panel, Groove, Fill, Scale, Handle, Ripple, GrooveStopIndicators, FillStopIndicators,
LabelContainer, LabelText )
QSK_STATES( Pressed )
explicit QskSlider( QQuickItem* parent = nullptr );

View File

@ -11,12 +11,15 @@
#include "QskFunctions.h"
#include <QFontMetricsF>
#include <QtMath>
static inline QRectF qskInnerPanelRect(
const QskSlider* slider, const QRectF& contentsRect )
{
using Q = QskSlider;
namespace
{
inline QRectF qskInnerPanelRect(
const QskSlider* slider, const QRectF& contentsRect )
{
#if 1
auto padding = slider->paddingHint( Q::Panel );
padding += slider->boxBorderMetricsHint( Q::Panel ).widths();
@ -30,10 +33,31 @@ static inline QRectF qskInnerPanelRect(
return r;
}
QRectF qskInnerValueRect( const QskSlider* slider, const QRectF& contentsRect )
{
// For M3 the stop indicators have some padding related to the groove (and fill),
// so we use the rect between first and last stop indicator as authoritative for
// indicators, handle etc.
const auto grooveIndicatorMargins = slider->paddingHint( Q::GrooveStopIndicators );
const auto r = qskInnerPanelRect( slider, contentsRect ).marginsRemoved( grooveIndicatorMargins );
return r;
}
}
QskSliderSkinlet::QskSliderSkinlet( QskSkin* skin )
: Inherited( skin )
{
setNodeRoles( { PanelRole, GrooveRole, FillRole, HandleRole, RippleRole, LabelContainerRole, LabelTextRole } );
setNodeRoles( {
PanelRole,
GrooveRole,
FillRole,
FillStopIndicatorsRole,
GrooveStopIndicatorsRole,
HandleRole,
RippleRole,
LabelContainerRole,
LabelTextRole,
} );
}
QskSliderSkinlet::~QskSliderSkinlet()
@ -45,42 +69,42 @@ QRectF QskSliderSkinlet::subControlRect( const QskSkinnable* skinnable,
{
const auto slider = static_cast< const QskSlider* >( skinnable );
if ( subControl == QskSlider::Panel )
if ( subControl == Q::Panel )
{
return panelRect( slider, contentsRect );
}
if ( subControl == QskSlider::Groove )
if ( subControl == Q::Groove )
{
return grooveRect( slider, contentsRect );
}
if ( subControl == QskSlider::Fill )
if ( subControl == Q::Fill )
{
return fillRect( slider, contentsRect );
}
if ( subControl == QskSlider::Handle )
if ( subControl == Q::Handle )
{
return handleRect( slider, contentsRect );
}
if ( subControl == QskSlider::Scale )
if ( subControl == Q::Scale )
{
return scaleRect( slider, contentsRect );
}
if ( subControl == QskSlider::Ripple )
if ( subControl == Q::Ripple )
{
return rippleRect( slider, contentsRect );
}
if ( subControl == QskSlider::LabelContainer )
if ( subControl == Q::LabelContainer )
{
return labelContainerRect( slider, contentsRect );
}
if ( subControl == QskSlider::LabelText )
if ( subControl == Q::LabelText )
{
return labelContainerRect( slider, contentsRect );
}
@ -88,6 +112,68 @@ QRectF QskSliderSkinlet::subControlRect( const QskSkinnable* skinnable,
return Inherited::subControlRect( skinnable, contentsRect, subControl );
}
int QskSliderSkinlet::sampleCount( const QskSkinnable* skinnable, QskAspect::Subcontrol subControl ) const
{
const auto slider = static_cast< const QskSlider* >( skinnable );
if( slider->snap() )
{
const auto num = qCeil( slider->boundaryLength() / slider->stepSize() ) + 1;
return num;
}
else
{
return ( subControl == Q::GrooveStopIndicators ) ? 1 : 0;
}
}
QRectF QskSliderSkinlet::sampleRect( const QskSkinnable* skinnable, const QRectF& contentsRect,
QskAspect::Subcontrol subControl, int index ) const
{
const auto slider = static_cast< const QskSlider* >( skinnable );
auto r = qskInnerValueRect( slider, contentsRect );
const auto size = slider->strutSizeHint( subControl );
const auto filledPoints = qFloor( ( slider->value() - slider->minimum() ) / slider->stepSize() );
if( slider->snap())
{
if( slider->snap()
&& ( ( index >= filledPoints && subControl == Q::FillStopIndicators )
|| ( index < filledPoints && subControl == Q::GrooveStopIndicators ) ) )
{
return {};
}
}
const auto pos = slider->snap() ? slider->minimum() + index * slider->stepSize() : slider->maximum();
if( slider->orientation() == Qt::Horizontal )
{
r.setTop( r.center().y() - size.height() / 2 );
const auto x = r.left() + slider->valueAsRatio( pos ) * r.width() - size.width() / 2;
r.setLeft( x );
}
else
{
r.setLeft( r.center().x() - size.width() / 2 );
const auto y = r.bottom() - slider->valueAsRatio( pos ) * r.height() - size.height() / 2;
r.setTop( y );
}
r.setHeight( size.height() );
r.setWidth( size.width() );
return r;
}
QskAspect::States QskSliderSkinlet::sampleStates( const QskSkinnable*, QskAspect::Subcontrol, int ) const
{
return {};
}
QSGNode* QskSliderSkinlet::updateSubNode(
const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const
{
@ -97,53 +183,72 @@ QSGNode* QskSliderSkinlet::updateSubNode(
{
case PanelRole:
{
return updateBoxNode( slider, node, QskSlider::Panel );
return updateBoxNode( slider, node, Q::Panel );
}
case GrooveRole:
{
return updateBoxNode( slider, node, QskSlider::Groove );
return updateBoxNode( slider, node, Q::Groove );
}
case FillRole:
{
return updateBoxNode( slider, node, QskSlider::Fill );
return updateBoxNode( slider, node, Q::Fill );
}
case GrooveStopIndicatorsRole:
{
return updateSeriesNode( slider, Q::GrooveStopIndicators, node );
}
case FillStopIndicatorsRole:
{
return updateSeriesNode( slider, Q::FillStopIndicators, node );
}
case HandleRole:
{
return updateBoxNode( slider, node, QskSlider::Handle );
return updateBoxNode( slider, node, Q::Handle );
}
case RippleRole:
{
return updateBoxNode( slider, node, QskSlider::Ripple );
return updateBoxNode( slider, node, Q::Ripple );
}
case LabelContainerRole:
{
return updateBoxNode( slider, node, QskSlider::LabelContainer );
return updateBoxNode( slider, node, Q::LabelContainer );
}
case LabelTextRole:
{
const auto text = labelValue( slider );
return updateTextNode( slider, node, text, QskSlider::LabelText );
return updateTextNode( slider, node, text, Q::LabelText );
}
}
return Inherited::updateSubNode( skinnable, nodeRole, node );
}
QSGNode* QskSliderSkinlet::updateSampleNode( const QskSkinnable* skinnable,
QskAspect::Subcontrol subControl, int index, QSGNode* node ) const
{
const auto slider = static_cast< const QskSlider* >( skinnable );
const auto rect = sampleRect( slider, slider->contentsRect(), subControl, index );
return updateBoxNode( skinnable, node, rect, subControl );
}
QRectF QskSliderSkinlet::panelRect(
const QskSlider* slider, const QRectF& contentsRect ) const
{
auto r = contentsRect;
const qreal size = slider->metric( QskSlider::Panel | QskAspect::Size ); // 0: no hint
const qreal size = slider->metric( Q::Panel | QskAspect::Size ); // 0: no hint
if ( size > 0 && size < r.height() )
{
const auto alignment = slider->alignmentHint( QskSlider::Panel );
const auto alignment = slider->alignmentHint( Q::Panel );
if ( slider->orientation() == Qt::Horizontal )
r = qskAlignedRectF( r, r.width(), size, alignment & Qt::AlignVertical_Mask );
@ -187,17 +292,17 @@ QRectF QskSliderSkinlet::grooveRect(
const QskSlider* slider, const QRectF& contentsRect ) const
{
const auto r = qskInnerPanelRect( slider, contentsRect );
auto grooveRect = innerRect( slider, contentsRect, QskSlider::Groove );
const auto pos = qBound( 0.0, slider->handlePosition(), 1.0 );
auto grooveRect = innerRect( slider, contentsRect, Q::Groove );
const auto handleRect = slider->subControlRect( Q::Handle );
if ( slider->orientation() == Qt::Horizontal )
{
grooveRect.setLeft( r.left() + pos * r.width() );
grooveRect.setLeft( handleRect.right() );
grooveRect.setRight( r.right() );
}
else
{
grooveRect.setBottom( r.bottom() - pos * r.height() );
grooveRect.setBottom( handleRect.top() );
grooveRect.setTop( r.top() );
}
@ -207,25 +312,25 @@ QRectF QskSliderSkinlet::grooveRect(
QRectF QskSliderSkinlet::scaleRect(
const QskSlider* slider, const QRectF& rect ) const
{
return innerRect( slider, rect, QskSlider::Groove );
return innerRect( slider, rect, Q::Groove );
}
QRectF QskSliderSkinlet::fillRect(
const QskSlider* slider, const QRectF& contentsRect ) const
{
const auto r = qskInnerPanelRect( slider, contentsRect );
const auto pos = qBound( 0.0, slider->handlePosition(), 1.0 );
const auto handleRect = slider->subControlRect( Q::Handle );
auto fillRect = innerRect( slider, contentsRect, QskSlider::Fill );
auto fillRect = innerRect( slider, contentsRect, Q::Fill );
if ( slider->orientation() == Qt::Horizontal )
{
fillRect.setLeft( r.left() );
fillRect.setRight( r.left() + pos * r.width() );
fillRect.setRight( handleRect.left() );
}
else
{
fillRect.setBottom( r.bottom() );
fillRect.setTop( r.bottom() - pos * r.height() );
fillRect.setTop( handleRect.bottom() );
}
return fillRect;
@ -234,12 +339,10 @@ QRectF QskSliderSkinlet::fillRect(
QRectF QskSliderSkinlet::handleRect(
const QskSlider* slider, const QRectF& contentsRect ) const
{
using Q = QskSlider;
auto handleSize = slider->strutSizeHint( Q::Handle );
const auto pos = qBound( 0.0, slider->handlePosition(), 1.0 );
const auto r = qskInnerPanelRect( slider, contentsRect );
const auto r = qskInnerValueRect( slider, contentsRect );
auto center = r.center();
if ( slider->orientation() == Qt::Horizontal )
@ -274,8 +377,8 @@ QRectF QskSliderSkinlet::handleRect(
QRectF QskSliderSkinlet::rippleRect(
const QskSlider* slider, const QRectF& rect ) const
{
const auto rippleSize = slider->strutSizeHint( QskSlider::Ripple );
const auto handleSize = slider->strutSizeHint( QskSlider::Handle );
const auto rippleSize = slider->strutSizeHint( Q::Ripple );
const auto handleSize = slider->strutSizeHint( Q::Handle );
const auto w = ( rippleSize.width() - handleSize.width() ) / 2;
const auto h = ( rippleSize.height() - handleSize.height() ) / 2;
@ -289,23 +392,23 @@ QRectF QskSliderSkinlet::rippleRect(
QRectF QskSliderSkinlet::labelContainerRect(
const QskSlider* slider, const QRectF& rect ) const
{
auto size = slider->strutSizeHint( QskSlider::LabelContainer );
auto size = slider->strutSizeHint( Q::LabelContainer );
if( size.isEmpty() )
{
return {};
}
QFontMetricsF fm( slider->effectiveFont( QskSlider::LabelText ) );
QFontMetricsF fm( slider->effectiveFont( Q::LabelText ) );
const auto w = qskHorizontalAdvance( fm, labelValue( slider ) );
const auto padding = slider->paddingHint( QskSlider::LabelContainer );
const auto padding = slider->paddingHint( Q::LabelContainer );
const auto h = fm.height() + padding.top() + padding.bottom();
size = size.expandedTo( { w, h } );
const auto hr = subControlRect( slider, rect, QskSlider::Handle );
const auto margins = slider->marginHint( QskSlider::LabelContainer );
const auto hr = subControlRect( slider, rect, Q::Handle );
const auto margins = slider->marginHint( Q::LabelContainer );
qreal x, y;
@ -331,10 +434,10 @@ QSizeF QskSliderSkinlet::sizeHint( const QskSkinnable* skinnable,
if ( which != Qt::PreferredSize )
return QSizeF();
const auto panelHint = skinnable->strutSizeHint( QskSlider::Panel );
const auto grooveHint = skinnable->strutSizeHint( QskSlider::Groove );
const auto fillHint = skinnable->strutSizeHint( QskSlider::Fill );
const auto handleHint = skinnable->strutSizeHint( QskSlider::Handle );
const auto panelHint = skinnable->strutSizeHint( Q::Panel );
const auto grooveHint = skinnable->strutSizeHint( Q::Groove );
const auto fillHint = skinnable->strutSizeHint( Q::Fill );
const auto handleHint = skinnable->strutSizeHint( Q::Handle );
auto hint = panelHint;
hint = hint.expandedTo( grooveHint );

View File

@ -22,6 +22,8 @@ class QSK_EXPORT QskSliderSkinlet : public QskSkinlet
PanelRole,
GrooveRole,
FillRole,
GrooveStopIndicatorsRole,
FillStopIndicatorsRole,
HandleRole,
RippleRole,
LabelContainerRole,
@ -39,10 +41,21 @@ class QSK_EXPORT QskSliderSkinlet : public QskSkinlet
QSizeF sizeHint( const QskSkinnable*,
Qt::SizeHint, const QSizeF& ) const override;
int sampleCount( const QskSkinnable*, QskAspect::Subcontrol ) const override;
QRectF sampleRect( const QskSkinnable*,
const QRectF&, QskAspect::Subcontrol, int index ) const override;
QskAspect::States sampleStates( const QskSkinnable*,
QskAspect::Subcontrol, int ) const override;
protected:
QSGNode* updateSubNode( const QskSkinnable*,
quint8 nodeRole, QSGNode* ) const override;
QSGNode* updateSampleNode( const QskSkinnable*,
QskAspect::Subcontrol, int index, QSGNode* ) const override;
private:
QRectF panelRect( const QskSlider*, const QRectF& ) const;
QRectF grooveRect( const QskSlider*, const QRectF& ) const;