From 73610cdb61a6614ad1a318d5ec3a798a45188951 Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Sun, 5 Mar 2023 16:31:55 +0100 Subject: [PATCH] QskRadioBox updated. Adding a new subcontrol that corresponds to a single button ( check indicator + text ), what allows better configuration from the skin --- skins/material3/QskMaterial3Skin.cpp | 26 +++-- skins/squiek/QskSquiekSkin.cpp | 24 +++-- src/controls/QskBoundedRangeInput.cpp | 4 +- src/controls/QskRadioBox.cpp | 60 +++-------- src/controls/QskRadioBox.h | 4 +- src/controls/QskRadioBoxSkinlet.cpp | 150 +++++++++++++------------- src/controls/QskRadioBoxSkinlet.h | 4 +- 7 files changed, 124 insertions(+), 148 deletions(-) diff --git a/skins/material3/QskMaterial3Skin.cpp b/skins/material3/QskMaterial3Skin.cpp index 1fa55aa2..bd48c08b 100644 --- a/skins/material3/QskMaterial3Skin.cpp +++ b/skins/material3/QskMaterial3Skin.cpp @@ -458,16 +458,19 @@ void Editor::setupRadioBox() using A = QskAspect; 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 ); - for ( auto subControl : { Q::Button, Q::Indicator, Q::Ripple } ) + for ( auto subControl : { Q::CheckIndicatorPanel, Q::CheckIndicator, Q::Ripple } ) setBoxShape( subControl, 100, Qt::RelativeSize ); // circular - setBoxBorderMetrics( Q::Button, 2_dp ); - setBoxBorderColors( Q::Button, m_pal.onBackground ); - setPadding( Q::Button, 5_dp ); + setBoxBorderMetrics( Q::CheckIndicatorPanel, 2_dp ); + setBoxBorderColors( Q::CheckIndicatorPanel, m_pal.onBackground ); + setPadding( Q::CheckIndicatorPanel, 5_dp ); + + setGradient( Q::Button, QskGradient() ); setColor( Q::Text, m_pal.onBackground ); setColor( Q::Text | Q::Disabled, m_pal.onSurface38 ); @@ -476,15 +479,16 @@ void Editor::setupRadioBox() setColor( Q::Ripple | Q::Selected, stateLayerColor( m_pal.primary, m_pal.focusOpacity ) ); - setColor( Q::Indicator, Qt::transparent); - setColor( Q::Indicator | Q::Selected, m_pal.primary ); - setColor( Q::Indicator | Q::Selected | Q::Disabled, m_pal.onSurface38 ); + setColor( Q::CheckIndicator, Qt::transparent); + setColor( Q::CheckIndicator | Q::Selected, m_pal.primary ); + setColor( Q::CheckIndicator | Q::Selected | Q::Disabled, m_pal.onSurface38 ); // Selected - setBoxBorderColors( Q::Button | Q::Selected, m_pal.primary ); - setBoxBorderColors( Q::Button | Q::Disabled, m_pal.onSurface38 ); - setBoxBorderColors( Q::Button | Q::Disabled | Q::Selected, m_pal.onSurface38 ); + setBoxBorderColors( Q::CheckIndicatorPanel | Q::Selected, m_pal.primary ); + setBoxBorderColors( Q::CheckIndicatorPanel | Q::Disabled, m_pal.onSurface38 ); + setBoxBorderColors( + Q::CheckIndicatorPanel | Q::Disabled | Q::Selected, m_pal.onSurface38 ); setAnimation( Q::Ripple | A::Metric | A::Position, qskDuration ); } diff --git a/skins/squiek/QskSquiekSkin.cpp b/skins/squiek/QskSquiekSkin.cpp index 2f651c9d..207577cc 100644 --- a/skins/squiek/QskSquiekSkin.cpp +++ b/skins/squiek/QskSquiekSkin.cpp @@ -689,17 +689,21 @@ void Editor::setupRadioBox() using Q = QskRadioBox; 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 - setBoxBorderMetrics( Q::Button, 1_dp ); + setBoxBorderMetrics( Q::CheckIndicatorPanel, 1_dp ); - setBoxBorderColors( Q::Button, m_pal.darker125 ); - setBoxBorderColors( Q::Button | Q::Disabled, m_pal.theme ); + setBoxBorderColors( Q::CheckIndicatorPanel, m_pal.darker125 ); + 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 | Q::Disabled, m_pal.darker200 ); @@ -707,11 +711,11 @@ void Editor::setupRadioBox() setColor( Q::Panel, 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::Indicator | Q::Selected, m_pal.themeForeground ); - setColor( Q::Indicator | Q::Selected | Q::Disabled, m_pal.darker200 ); + setColor( Q::CheckIndicator, Qt::transparent); + setColor( Q::CheckIndicator | Q::Selected, m_pal.themeForeground ); + setColor( Q::CheckIndicator | Q::Selected | Q::Disabled, m_pal.darker200 ); } void Editor::setupDialogButtonBox() diff --git a/src/controls/QskBoundedRangeInput.cpp b/src/controls/QskBoundedRangeInput.cpp index ccd88ecb..6271aa91 100644 --- a/src/controls/QskBoundedRangeInput.cpp +++ b/src/controls/QskBoundedRangeInput.cpp @@ -49,7 +49,7 @@ void QskBoundedRangeInput::setLowerValue( qreal value ) { if ( isComponentComplete() ) { - value = std::min( value, m_range.upperBound() ); + value = qMin( value, m_range.upperBound() ); value = boundedValue( value ); } @@ -65,7 +65,7 @@ void QskBoundedRangeInput::setUpperValue( qreal value ) { if ( isComponentComplete() ) { - value = std::max( m_range.lowerBound(), value ); + value = qMax( m_range.lowerBound(), value ); value = boundedValue( value ); } diff --git a/src/controls/QskRadioBox.cpp b/src/controls/QskRadioBox.cpp index 7eb3790d..1db2a8bf 100644 --- a/src/controls/QskRadioBox.cpp +++ b/src/controls/QskRadioBox.cpp @@ -8,37 +8,10 @@ #include "QskAnimationHint.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, Button ) -QSK_SUBCONTROL( QskRadioBox, Indicator ) +QSK_SUBCONTROL( QskRadioBox, CheckIndicatorPanel ) +QSK_SUBCONTROL( QskRadioBox, CheckIndicator ) QSK_SUBCONTROL( QskRadioBox, Text ) QSK_SUBCONTROL( QskRadioBox, Ripple ) @@ -94,19 +67,19 @@ QRectF QskRadioBox::focusIndicatorRect() const auto skinlet = effectiveSkinlet(); - auto buttonRect = skinlet->sampleRect( this, - rect, QskRadioBox::Button, m_data->focusedIndex ); + const auto panelRect = skinlet->sampleRect( this, + rect, QskRadioBox::CheckIndicatorPanel, m_data->focusedIndex ); - auto y = buttonRect.y(); - auto h = buttonRect.height(); + auto y = panelRect.y(); + auto h = panelRect.height(); - auto textRect = skinlet->sampleRect( this, + const auto textRect = skinlet->sampleRect( this, rect, QskRadioBox::Text, m_data->focusedIndex ); if( textRect.height() > 0.0 ) { - y = std::min( y, textRect.y() ); - h = std::max( h, textRect.height() ); + y = qMin( y, textRect.y() ); + h = qMax( h, textRect.height() ); } return QRectF( rect.x(), y, rect.width(), h ); @@ -122,7 +95,7 @@ QStringList QskRadioBox::options() const return m_data->options; } -QString QskRadioBox::option( int index ) const +QString QskRadioBox::optionAt( int index ) const { return m_data->options.value( index ); } @@ -268,17 +241,8 @@ void QskRadioBox::focusOutEvent( QFocusEvent* event ) int QskRadioBox::indexAt( const QPointF& pos ) const { - const auto skinlet = effectiveSkinlet(); - const auto cr = contentsRect(); - - 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; + return effectiveSkinlet()->sampleIndexAt( this, + contentsRect(), QskRadioBox::Button, pos ); } void QskRadioBox::setFocusedIndex( int index ) diff --git a/src/controls/QskRadioBox.h b/src/controls/QskRadioBox.h index a3eef668..2820e3a0 100644 --- a/src/controls/QskRadioBox.h +++ b/src/controls/QskRadioBox.h @@ -22,7 +22,7 @@ class QSK_EXPORT QskRadioBox : public QskControl using Inherited = QskControl; public: - QSK_SUBCONTROLS( Panel, Button, Indicator, Text, Ripple ) + QSK_SUBCONTROLS( Panel, Button, CheckIndicatorPanel, CheckIndicator, Text, Ripple ) QSK_STATES( Selected, Pressed ) QskRadioBox( QQuickItem* parent = nullptr ); @@ -34,7 +34,7 @@ class QSK_EXPORT QskRadioBox : public QskControl QRectF focusIndicatorRect() const override; QStringList options() const; - QString option( int ) const; + QString optionAt( int ) const; int selectedIndex() const; int pressedIndex() const; diff --git a/src/controls/QskRadioBoxSkinlet.cpp b/src/controls/QskRadioBoxSkinlet.cpp index 0c83432b..5633753e 100644 --- a/src/controls/QskRadioBoxSkinlet.cpp +++ b/src/controls/QskRadioBoxSkinlet.cpp @@ -12,62 +12,34 @@ namespace { - qreal lineHeight( const QskRadioBox* radioBox ) + QSizeF buttonSizeHint( const QskSkinnable* skinnable, + const QFontMetricsF& fm, const QString& text ) { using Q = QskRadioBox; - auto strutHight = qMax( radioBox->strutSizeHint( Q::Button ).height(), - radioBox->strutSizeHint( Q::Text ).height() ); + auto hint = skinnable->strutSizeHint( Q::CheckIndicatorPanel ); - 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 ); - fontHeight += textMargins.top() + textMargins.bottom(); + hint = hint.grownBy( skinnable->paddingHint( Q::Button ) ); + 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 auto options = radioBox->options(); - for( const auto& option : options ) - w = std::max( w, qskHorizontalAdvance( fm, option ) ); - - return w; + return buttonSizeHint( radioBox, fm, radioBox->optionAt( index ) ); } - -#if 1 - inline qreal lineSpacing( const QskRadioBox* ) - { - // skinHint TODO ... - return 10; - } -#endif } QskRadioBoxSkinlet::QskRadioBoxSkinlet( QskSkin* ) { - setNodeRoles( { PanelRole, ButtonRole, IndicatorRole, TextRole, RippleRole } ); + setNodeRoles( { PanelRole, ButtonRole, CheckPanelRole, + CheckIndicatorRole, TextRole, RippleRole } ); } QskRadioBoxSkinlet::~QskRadioBoxSkinlet() @@ -100,8 +72,11 @@ QSGNode* QskRadioBoxSkinlet::updateSubNode( const QskSkinnable* skinnable, case ButtonRole: return updateSeriesNode( skinnable, Q::Button, node ); - case IndicatorRole: - return updateSeriesNode( skinnable, Q::Indicator, node ); + case CheckPanelRole: + return updateSeriesNode( skinnable, Q::CheckIndicatorPanel, node ); + + case CheckIndicatorRole: + return updateSeriesNode( skinnable, Q::CheckIndicator, node ); case TextRole: return updateSeriesNode( skinnable, Q::Text, node ); @@ -134,28 +109,47 @@ QRectF QskRadioBoxSkinlet::rippleRect( if ( !r.isEmpty() ) { - const auto buttonRect = sampleRect( radioBox, rect, Q::Button, index ); - r.moveCenter( buttonRect.center() ); + const auto checkBoxRect = sampleRect( + radioBox, rect, Q::CheckIndicatorPanel, index ); + + r.moveCenter( checkBoxRect.center() ); } 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 { using Q = QskRadioBox; - auto r = lineRect( radioBox, rect, index ); - r = r.marginsRemoved( radioBox->paddingHint( Q::Panel ) ); + auto r = sampleRect( radioBox, rect, Q::Button, index ); + 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 |= radioBox->layoutMirroring() ? Qt::AlignRight : Qt::AlignLeft; - auto size = radioBox->strutSizeHint( Q::Button ); - size = size.grownBy( radioBox->marginHint( Q::Button ) ); + auto size = radioBox->strutSizeHint( Q::CheckIndicatorPanel ); + size = size.grownBy( radioBox->marginHint( Q::CheckIndicatorPanel ) ); return qskAlignedRectF( r, size.width(), size.height(), alignment ); } @@ -165,16 +159,18 @@ QRectF QskRadioBoxSkinlet::textRect( const QskRadioBox* radioBox, { using Q = QskRadioBox; - auto r = lineRect( radioBox, rect, index ); - r = r.marginsRemoved( radioBox->paddingHint( Q::Panel ) ); + auto r = sampleRect( radioBox, rect, Q::Button, index ); + r = radioBox->innerBox( Q::Button, r ); - const auto buttonRect = sampleRect( radioBox, rect, Q::Button, index ); - const auto spacing = lineSpacing( radioBox ); + const auto checkPanelRect = sampleRect( + radioBox, rect, Q::CheckIndicatorPanel, index ); + + const auto spacing = radioBox->spacingHint( Q::Button ); if ( !radioBox->layoutMirroring() ) - r.setLeft( buttonRect.right() + spacing ); + r.setLeft( checkPanelRect.right() + spacing ); else - r.setRight( buttonRect.left() - spacing ); + r.setRight( checkPanelRect.left() - spacing ); return r; } @@ -190,12 +186,15 @@ QRectF QskRadioBoxSkinlet::sampleRect( const QskSkinnable* skinnable, return textRect( radioBox, rect, index ); 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 ); - r = r.marginsRemoved( radioBox->paddingHint( Q::Button ) ); + auto r = sampleRect( radioBox, rect, Q::CheckIndicatorPanel, index ); + r = r.marginsRemoved( radioBox->paddingHint( Q::CheckIndicatorPanel ) ); return r; } @@ -247,10 +246,10 @@ QSGNode* QskRadioBoxSkinlet::updateSampleNode( const QskSkinnable* skinnable, alignment |= radioBox->layoutMirroring() ? Qt::AlignRight : Qt::AlignLeft; 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 node; @@ -261,8 +260,6 @@ QSizeF QskRadioBoxSkinlet::sizeHint( const QskSkinnable* skinnable, { using Q = QskRadioBox; - const auto radioBox = static_cast< const QskRadioBox* >( skinnable ); - if ( which != Qt::PreferredSize ) return QSizeF(); @@ -271,19 +268,24 @@ QSizeF QskRadioBoxSkinlet::sizeHint( const QskSkinnable* skinnable, // 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 ) ); - textSize = textSize.grownBy( skinnable->marginHint( Q::Text ) ); + const QFontMetrics fm( radioBox->effectiveFont( QskRadioBox::Text ) ); - QSizeF buttonSize = skinnable->strutSizeHint( Q::Button ); - buttonSize = buttonSize.grownBy( skinnable->marginHint( Q::Button ) ); + qreal w = 0.0; + 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(); - const qreal h = count * std::max( textSize.height(), buttonSize.height() ) - + ( count - 1 ) * skinnable->spacingHint( Q::Panel ); + w = qMax( w, buttonSize.width() ); + h += buttonSize.height(); + } + + if ( auto count = radioBox->options().count() ) + h += ( count - 1 ) * skinnable->spacingHint( Q::Panel ); QSizeF hint( w, h ); hint = hint.grownBy( skinnable->paddingHint( Q::Panel ) ); diff --git a/src/controls/QskRadioBoxSkinlet.h b/src/controls/QskRadioBoxSkinlet.h index 0c9cdad4..e3de7c44 100644 --- a/src/controls/QskRadioBoxSkinlet.h +++ b/src/controls/QskRadioBoxSkinlet.h @@ -21,7 +21,8 @@ class QSK_EXPORT QskRadioBoxSkinlet : public QskSkinlet { PanelRole, ButtonRole, - IndicatorRole, + CheckPanelRole, + CheckIndicatorRole, TextRole, RippleRole, @@ -54,6 +55,7 @@ class QSK_EXPORT QskRadioBoxSkinlet : public QskSkinlet private: 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 rippleRect( const QskRadioBox*, const QRectF& ) const;