switch button: Support icons (for M3) (#452)

This commit is contained in:
Peter Hartmann 2024-10-28 08:14:32 +01:00 committed by GitHub
parent 80934fa07f
commit b2f3220dfe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 155 additions and 41 deletions

View File

@ -7,6 +7,8 @@
<file>icons/qvg/combo-box-arrow-closed.qvg</file> <file>icons/qvg/combo-box-arrow-closed.qvg</file>
<file>icons/qvg/combo-box-arrow-open.qvg</file> <file>icons/qvg/combo-box-arrow-open.qvg</file>
<file>icons/qvg/segmented-button-check.qvg</file> <file>icons/qvg/segmented-button-check.qvg</file>
<file>icons/qvg/switchbutton-checked.qvg</file>
<file>icons/qvg/switchbutton-unchecked.qvg</file>
<!-- https://github.com/marella/material-design-icons/tree/main/svg/round --> <!-- https://github.com/marella/material-design-icons/tree/main/svg/round -->

View File

@ -40,7 +40,6 @@
#include <QskStandardSymbol.h> #include <QskStandardSymbol.h>
#include <QskSubWindow.h> #include <QskSubWindow.h>
#include <QskSwitchButton.h> #include <QskSwitchButton.h>
#include <QskSwitchButtonSkinlet.h>
#include <QskTabBar.h> #include <QskTabBar.h>
#include <QskTabButton.h> #include <QskTabButton.h>
#include <QskTabView.h> #include <QskTabView.h>
@ -998,6 +997,8 @@ void Editor::setupSwitchButton()
using A = QskAspect; using A = QskAspect;
using Q = QskSwitchButton; using Q = QskSwitchButton;
const QskStateCombination allStates ( QskStateCombination::CombinationNoState, QskAspect::AllStates );
setBoxShape( Q::Groove, 100, Qt::RelativeSize ); setBoxShape( Q::Groove, 100, Qt::RelativeSize );
const QSizeF strutSize( 52_dp, 32_dp ); const QSizeF strutSize( 52_dp, 32_dp );
setStrutSize( Q::Groove | A::Horizontal, strutSize ); setStrutSize( Q::Groove | A::Horizontal, strutSize );
@ -1010,11 +1011,12 @@ void Editor::setupSwitchButton()
setGradient( Q::Groove | Q::Checked | Q::Disabled, m_pal.onSurface12 ); setGradient( Q::Groove | Q::Checked | Q::Disabled, m_pal.onSurface12 );
setBoxBorderMetrics( Q::Groove, 2_dp ); setBoxBorderMetrics( Q::Groove, 2_dp );
setBoxBorderColors( Q::Groove, m_pal.outline ); 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 ); 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 ); setMargin( Q::Handle, 7_dp );
setShadowMetrics( Q::Handle, { 17_dp, 0 } ); setShadowMetrics( Q::Handle, { 17_dp, 0 } );
setShadowColor( Q::Handle, QskRgb::Transparent ); setShadowColor( Q::Handle, QskRgb::Transparent );
@ -1022,6 +1024,15 @@ void Editor::setupSwitchButton()
setGradient( Q::Handle, m_pal.outline ); setGradient( Q::Handle, m_pal.outline );
setGradient( Q::Handle | Q::Checked, m_pal.onPrimary ); 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 } ) for ( auto state1 : { A::NoState, Q::Hovered, Q::Focused, Q::Pressed } )
{ {
const qreal opacity = m_pal.stateOpacity( state1 ); const qreal opacity = m_pal.stateOpacity( state1 );
@ -1067,8 +1078,8 @@ void Editor::setupSwitchButton()
{ {
auto aspect = Q::Handle | state; auto aspect = Q::Handle | state;
setPosition( aspect, 0.15 ); setPosition( aspect, 0.10 );
setPosition( aspect | Q::Checked, 0.85 ); setPosition( aspect | Q::Checked, 0.9 );
} }
setAnimation( Q::Handle | A::Color, qskDuration ); setAnimation( Q::Handle | A::Color, qskDuration );
@ -1492,6 +1503,8 @@ QskMaterial3Theme::QskMaterial3Theme( QskSkin::ColorScheme colorScheme,
surfaceVariant12 = QskRgb::toTransparentF( surfaceVariant, 0.12 ); surfaceVariant12 = QskRgb::toTransparentF( surfaceVariant, 0.12 );
surfaceContainerHighest38 = QskRgb::toTransparentF( surfaceContainerHighest, 0.38 );
elevation0 = QskShadowMetrics( 0, 0 ); elevation0 = QskShadowMetrics( 0, 0 );
elevation1 = QskShadowMetrics( -2, 9, { 0, 1 } ); elevation1 = QskShadowMetrics( -2, 9, { 0, 1 } );
elevation2 = QskShadowMetrics( -2, 8, { 0, 2 } ); elevation2 = QskShadowMetrics( -2, 8, { 0, 2 } );
@ -1597,6 +1610,7 @@ void QskMaterial3Skin::setGraphicColor( GraphicRole role, QRgb rgb )
void QskMaterial3Skin::setupGraphicFilters( const QskMaterial3Theme& theme ) void QskMaterial3Skin::setupGraphicFilters( const QskMaterial3Theme& theme )
{ {
setGraphicColor( GraphicRoleOnPrimary, theme.onPrimary ); setGraphicColor( GraphicRoleOnPrimary, theme.onPrimary );
setGraphicColor( GraphicRoleOnPrimaryContainer, theme.onPrimaryContainer );
setGraphicColor( GraphicRoleOnSecondaryContainer, theme.onSecondaryContainer ); setGraphicColor( GraphicRoleOnSecondaryContainer, theme.onSecondaryContainer );
setGraphicColor( GraphicRoleOnError, theme.onError ); setGraphicColor( GraphicRoleOnError, theme.onError );
setGraphicColor( GraphicRoleOnSurface, theme.onSurface ); setGraphicColor( GraphicRoleOnSurface, theme.onSurface );
@ -1604,6 +1618,8 @@ void QskMaterial3Skin::setupGraphicFilters( const QskMaterial3Theme& theme )
setGraphicColor( GraphicRoleOnSurfaceVariant, theme.onSurfaceVariant ); setGraphicColor( GraphicRoleOnSurfaceVariant, theme.onSurfaceVariant );
setGraphicColor( GraphicRolePrimary, theme.primary ); setGraphicColor( GraphicRolePrimary, theme.primary );
setGraphicColor( GraphicRoleSurface, theme.surface ); setGraphicColor( GraphicRoleSurface, theme.surface );
setGraphicColor( GraphicRoleSurfaceContainerHighest, theme.surfaceContainerHighest );
setGraphicColor( GraphicRoleSurfaceContainerHighest38, theme.surfaceContainerHighest38 );
} }
void QskMaterial3Skin::initHints() void QskMaterial3Skin::initHints()

View File

@ -80,6 +80,7 @@ class QSK_MATERIAL3_EXPORT QskMaterial3Theme
QRgb outlineVariant; QRgb outlineVariant;
QRgb surfaceContainerHighest; QRgb surfaceContainerHighest;
QRgb surfaceContainerHighest38;
QRgb shadow; QRgb shadow;
@ -110,12 +111,15 @@ class QSK_MATERIAL3_EXPORT QskMaterial3Skin : public QskSkin
{ {
GraphicRoleOnError, GraphicRoleOnError,
GraphicRoleOnPrimary, GraphicRoleOnPrimary,
GraphicRoleOnPrimaryContainer,
GraphicRoleOnSecondaryContainer, GraphicRoleOnSecondaryContainer,
GraphicRoleOnSurface, GraphicRoleOnSurface,
GraphicRoleOnSurface38, GraphicRoleOnSurface38,
GraphicRoleOnSurfaceVariant, GraphicRoleOnSurfaceVariant,
GraphicRolePrimary, GraphicRolePrimary,
GraphicRoleSurface, GraphicRoleSurface,
GraphicRoleSurfaceContainerHighest,
GraphicRoleSurfaceContainerHighest38,
}; };
QskMaterial3Skin( QObject* parent = nullptr ); QskMaterial3Skin( QObject* parent = nullptr );

View File

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.0001 10.78L3.2201 8.00002L2.27344 8.94002L6.0001 12.6667L14.0001 4.66668L13.0601 3.72668L6.0001 10.78Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 235 B

View File

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.6666 4.27337L11.7266 3.33337L7.99992 7.06004L4.27325 3.33337L3.33325 4.27337L7.05992 8.00004L3.33325 11.7267L4.27325 12.6667L7.99992 8.94004L11.7266 12.6667L12.6666 11.7267L8.93992 8.00004L12.6666 4.27337Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 339 B

View File

@ -133,13 +133,24 @@ namespace
SwitchButtonBox( QQuickItem* parent = nullptr ) SwitchButtonBox( QQuickItem* parent = nullptr )
: ButtonBox( Qt::Horizontal, parent ) : ButtonBox( Qt::Horizontal, parent )
{ {
setDimension( 6 );
setSpacing( 20 );
setDefaultAlignment( Qt::AlignCenter );
for ( auto orientation : { Qt::Vertical, Qt::Horizontal } ) for ( auto orientation : { Qt::Vertical, Qt::Horizontal } )
{ {
(void) new QskSwitchButton( orientation, this ); using Q = QskSwitchButton;
for( auto iconMode : { Q::NoIcon, Q::ShowIconWhenSelected, Q::ShowIconAlways } )
{
auto button = new QskSwitchButton( orientation, this ); auto button = new QskSwitchButton( orientation, this );
button->setInverted( true ); button->setIconMode( iconMode );
button->setChecked( true );
auto invertedButton = new QskSwitchButton( orientation, this );
invertedButton->setInverted( true );
invertedButton->setChecked( true );
invertedButton->setIconMode( iconMode );
}
} }
} }
}; };

View File

@ -5,8 +5,9 @@
#include "QskSwitchButton.h" #include "QskSwitchButton.h"
QSK_SUBCONTROL( QskSwitchButton, Handle )
QSK_SUBCONTROL( QskSwitchButton, Groove ) QSK_SUBCONTROL( QskSwitchButton, Groove )
QSK_SUBCONTROL( QskSwitchButton, Handle )
QSK_SUBCONTROL( QskSwitchButton, Icon )
struct QskSwitchButton::PrivateData struct QskSwitchButton::PrivateData
{ {
@ -17,6 +18,7 @@ struct QskSwitchButton::PrivateData
bool inverted = false; bool inverted = false;
Qt::Orientation orientation; Qt::Orientation orientation;
IconMode iconMode = NoIcon;
}; };
QskSwitchButton::QskSwitchButton( QQuickItem* parent ) 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 QskAspect::Variation QskSwitchButton::effectiveVariation() const
{ {
return static_cast< QskAspect::Variation >( m_data->orientation ); return static_cast< QskAspect::Variation >( m_data->orientation );

View File

@ -21,8 +21,19 @@ class QSK_EXPORT QskSwitchButton : public QskAbstractButton
Q_PROPERTY( bool inverted READ isInverted Q_PROPERTY( bool inverted READ isInverted
WRITE setInverted NOTIFY invertedChanged FINAL ) WRITE setInverted NOTIFY invertedChanged FINAL )
Q_PROPERTY( IconMode iconMode READ iconMode
WRITE setIconMode NOTIFY iconModeChanged FINAL )
public: 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( Qt::Orientation, QQuickItem* parent = nullptr );
QskSwitchButton( QQuickItem* parent = nullptr ); QskSwitchButton( QQuickItem* parent = nullptr );
@ -32,16 +43,20 @@ class QSK_EXPORT QskSwitchButton : public QskAbstractButton
bool isCheckable() const override final; bool isCheckable() const override final;
Qt::Orientation orientation() const; Qt::Orientation orientation() const;
void setOrientation(Qt::Orientation); void setOrientation( Qt::Orientation );
bool isInverted() const; bool isInverted() const;
void setInverted( bool ); void setInverted( bool );
IconMode iconMode() const;
void setIconMode( IconMode );
QskAspect::Variation effectiveVariation() const override; QskAspect::Variation effectiveVariation() const override;
Q_SIGNALS: Q_SIGNALS:
void orientationChanged( Qt::Orientation ); void orientationChanged( Qt::Orientation );
void invertedChanged( bool ); void invertedChanged( bool );
void iconModeChanged( IconMode );
private: private:
struct PrivateData; struct PrivateData;

View File

@ -6,6 +6,8 @@
#include "QskSwitchButtonSkinlet.h" #include "QskSwitchButtonSkinlet.h"
#include "QskSwitchButton.h" #include "QskSwitchButton.h"
using Q = QskSwitchButton;
static inline qreal qskEffectivePosition( const QskSwitchButton* switchButton ) static inline qreal qskEffectivePosition( const QskSwitchButton* switchButton )
{ {
auto pos = switchButton->positionHint( QskSwitchButton::Handle ); auto pos = switchButton->positionHint( QskSwitchButton::Handle );
@ -23,10 +25,23 @@ static inline qreal qskEffectivePosition( const QskSwitchButton* switchButton )
return pos; 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 ) QskSwitchButtonSkinlet::QskSwitchButtonSkinlet( QskSkin* skin )
: Inherited( skin ) : Inherited( skin )
{ {
setNodeRoles( { GrooveRole, HandleRole } ); setNodeRoles( { GrooveRole, HandleRole, IconRole } );
} }
QskSwitchButtonSkinlet::~QskSwitchButtonSkinlet() QskSwitchButtonSkinlet::~QskSwitchButtonSkinlet()
@ -36,16 +51,21 @@ QskSwitchButtonSkinlet::~QskSwitchButtonSkinlet()
QRectF QskSwitchButtonSkinlet::subControlRect( const QskSkinnable* skinnable, QRectF QskSwitchButtonSkinlet::subControlRect( const QskSkinnable* skinnable,
const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const
{ {
using Q = QskSwitchButton; const auto button = static_cast< const Q* >( skinnable );
if ( subControl == Q::Handle )
{
return handleRect( skinnable, contentsRect );
}
if ( subControl == Q::Groove ) 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 ); return Inherited::subControlRect( skinnable, contentsRect, subControl );
@ -69,13 +89,14 @@ QSizeF QskSwitchButtonSkinlet::sizeHint( const QskSkinnable* skinnable,
QSGNode* QskSwitchButtonSkinlet::updateSubNode( const QskSkinnable* skinnable, QSGNode* QskSwitchButtonSkinlet::updateSubNode( const QskSkinnable* skinnable,
quint8 nodeRole, QSGNode* node ) const quint8 nodeRole, QSGNode* node ) const
{ {
using Q = QskSwitchButton;
switch ( nodeRole ) switch ( nodeRole )
{ {
case HandleRole: case HandleRole:
return updateBoxNode( skinnable, node, Q::Handle ); return updateBoxNode( skinnable, node, Q::Handle );
case IconRole:
return updateSymbolNode( skinnable, node, Q::Icon );
case GrooveRole: case GrooveRole:
return updateBoxNode( skinnable, node, Q::Groove ); return updateBoxNode( skinnable, node, Q::Groove );
} }
@ -84,19 +105,15 @@ QSGNode* QskSwitchButtonSkinlet::updateSubNode( const QskSkinnable* skinnable,
} }
QRectF QskSwitchButtonSkinlet::grooveRect( 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 ); if ( button->orientation() == Qt::Vertical )
auto size = skinnable->strutSizeHint( Q::Groove );
if ( switchButton->orientation() == Qt::Vertical )
{ {
if ( size.height() < 0.0 ) if ( size.height() < 0.0 )
{ {
const auto handleSize = skinnable->strutSizeHint( Q::Handle ); const auto handleSize = button->strutSizeHint( Q::Handle );
size.setHeight( 2 * handleSize.height() ); size.setHeight( 2 * handleSize.height() );
} }
} }
@ -104,7 +121,7 @@ QRectF QskSwitchButtonSkinlet::grooveRect(
{ {
if ( size.width() < 0.0 ) if ( size.width() < 0.0 )
{ {
const auto handleSize = skinnable->strutSizeHint( Q::Handle ); const auto handleSize = button->strutSizeHint( Q::Handle );
size.setWidth( 2 * handleSize.width() ); size.setWidth( 2 * handleSize.width() );
} }
} }
@ -119,19 +136,16 @@ QRectF QskSwitchButtonSkinlet::grooveRect(
} }
QRectF QskSwitchButtonSkinlet::handleRect( 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 ); auto size = button->strutSizeHint( Q::Handle );
const auto grooveRect = subControlRect( skinnable, contentsRect, Q::Groove );
const auto pos = qskEffectivePosition( switchButton );
const auto size = skinnable->strutSizeHint( Q::Handle );
qreal cx, cy; qreal cx, cy;
if( switchButton->orientation() == Qt::Vertical ) if( button->orientation() == Qt::Vertical )
{ {
const qreal y0 = grooveRect.y() + 0.5 * size.height(); const qreal y0 = grooveRect.y() + 0.5 * size.height();
const qreal h = grooveRect.height() - size.height(); const qreal h = grooveRect.height() - size.height();
@ -148,6 +162,20 @@ QRectF QskSwitchButtonSkinlet::handleRect(
cy = grooveRect.y() + 0.5 * grooveRect.height(); 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; QRectF r;
r.setSize( size ); r.setSize( size );
r.moveCenter( QPointF( cx, cy ) ); r.moveCenter( QPointF( cx, cy ) );
@ -155,4 +183,14 @@ QRectF QskSwitchButtonSkinlet::handleRect(
return r; 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" #include "moc_QskSwitchButtonSkinlet.cpp"

View File

@ -8,6 +8,8 @@
#include "QskSkinlet.h" #include "QskSkinlet.h"
class QskSwitchButton;
class QSK_EXPORT QskSwitchButtonSkinlet : public QskSkinlet class QSK_EXPORT QskSwitchButtonSkinlet : public QskSkinlet
{ {
Q_GADGET Q_GADGET
@ -19,6 +21,7 @@ class QSK_EXPORT QskSwitchButtonSkinlet : public QskSkinlet
{ {
GrooveRole, GrooveRole,
HandleRole, HandleRole,
IconRole,
RoleCount RoleCount
}; };
@ -37,8 +40,9 @@ class QSK_EXPORT QskSwitchButtonSkinlet : public QskSkinlet
quint8 nodeRole, QSGNode* ) const override; quint8 nodeRole, QSGNode* ) const override;
private: private:
QRectF grooveRect( const QskSkinnable*, const QRectF& ) const; QRectF grooveRect( const QskSwitchButton*, const QRectF& ) const;
QRectF handleRect( const QskSkinnable*, const QRectF& ) const; QRectF handleRect( const QskSwitchButton*, const QRectF& ) const;
QRectF iconRect( const QskSwitchButton*, const QRectF& ) const;
}; };
#endif #endif