QskRadioBox updated. Adding a new subcontrol that corresponds to a

single button ( check indicator + text ), what allows better
configuration from the skin
This commit is contained in:
Uwe Rathmann 2023-03-05 16:31:55 +01:00
parent 75d9b4fd31
commit 73610cdb61
7 changed files with 124 additions and 148 deletions

View File

@ -458,16 +458,19 @@ void Editor::setupRadioBox()
using A = QskAspect; using A = QskAspect;
setSpacing( Q::Panel, 10_dp ); setSpacing( Q::Panel, 10_dp );
setSpacing( Q::Button, 10_dp );
setStrutSize( Q::Button, 20_dp, 20_dp ); setStrutSize( Q::CheckIndicatorPanel, 20_dp, 20_dp );
setStrutSize( Q::Ripple, 40_dp, 40_dp ); setStrutSize( Q::Ripple, 40_dp, 40_dp );
for ( auto subControl : { Q::Button, Q::Indicator, Q::Ripple } ) for ( auto subControl : { Q::CheckIndicatorPanel, Q::CheckIndicator, Q::Ripple } )
setBoxShape( subControl, 100, Qt::RelativeSize ); // circular setBoxShape( subControl, 100, Qt::RelativeSize ); // circular
setBoxBorderMetrics( Q::Button, 2_dp ); setBoxBorderMetrics( Q::CheckIndicatorPanel, 2_dp );
setBoxBorderColors( Q::Button, m_pal.onBackground ); setBoxBorderColors( Q::CheckIndicatorPanel, m_pal.onBackground );
setPadding( Q::Button, 5_dp ); setPadding( Q::CheckIndicatorPanel, 5_dp );
setGradient( Q::Button, QskGradient() );
setColor( Q::Text, m_pal.onBackground ); setColor( Q::Text, m_pal.onBackground );
setColor( Q::Text | Q::Disabled, m_pal.onSurface38 ); setColor( Q::Text | Q::Disabled, m_pal.onSurface38 );
@ -476,15 +479,16 @@ void Editor::setupRadioBox()
setColor( Q::Ripple | Q::Selected, setColor( Q::Ripple | Q::Selected,
stateLayerColor( m_pal.primary, m_pal.focusOpacity ) ); stateLayerColor( m_pal.primary, m_pal.focusOpacity ) );
setColor( Q::Indicator, Qt::transparent); setColor( Q::CheckIndicator, Qt::transparent);
setColor( Q::Indicator | Q::Selected, m_pal.primary ); setColor( Q::CheckIndicator | Q::Selected, m_pal.primary );
setColor( Q::Indicator | Q::Selected | Q::Disabled, m_pal.onSurface38 ); setColor( Q::CheckIndicator | Q::Selected | Q::Disabled, m_pal.onSurface38 );
// Selected // Selected
setBoxBorderColors( Q::Button | Q::Selected, m_pal.primary ); setBoxBorderColors( Q::CheckIndicatorPanel | Q::Selected, m_pal.primary );
setBoxBorderColors( Q::Button | Q::Disabled, m_pal.onSurface38 ); setBoxBorderColors( Q::CheckIndicatorPanel | Q::Disabled, m_pal.onSurface38 );
setBoxBorderColors( Q::Button | Q::Disabled | Q::Selected, m_pal.onSurface38 ); setBoxBorderColors(
Q::CheckIndicatorPanel | Q::Disabled | Q::Selected, m_pal.onSurface38 );
setAnimation( Q::Ripple | A::Metric | A::Position, qskDuration ); setAnimation( Q::Ripple | A::Metric | A::Position, qskDuration );
} }

View File

