diff --git a/designsystems/material3/QskMaterial3Icons.qrc b/designsystems/material3/QskMaterial3Icons.qrc index 4e55e3be..72725b7a 100644 --- a/designsystems/material3/QskMaterial3Icons.qrc +++ b/designsystems/material3/QskMaterial3Icons.qrc @@ -7,6 +7,8 @@ icons/qvg/combo-box-arrow-closed.qvg icons/qvg/combo-box-arrow-open.qvg icons/qvg/segmented-button-check.qvg + icons/qvg/switchbutton-checked.qvg + icons/qvg/switchbutton-unchecked.qvg diff --git a/designsystems/material3/QskMaterial3Skin.cpp b/designsystems/material3/QskMaterial3Skin.cpp index c440e193..ca8bd5eb 100644 --- a/designsystems/material3/QskMaterial3Skin.cpp +++ b/designsystems/material3/QskMaterial3Skin.cpp @@ -40,7 +40,6 @@ #include #include #include -#include #include #include #include @@ -998,6 +997,8 @@ void Editor::setupSwitchButton() using A = QskAspect; using Q = QskSwitchButton; + const QskStateCombination allStates ( QskStateCombination::CombinationNoState, QskAspect::AllStates ); + setBoxShape( Q::Groove, 100, Qt::RelativeSize ); const QSizeF strutSize( 52_dp, 32_dp ); setStrutSize( Q::Groove | A::Horizontal, strutSize ); @@ -1010,11 +1011,12 @@ void Editor::setupSwitchButton() setGradient( Q::Groove | Q::Checked | Q::Disabled, m_pal.onSurface12 ); setBoxBorderMetrics( Q::Groove, 2_dp ); setBoxBorderColors( Q::Groove, m_pal.outline ); + setBoxBorderColors( Q::Groove | Q::Disabled, m_pal.onSurface12 ); - setBoxBorderMetrics( Q::Groove | Q::Checked, 0 ); + setBoxBorderMetrics( Q::Groove | Q::Checked, 0, allStates ); setBoxShape( Q::Handle, 100, Qt::RelativeSize ); - setStrutSize( Q::Handle, 30_dp, 30_dp ); + setStrutSize( Q::Handle, { 30_dp, 30_dp } ); setMargin( Q::Handle, 7_dp ); setShadowMetrics( Q::Handle, { 17_dp, 0 } ); setShadowColor( Q::Handle, QskRgb::Transparent ); @@ -1022,6 +1024,15 @@ void Editor::setupSwitchButton() setGradient( Q::Handle, m_pal.outline ); setGradient( Q::Handle | Q::Checked, m_pal.onPrimary ); + setStrutSize( Q::Icon, { 16_dp, 16_dp } ); + setPadding( Q::Icon, 6_dp ); + setSymbol( Q::Icon, symbol( "switchbutton-unchecked" ) ); + setSymbol( Q::Icon | Q::Checked, symbol( "switchbutton-checked" ), allStates ); + setGraphicRole( Q::Icon, QskMaterial3Skin::GraphicRoleSurfaceContainerHighest ); + setGraphicRole( Q::Icon | Q::Checked, QskMaterial3Skin::GraphicRoleOnPrimaryContainer, allStates ); + setGraphicRole( Q::Icon | Q::Disabled, QskMaterial3Skin::GraphicRoleSurfaceContainerHighest38, allStates ); + setGraphicRole( Q::Icon | Q::Checked | Q::Disabled, QskMaterial3Skin::GraphicRoleOnSurface38, allStates ); + for ( auto state1 : { A::NoState, Q::Hovered, Q::Focused, Q::Pressed } ) { const qreal opacity = m_pal.stateOpacity( state1 ); @@ -1067,8 +1078,8 @@ void Editor::setupSwitchButton() { auto aspect = Q::Handle | state; - setPosition( aspect, 0.15 ); - setPosition( aspect | Q::Checked, 0.85 ); + setPosition( aspect, 0.10 ); + setPosition( aspect | Q::Checked, 0.9 ); } setAnimation( Q::Handle | A::Color, qskDuration ); @@ -1492,6 +1503,8 @@ QskMaterial3Theme::QskMaterial3Theme( QskSkin::ColorScheme colorScheme, surfaceVariant12 = QskRgb::toTransparentF( surfaceVariant, 0.12 ); + surfaceContainerHighest38 = QskRgb::toTransparentF( surfaceContainerHighest, 0.38 ); + elevation0 = QskShadowMetrics( 0, 0 ); elevation1 = QskShadowMetrics( -2, 9, { 0, 1 } ); elevation2 = QskShadowMetrics( -2, 8, { 0, 2 } ); @@ -1597,6 +1610,7 @@ void QskMaterial3Skin::setGraphicColor( GraphicRole role, QRgb rgb ) void QskMaterial3Skin::setupGraphicFilters( const QskMaterial3Theme& theme ) { setGraphicColor( GraphicRoleOnPrimary, theme.onPrimary ); + setGraphicColor( GraphicRoleOnPrimaryContainer, theme.onPrimaryContainer ); setGraphicColor( GraphicRoleOnSecondaryContainer, theme.onSecondaryContainer ); setGraphicColor( GraphicRoleOnError, theme.onError ); setGraphicColor( GraphicRoleOnSurface, theme.onSurface ); @@ -1604,6 +1618,8 @@ void QskMaterial3Skin::setupGraphicFilters( const QskMaterial3Theme& theme ) setGraphicColor( GraphicRoleOnSurfaceVariant, theme.onSurfaceVariant ); setGraphicColor( GraphicRolePrimary, theme.primary ); setGraphicColor( GraphicRoleSurface, theme.surface ); + setGraphicColor( GraphicRoleSurfaceContainerHighest, theme.surfaceContainerHighest ); + setGraphicColor( GraphicRoleSurfaceContainerHighest38, theme.surfaceContainerHighest38 ); } void QskMaterial3Skin::initHints() diff --git a/designsystems/material3/QskMaterial3Skin.h b/designsystems/material3/QskMaterial3Skin.h index b2790e02..bac26cba 100644 --- a/designsystems/material3/QskMaterial3Skin.h +++ b/designsystems/material3/QskMaterial3Skin.h @@ -80,6 +80,7 @@ class QSK_MATERIAL3_EXPORT QskMaterial3Theme QRgb outlineVariant; QRgb surfaceContainerHighest; + QRgb surfaceContainerHighest38; QRgb shadow; @@ -110,12 +111,15 @@ class QSK_MATERIAL3_EXPORT QskMaterial3Skin : public QskSkin { GraphicRoleOnError, GraphicRoleOnPrimary, + GraphicRoleOnPrimaryContainer, GraphicRoleOnSecondaryContainer, GraphicRoleOnSurface, GraphicRoleOnSurface38, GraphicRoleOnSurfaceVariant, GraphicRolePrimary, GraphicRoleSurface, + GraphicRoleSurfaceContainerHighest, + GraphicRoleSurfaceContainerHighest38, }; QskMaterial3Skin( QObject* parent = nullptr ); diff --git a/designsystems/material3/icons/qvg/switchbutton-checked.qvg b/designsystems/material3/icons/qvg/switchbutton-checked.qvg new file mode 100644 index 00000000..c927ddb3 Binary files /dev/null and b/designsystems/material3/icons/qvg/switchbutton-checked.qvg differ diff --git a/designsystems/material3/icons/qvg/switchbutton-unchecked.qvg b/designsystems/material3/icons/qvg/switchbutton-unchecked.qvg new file mode 100644 index 00000000..b33f61e4 Binary files /dev/null and b/designsystems/material3/icons/qvg/switchbutton-unchecked.qvg differ diff --git a/designsystems/material3/icons/switchbutton-checked.svg b/designsystems/material3/icons/switchbutton-checked.svg new file mode 100644 index 00000000..7f4d9275 --- /dev/null +++ b/designsystems/material3/icons/switchbutton-checked.svg @@ -0,0 +1,4 @@ + + + + diff --git a/designsystems/material3/icons/switchbutton-unchecked.svg b/designsystems/material3/icons/switchbutton-unchecked.svg new file mode 100644 index 00000000..a9e2ee7e --- /dev/null +++ b/designsystems/material3/icons/switchbutton-unchecked.svg @@ -0,0 +1,4 @@ + + + + diff --git a/examples/gallery/button/ButtonPage.cpp b/examples/gallery/button/ButtonPage.cpp index 7ba35788..bc00b4d1 100644 --- a/examples/gallery/button/ButtonPage.cpp +++ b/examples/gallery/button/ButtonPage.cpp @@ -133,13 +133,24 @@ namespace SwitchButtonBox( QQuickItem* parent = nullptr ) : ButtonBox( Qt::Horizontal, parent ) { + setDimension( 6 ); + setSpacing( 20 ); + setDefaultAlignment( Qt::AlignCenter ); + for ( auto orientation : { Qt::Vertical, Qt::Horizontal } ) { - (void) new QskSwitchButton( orientation, this ); + using Q = QskSwitchButton; - auto button = new QskSwitchButton( orientation, this ); - button->setInverted( true ); - button->setChecked( true ); + for( auto iconMode : { Q::NoIcon, Q::ShowIconWhenSelected, Q::ShowIconAlways } ) + { + auto button = new QskSwitchButton( orientation, this ); + button->setIconMode( iconMode ); + + auto invertedButton = new QskSwitchButton( orientation, this ); + invertedButton->setInverted( true ); + invertedButton->setChecked( true ); + invertedButton->setIconMode( iconMode ); + } } } }; diff --git a/src/controls/QskSwitchButton.cpp b/src/controls/QskSwitchButton.cpp index c9988dd2..9d6f3b93 100644 --- a/src/controls/QskSwitchButton.cpp +++ b/src/controls/QskSwitchButton.cpp @@ -5,8 +5,9 @@ #include "QskSwitchButton.h" -QSK_SUBCONTROL( QskSwitchButton, Handle ) QSK_SUBCONTROL( QskSwitchButton, Groove ) +QSK_SUBCONTROL( QskSwitchButton, Handle ) +QSK_SUBCONTROL( QskSwitchButton, Icon ) struct QskSwitchButton::PrivateData { @@ -17,6 +18,7 @@ struct QskSwitchButton::PrivateData bool inverted = false; Qt::Orientation orientation; + IconMode iconMode = NoIcon; }; QskSwitchButton::QskSwitchButton( QQuickItem* parent ) @@ -76,6 +78,20 @@ void QskSwitchButton::setInverted( bool on ) } } +QskSwitchButton::IconMode QskSwitchButton::iconMode() const +{ + return m_data->iconMode; +} + +void QskSwitchButton::setIconMode( IconMode iconMode ) +{ + if( iconMode != m_data->iconMode ) + { + m_data->iconMode = iconMode; + Q_EMIT iconModeChanged( m_data->iconMode ); + } +} + QskAspect::Variation QskSwitchButton::effectiveVariation() const { return static_cast< QskAspect::Variation >( m_data->orientation ); diff --git a/src/controls/QskSwitchButton.h b/src/controls/QskSwitchButton.h index c63835e3..e12cc65f 100644 --- a/src/controls/QskSwitchButton.h +++ b/src/controls/QskSwitchButton.h @@ -21,8 +21,19 @@ class QSK_EXPORT QskSwitchButton : public QskAbstractButton Q_PROPERTY( bool inverted READ isInverted WRITE setInverted NOTIFY invertedChanged FINAL ) + Q_PROPERTY( IconMode iconMode READ iconMode + WRITE setIconMode NOTIFY iconModeChanged FINAL ) + public: - QSK_SUBCONTROLS( Groove, Handle ) + QSK_SUBCONTROLS( Groove, Handle, Icon ) + + enum IconMode + { + NoIcon, + ShowIconWhenSelected, + ShowIconAlways + }; + Q_ENUM( IconMode ) QskSwitchButton( Qt::Orientation, QQuickItem* parent = nullptr ); QskSwitchButton( QQuickItem* parent = nullptr ); @@ -32,16 +43,20 @@ class QSK_EXPORT QskSwitchButton : public QskAbstractButton bool isCheckable() const override final; Qt::Orientation orientation() const; - void setOrientation(Qt::Orientation); + void setOrientation( Qt::Orientation ); bool isInverted() const; void setInverted( bool ); + IconMode iconMode() const; + void setIconMode( IconMode ); + QskAspect::Variation effectiveVariation() const override; Q_SIGNALS: void orientationChanged( Qt::Orientation ); void invertedChanged( bool ); + void iconModeChanged( IconMode ); private: struct PrivateData; diff --git a/src/controls/QskSwitchButtonSkinlet.cpp b/src/controls/QskSwitchButtonSkinlet.cpp index 2a62ac08..471c39d0 100644 --- a/src/controls/QskSwitchButtonSkinlet.cpp +++ b/src/controls/QskSwitchButtonSkinlet.cpp @@ -6,6 +6,8 @@ #include "QskSwitchButtonSkinlet.h" #include "QskSwitchButton.h" +using Q = QskSwitchButton; + static inline qreal qskEffectivePosition( const QskSwitchButton* switchButton ) { auto pos = switchButton->positionHint( QskSwitchButton::Handle ); @@ -23,10 +25,23 @@ static inline qreal qskEffectivePosition( const QskSwitchButton* switchButton ) return pos; } +static QSizeF qskIconSize( const QskSwitchButton* button ) +{ + if( button->iconMode() == Q::NoIcon + || ( button->iconMode() == Q::ShowIconWhenSelected && !button->isChecked() ) ) + { + return {}; + } + else + { + return button->strutSizeHint( Q::Icon ); + } +} + QskSwitchButtonSkinlet::QskSwitchButtonSkinlet( QskSkin* skin ) : Inherited( skin ) { - setNodeRoles( { GrooveRole, HandleRole } ); + setNodeRoles( { GrooveRole, HandleRole, IconRole } ); } QskSwitchButtonSkinlet::~QskSwitchButtonSkinlet() @@ -36,16 +51,21 @@ QskSwitchButtonSkinlet::~QskSwitchButtonSkinlet() QRectF QskSwitchButtonSkinlet::subControlRect( const QskSkinnable* skinnable, const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const { - using Q = QskSwitchButton; - - if ( subControl == Q::Handle ) - { - return handleRect( skinnable, contentsRect ); - } + const auto button = static_cast< const Q* >( skinnable ); if ( subControl == Q::Groove ) { - return grooveRect( skinnable, contentsRect ); + return grooveRect( button, contentsRect ); + } + + if ( subControl == Q::Handle ) + { + return handleRect( button, contentsRect ); + } + + if ( subControl == Q::Icon ) + { + return iconRect( button, contentsRect ); } return Inherited::subControlRect( skinnable, contentsRect, subControl ); @@ -69,13 +89,14 @@ QSizeF QskSwitchButtonSkinlet::sizeHint( const QskSkinnable* skinnable, QSGNode* QskSwitchButtonSkinlet::updateSubNode( const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const { - using Q = QskSwitchButton; - switch ( nodeRole ) { case HandleRole: return updateBoxNode( skinnable, node, Q::Handle ); + case IconRole: + return updateSymbolNode( skinnable, node, Q::Icon ); + case GrooveRole: return updateBoxNode( skinnable, node, Q::Groove ); } @@ -84,19 +105,15 @@ QSGNode* QskSwitchButtonSkinlet::updateSubNode( const QskSkinnable* skinnable, } QRectF QskSwitchButtonSkinlet::grooveRect( - const QskSkinnable* skinnable, const QRectF& contentsRect ) const + const QskSwitchButton* button, const QRectF& contentsRect ) const { - using Q = QskSwitchButton; + auto size = button->strutSizeHint( Q::Groove ); - const auto switchButton = static_cast< const Q* >( skinnable ); - - auto size = skinnable->strutSizeHint( Q::Groove ); - - if ( switchButton->orientation() == Qt::Vertical ) + if ( button->orientation() == Qt::Vertical ) { if ( size.height() < 0.0 ) { - const auto handleSize = skinnable->strutSizeHint( Q::Handle ); + const auto handleSize = button->strutSizeHint( Q::Handle ); size.setHeight( 2 * handleSize.height() ); } } @@ -104,7 +121,7 @@ QRectF QskSwitchButtonSkinlet::grooveRect( { if ( size.width() < 0.0 ) { - const auto handleSize = skinnable->strutSizeHint( Q::Handle ); + const auto handleSize = button->strutSizeHint( Q::Handle ); size.setWidth( 2 * handleSize.width() ); } } @@ -119,19 +136,16 @@ QRectF QskSwitchButtonSkinlet::grooveRect( } QRectF QskSwitchButtonSkinlet::handleRect( - const QskSkinnable* skinnable, const QRectF& contentsRect ) const + const QskSwitchButton* button, const QRectF& contentsRect ) const { - using Q = QskSwitchButton; + const auto grooveRect = subControlRect( button, contentsRect, Q::Groove ); + const auto pos = qskEffectivePosition( button ); - const auto switchButton = static_cast< const Q* >( skinnable ); - - const auto grooveRect = subControlRect( skinnable, contentsRect, Q::Groove ); - const auto pos = qskEffectivePosition( switchButton ); - const auto size = skinnable->strutSizeHint( Q::Handle ); + auto size = button->strutSizeHint( Q::Handle ); qreal cx, cy; - if( switchButton->orientation() == Qt::Vertical ) + if( button->orientation() == Qt::Vertical ) { const qreal y0 = grooveRect.y() + 0.5 * size.height(); const qreal h = grooveRect.height() - size.height(); @@ -148,6 +162,20 @@ QRectF QskSwitchButtonSkinlet::handleRect( cy = grooveRect.y() + 0.5 * grooveRect.height(); } + auto iconSize = qskIconSize( button ); + + if( !iconSize.isNull() ) + { + auto padding = button->paddingHint( Q::Icon ); + + // need to compensate for the margins, + // which might differ between states: + auto margins = button->marginHint( Q::Handle ); + + iconSize = iconSize.grownBy( padding ).grownBy( margins ); + size = size.expandedTo( iconSize ); + } + QRectF r; r.setSize( size ); r.moveCenter( QPointF( cx, cy ) ); @@ -155,4 +183,14 @@ QRectF QskSwitchButtonSkinlet::handleRect( return r; } +QRectF QskSwitchButtonSkinlet::iconRect( const QskSwitchButton* button, const QRectF& contentsRect ) const +{ + QRectF rect; + rect.setSize( qskIconSize( button ) ); + const auto hr = handleRect( button, contentsRect ); + rect.moveCenter( hr.center() ); + return rect; +} + + #include "moc_QskSwitchButtonSkinlet.cpp" diff --git a/src/controls/QskSwitchButtonSkinlet.h b/src/controls/QskSwitchButtonSkinlet.h index f39fbf3c..1f7e614b 100644 --- a/src/controls/QskSwitchButtonSkinlet.h +++ b/src/controls/QskSwitchButtonSkinlet.h @@ -8,6 +8,8 @@ #include "QskSkinlet.h" +class QskSwitchButton; + class QSK_EXPORT QskSwitchButtonSkinlet : public QskSkinlet { Q_GADGET @@ -19,6 +21,7 @@ class QSK_EXPORT QskSwitchButtonSkinlet : public QskSkinlet { GrooveRole, HandleRole, + IconRole, RoleCount }; @@ -37,8 +40,9 @@ class QSK_EXPORT QskSwitchButtonSkinlet : public QskSkinlet quint8 nodeRole, QSGNode* ) const override; private: - QRectF grooveRect( const QskSkinnable*, const QRectF& ) const; - QRectF handleRect( const QskSkinnable*, const QRectF& ) const; + QRectF grooveRect( const QskSwitchButton*, const QRectF& ) const; + QRectF handleRect( const QskSwitchButton*, const QRectF& ) const; + QRectF iconRect( const QskSwitchButton*, const QRectF& ) const; }; #endif