@ -689,17 +689,21 @@ void Editor::setupRadioBox()
using Q = QskRadioBox; using Q = QskRadioBox;
setSpacing( Q::Panel, 10_dp ); setSpacing( Q::Panel, 10_dp );
setStrutSize( Q::Button, 20_dp, 20_dp ); setSpacing( Q::Button, 10_dp );
for ( auto subControl : { Q::Button, Q::Indicator, Q::Ripple } ) setStrutSize( Q::CheckIndicatorPanel, 20_dp, 20_dp );
for ( auto subControl : { Q::CheckIndicatorPanel, Q::CheckIndicator, Q::Ripple } )
setBoxShape( subControl, 100, Qt::RelativeSize ); // circular setBoxShape( subControl, 100, Qt::RelativeSize ); // circular
setBoxBorderMetrics( Q::Button, 1_dp ); setBoxBorderMetrics( Q::CheckIndicatorPanel, 1_dp );
setBoxBorderColors( Q::Button, m_pal.darker125 ); setBoxBorderColors( Q::CheckIndicatorPanel, m_pal.darker125 );
setBoxBorderColors( Q::Button | Q::Disabled, m_pal.theme ); setBoxBorderColors( Q::CheckIndicatorPanel | Q::Disabled, m_pal.theme );
setPadding( Q::Button, 5_dp ); setPadding( Q::CheckIndicatorPanel, 5_dp );
setGradient( Q::Button, QskGradient() );
setColor( Q::Text, m_pal.themeForeground ); setColor( Q::Text, m_pal.themeForeground );
setColor( Q::Text | Q::Disabled, m_pal.darker200 ); setColor( Q::Text | Q::Disabled, m_pal.darker200 );
@ -707,11 +711,11 @@ void Editor::setupRadioBox()
setColor( Q::Panel, m_pal.lighter125 ); setColor( Q::Panel, m_pal.lighter125 );
setColor( Q::Panel | Q::Disabled, m_pal.lighter125 ); setColor( Q::Panel | Q::Disabled, m_pal.lighter125 );
setColor( Q::Button | Q::Disabled, m_pal.lighter110 ); setColor( Q::CheckIndicatorPanel | Q::Disabled, m_pal.lighter110 );
setColor( Q::Indicator, Qt::transparent); setColor( Q::CheckIndicator, Qt::transparent);
setColor( Q::Indicator | Q::Selected, m_pal.themeForeground ); setColor( Q::CheckIndicator | Q::Selected, m_pal.themeForeground );
setColor( Q::Indicator | Q::Selected | Q::Disabled, m_pal.darker200 ); setColor( Q::CheckIndicator | Q::Selected | Q::Disabled, m_pal.darker200 );
} }
void Editor::setupDialogButtonBox() void Editor::setupDialogButtonBox()

View File

@ -49,7 +49,7 @@ void QskBoundedRangeInput::setLowerValue( qreal value )
{ {
if ( isComponentComplete() ) if ( isComponentComplete() )
{ {
value = std::min( value, m_range.upperBound() ); value = qMin( value, m_range.upperBound() );
value = boundedValue( value ); value = boundedValue( value );
} }
@ -65,7 +65,7 @@ void QskBoundedRangeInput::setUpperValue( qreal value )
{ {
if ( isComponentComplete() ) if ( isComponentComplete() )
{ {
value = std::max( m_range.lowerBound(), value ); value = qMax( m_range.lowerBound(), value );
value = boundedValue( value ); value = boundedValue( value );
} }

View File

@ -8,37 +8,10 @@
#include "QskAnimationHint.h" #include "QskAnimationHint.h"
#include "QskSkinlet.h" #include "QskSkinlet.h"
#if 1
/*
Maybe we should have the following subControls:
- Text
- IndicatorPanel
- Indicator
- Button: IndicatorPanel + Text ( a complete line )
- Ripple
- Panel
Then we can define spacings/margins for Button and
optional borders/colors - even if radio buttons usually don't have this
Then selection can then be done by:
effectiveSkinlet()->sampleIndexAt( this,
contentsRect(), QskRadioBox::Button, pos );
The focusRectangle can be found by:
effectiveSkinlet()->sampleRect( this,
contentsRect(), QskRadioBox::Button, index );
TODO ...
*/
#endif
QSK_SUBCONTROL( QskRadioBox, Panel ) QSK_SUBCONTROL( QskRadioBox, Panel )
QSK_SUBCONTROL( QskRadioBox, Button ) QSK_SUBCONTROL( QskRadioBox, Button )
QSK_SUBCONTROL( QskRadioBox, Indicator ) QSK_SUBCONTROL( QskRadioBox, CheckIndicatorPanel )
QSK_SUBCONTROL( QskRadioBox, CheckIndicator )
QSK_SUBCONTROL( QskRadioBox, Text ) QSK_SUBCONTROL( QskRadioBox, Text )
QSK_SUBCONTROL( QskRadioBox, Ripple ) QSK_SUBCONTROL( QskRadioBox, Ripple )
@ -94,19 +67,19 @@ QRectF QskRadioBox::focusIndicatorRect() const
auto skinlet = effectiveSkinlet(); auto skinlet = effectiveSkinlet();
auto buttonRect = skinlet->sampleRect( this, const auto panelRect = skinlet->sampleRect( this,
rect, QskRadioBox::Button, m_data->focusedIndex ); rect, QskRadioBox::CheckIndicatorPanel, m_data->focusedIndex );
auto y = buttonRect.y(); auto y = panelRect.y();
auto h = buttonRect.height(); auto h = panelRect.height();
auto textRect = skinlet->sampleRect( this, const auto textRect = skinlet->sampleRect( this,
rect, QskRadioBox::Text, m_data->focusedIndex ); rect, QskRadioBox::Text, m_data->focusedIndex );
if( textRect.height() > 0.0 ) if( textRect.height() > 0.0 )
{ {
y = std::min( y, textRect.y() ); y = qMin( y, textRect.y() );
h = std::max( h, textRect.height() ); h = qMax( h, textRect.height() );
} }
return QRectF( rect.x(), y, rect.width(), h ); return QRectF( rect.x(), y, rect.width(), h );
@ -122,7 +95,7 @@ QStringList QskRadioBox::options() const
return m_data->options; return m_data->options;
} }
QString QskRadioBox::option( int index ) const QString QskRadioBox::optionAt( int index ) const
{ {
return m_data->options.value( index ); return m_data->options.value( index );
} }
@ -268,17 +241,8 @@ void QskRadioBox::focusOutEvent( QFocusEvent* event )
int QskRadioBox::indexAt( const QPointF& pos ) const int QskRadioBox::indexAt( const QPointF& pos ) const
{ {
const auto skinlet = effectiveSkinlet(); return effectiveSkinlet()->sampleIndexAt( this,
const auto cr = contentsRect(); contentsRect(), QskRadioBox::Button, pos );
for ( int i = 0; i < m_data->options.size(); i++ )
{
const auto r = skinlet->sampleRect( this, cr, QskRadioBox::Button, i );
if ( r.top() <= pos.y() && r.bottom() >= pos.y() )
return i;
}
return -1;
} }
void QskRadioBox::setFocusedIndex( int index ) void QskRadioBox::setFocusedIndex( int index )

View File

@ -22,7 +22,7 @@ class QSK_EXPORT QskRadioBox : public QskControl
using Inherited = QskControl; using Inherited = QskControl;
public: public:
QSK_SUBCONTROLS( Panel, Button, Indicator, Text, Ripple ) QSK_SUBCONTROLS( Panel, Button, CheckIndicatorPanel, CheckIndicator, Text, Ripple )
QSK_STATES( Selected, Pressed ) QSK_STATES( Selected, Pressed )
QskRadioBox( QQuickItem* parent = nullptr ); QskRadioBox( QQuickItem* parent = nullptr );
@ -34,7 +34,7 @@ class QSK_EXPORT QskRadioBox : public QskControl
QRectF focusIndicatorRect() const override; QRectF focusIndicatorRect() const override;
QStringList options() const; QStringList options() const;
QString option( int ) const; QString optionAt( int ) const;
int selectedIndex() const; int selectedIndex() const;
int pressedIndex() const; int pressedIndex() const;

View File

@ -12,62 +12,34 @@
namespace namespace
{ {
qreal lineHeight( const QskRadioBox* radioBox ) QSizeF buttonSizeHint( const QskSkinnable* skinnable,
const QFontMetricsF& fm, const QString& text )
{ {
using Q = QskRadioBox; using Q = QskRadioBox;
auto strutHight = qMax( radioBox->strutSizeHint( Q::Button ).height(), auto hint = skinnable->strutSizeHint( Q::CheckIndicatorPanel );
radioBox->strutSizeHint( Q::Text ).height() );
const auto textMargins = radioBox->marginHint( Q::Text ); hint.rwidth() += skinnable->spacingHint( Q::Button )
+ qskHorizontalAdvance( fm, text );
hint.rheight() = qMax( hint.height(), fm.height() );
auto fontHeight = radioBox->effectiveFontHeight( Q::Text ); hint = hint.grownBy( skinnable->paddingHint( Q::Button ) );
fontHeight += textMargins.top() + textMargins.bottom(); hint = hint.expandedTo( skinnable->strutSizeHint( Q::Button ) );
return qMax( strutHight, fontHeight ); return hint;
} }
QRectF lineRect( const QskRadioBox* radioBox, const QRectF& rect, int index ) QSizeF buttonSizeHint( const QskRadioBox* radioBox, int index )
{ {
using Q = QskRadioBox;
auto h = lineHeight( radioBox );
auto y = rect.top();
if ( index > 0 )
{
const auto spacing = radioBox->spacingHint( Q::Panel );
y += index * ( h + spacing );
}
return QRectF( rect.x(), y, rect.width(), h );
}
inline qreal maxTextWidth( const QskRadioBox* radioBox )
{
qreal w = 0.0;
const QFontMetrics fm( radioBox->effectiveFont( QskRadioBox::Text ) ); const QFontMetrics fm( radioBox->effectiveFont( QskRadioBox::Text ) );
return buttonSizeHint( radioBox, fm, radioBox->optionAt( index ) );
const auto options = radioBox->options();
for( const auto& option : options )
w = std::max( w, qskHorizontalAdvance( fm, option ) );
return w;
} }
#if 1
inline qreal lineSpacing( const QskRadioBox* )
{
// skinHint TODO ...
return 10;
}
#endif
} }
QskRadioBoxSkinlet::QskRadioBoxSkinlet( QskSkin* ) QskRadioBoxSkinlet::QskRadioBoxSkinlet( QskSkin* )
{ {
setNodeRoles( { PanelRole, ButtonRole, IndicatorRole, TextRole, RippleRole } ); setNodeRoles( { PanelRole, ButtonRole, CheckPanelRole,
CheckIndicatorRole, TextRole, RippleRole } );
} }
QskRadioBoxSkinlet::~QskRadioBoxSkinlet() QskRadioBoxSkinlet::~QskRadioBoxSkinlet()
@ -100,8 +72,11 @@ QSGNode* QskRadioBoxSkinlet::updateSubNode( const QskSkinnable* skinnable,
case ButtonRole: case ButtonRole:
return updateSeriesNode( skinnable, Q::Button, node ); return updateSeriesNode( skinnable, Q::Button, node );
case IndicatorRole: case CheckPanelRole:
return updateSeriesNode( skinnable, Q::Indicator, node ); return updateSeriesNode( skinnable, Q::CheckIndicatorPanel, node );
case CheckIndicatorRole:
return updateSeriesNode( skinnable, Q::CheckIndicator, node );
case TextRole: case TextRole:
return updateSeriesNode( skinnable, Q::Text, node ); return updateSeriesNode( skinnable, Q::Text, node );
@ -134,28 +109,47 @@ QRectF QskRadioBoxSkinlet::rippleRect(
if ( !r.isEmpty() ) if ( !r.isEmpty() )
{ {
const auto buttonRect = sampleRect( radioBox, rect, Q::Button, index ); const auto checkBoxRect = sampleRect(
r.moveCenter( buttonRect.center() ); radioBox, rect, Q::CheckIndicatorPanel, index );
r.moveCenter( checkBoxRect.center() );
} }
return r; return r;
} }
QRectF QskRadioBoxSkinlet::buttonRect( const QskRadioBox* radioBox, QRectF QskRadioBoxSkinlet::buttonRect(
const QskRadioBox* radioBox, const QRectF& rect, int index ) const
{
using Q = QskRadioBox;
/*
code only works when all buttons have the same height
- what might be wron, when lineWrapping is enabled TODO ...
*/
const auto h = buttonSizeHint( radioBox, index ).height();
const auto y = index * ( h + radioBox->spacingHint( Q::Panel ) );
const auto r = radioBox->subControlContentsRect( rect, Q::Panel );
return QRectF( r.left(), r.top() + y, r.width(), h );
}
QRectF QskRadioBoxSkinlet::checkPanelRect( const QskRadioBox* radioBox,
const QRectF& rect, int index ) const const QRectF& rect, int index ) const
{ {
using Q = QskRadioBox; using Q = QskRadioBox;
auto r = lineRect( radioBox, rect, index ); auto r = sampleRect( radioBox, rect, Q::Button, index );
r = r.marginsRemoved( radioBox->paddingHint( Q::Panel ) ); r = radioBox->innerBox( Q::Button, r );
auto alignment = radioBox->alignmentHint( Q::Button, Qt::AlignCenter ); auto alignment = radioBox->alignmentHint( Q::CheckIndicatorPanel, Qt::AlignCenter );
alignment &= Qt::AlignVertical_Mask; alignment &= Qt::AlignVertical_Mask;
alignment |= radioBox->layoutMirroring() ? Qt::AlignRight : Qt::AlignLeft; alignment |= radioBox->layoutMirroring() ? Qt::AlignRight : Qt::AlignLeft;
auto size = radioBox->strutSizeHint( Q::Button ); auto size = radioBox->strutSizeHint( Q::CheckIndicatorPanel );
size = size.grownBy( radioBox->marginHint( Q::Button ) ); size = size.grownBy( radioBox->marginHint( Q::CheckIndicatorPanel ) );
return qskAlignedRectF( r, size.width(), size.height(), alignment ); return qskAlignedRectF( r, size.width(), size.height(), alignment );
} }
@ -165,16 +159,18 @@ QRectF QskRadioBoxSkinlet::textRect( const QskRadioBox* radioBox,
{ {
using Q = QskRadioBox; using Q = QskRadioBox;
auto r = lineRect( radioBox, rect, index ); auto r = sampleRect( radioBox, rect, Q::Button, index );
r = r.marginsRemoved( radioBox->paddingHint( Q::Panel ) ); r = radioBox->innerBox( Q::Button, r );
const auto buttonRect = sampleRect( radioBox, rect, Q::Button, index ); const auto checkPanelRect = sampleRect(
const auto spacing = lineSpacing( radioBox ); radioBox, rect, Q::CheckIndicatorPanel, index );
const auto spacing = radioBox->spacingHint( Q::Button );
if ( !radioBox->layoutMirroring() ) if ( !radioBox->layoutMirroring() )
r.setLeft( buttonRect.right() + spacing ); r.setLeft( checkPanelRect.right() + spacing );
else else
r.setRight( buttonRect.left() - spacing ); r.setRight( checkPanelRect.left() - spacing );
return r; return r;
} }
@ -190,12 +186,15 @@ QRectF QskRadioBoxSkinlet::sampleRect( const QskSkinnable* skinnable,
return textRect( radioBox, rect, index ); return textRect( radioBox, rect, index );
if( subControl == Q::Button ) if( subControl == Q::Button )
return buttonRect( radioBox, rect, index); return buttonRect( radioBox, rect, index );
if( subControl == Q::Indicator ) if( subControl == Q::CheckIndicatorPanel )
return checkPanelRect( radioBox, rect, index );
if( subControl == Q::CheckIndicator )
{ {
auto r = sampleRect( radioBox, rect, Q::Button, index ); auto r = sampleRect( radioBox, rect, Q::CheckIndicatorPanel, index );
r = r.marginsRemoved( radioBox->paddingHint( Q::Button ) ); r = r.marginsRemoved( radioBox->paddingHint( Q::CheckIndicatorPanel ) );
return r; return r;
} }
@ -247,10 +246,10 @@ QSGNode* QskRadioBoxSkinlet::updateSampleNode( const QskSkinnable* skinnable,
alignment |= radioBox->layoutMirroring() ? Qt::AlignRight : Qt::AlignLeft; alignment |= radioBox->layoutMirroring() ? Qt::AlignRight : Qt::AlignLeft;
return updateTextNode( radioBox, node, rect, return updateTextNode( radioBox, node, rect,
alignment, radioBox->option( index ), subcontrol ); alignment, radioBox->optionAt( index ), subcontrol );
} }
if ( subcontrol == Q::Button || subcontrol == Q::Indicator ) if ( subcontrol == Q::CheckIndicatorPanel || subcontrol == Q::CheckIndicator )
return updateBoxNode( radioBox, node, rect, subcontrol ); return updateBoxNode( radioBox, node, rect, subcontrol );
return node; return node;
@ -261,8 +260,6 @@ QSizeF QskRadioBoxSkinlet::sizeHint( const QskSkinnable* skinnable,
{ {
using Q = QskRadioBox; using Q = QskRadioBox;
const auto radioBox = static_cast< const QskRadioBox* >( skinnable );
if ( which != Qt::PreferredSize ) if ( which != Qt::PreferredSize )
return QSizeF(); return QSizeF();
@ -271,19 +268,24 @@ QSizeF QskRadioBoxSkinlet::sizeHint( const QskSkinnable* skinnable,
// heightForWidth would make sense when word wrapping is enabled TODO ... // heightForWidth would make sense when word wrapping is enabled TODO ...
} }
QSizeF textSize( maxTextWidth( radioBox ), 0.0 ); const auto radioBox = static_cast< const QskRadioBox* >( skinnable );
textSize = textSize.expandedTo( skinnable->strutSizeHint( Q::Text ) ); const QFontMetrics fm( radioBox->effectiveFont( QskRadioBox::Text ) );
textSize = textSize.grownBy( skinnable->marginHint( Q::Text ) );
QSizeF buttonSize = skinnable->strutSizeHint( Q::Button ); qreal w = 0.0;
buttonSize = buttonSize.grownBy( skinnable->marginHint( Q::Button ) ); qreal h = 0.0;
const auto count = std::max( ( int )radioBox->options().count(), 1 ); const auto options = radioBox->options();
for( const auto& option : options )
{
const auto buttonSize = buttonSizeHint( radioBox, fm, option );
const qreal w = textSize.width() + lineSpacing( radioBox ) + buttonSize.width(); w = qMax( w, buttonSize.width() );
const qreal h = count * std::max( textSize.height(), buttonSize.height() ) h += buttonSize.height();
+ ( count - 1 ) * skinnable->spacingHint( Q::Panel ); }
if ( auto count = radioBox->options().count() )
h += ( count - 1 ) * skinnable->spacingHint( Q::Panel );
QSizeF hint( w, h ); QSizeF hint( w, h );
hint = hint.grownBy( skinnable->paddingHint( Q::Panel ) ); hint = hint.grownBy( skinnable->paddingHint( Q::Panel ) );

View File

@ -21,7 +21,8 @@ class QSK_EXPORT QskRadioBoxSkinlet : public QskSkinlet
{ {
PanelRole, PanelRole,
ButtonRole, ButtonRole,
IndicatorRole, CheckPanelRole,
CheckIndicatorRole,
TextRole, TextRole,
RippleRole, RippleRole,
@ -54,6 +55,7 @@ class QSK_EXPORT QskRadioBoxSkinlet : public QskSkinlet
private: private:
QRectF textRect( const QskRadioBox*, const QRectF&, int ) const; QRectF textRect( const QskRadioBox*, const QRectF&, int ) const;
QRectF checkPanelRect( const QskRadioBox*, const QRectF&, int index ) const;
QRectF buttonRect( const QskRadioBox*, const QRectF&, int index ) const; QRectF buttonRect( const QskRadioBox*, const QRectF&, int index ) const;
QRectF rippleRect( const QskRadioBox*, const QRectF& ) const; QRectF rippleRect( const QskRadioBox*, const QRectF& ) const;