From 08837285a4dc6634bef4b6b540f7c03965a1837d Mon Sep 17 00:00:00 2001 From: Clemens Manert Date: Sun, 3 Apr 2022 23:05:27 +0200 Subject: [PATCH] Add checkbox --- skins/material/QskMaterialSkin.cpp | 21 +++++ skins/squiek/QskSquiekSkin.cpp | 20 +++++ src/controls/QskCheckBox.cpp | 95 ++++++++++++++++++++ src/controls/QskCheckBox.h | 42 +++++++++ src/controls/QskCheckBoxSkinlet.cpp | 134 ++++++++++++++++++++++++++++ src/controls/QskCheckBoxSkinlet.h | 32 +++++++ src/controls/QskSkin.cpp | 4 + src/src.pro | 4 + 8 files changed, 352 insertions(+) create mode 100644 src/controls/QskCheckBox.cpp create mode 100644 src/controls/QskCheckBox.h create mode 100644 src/controls/QskCheckBoxSkinlet.cpp create mode 100644 src/controls/QskCheckBoxSkinlet.h diff --git a/skins/material/QskMaterialSkin.cpp b/skins/material/QskMaterialSkin.cpp index a096179e..a89cbeec 100644 --- a/skins/material/QskMaterialSkin.cpp +++ b/skins/material/QskMaterialSkin.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -112,6 +113,7 @@ namespace void setupControl(); void setupBox(); + void setupCheckBox(); void setupDialogButtonBox(); void setupDialogButton(); void setupFocusIndicator(); @@ -142,6 +144,7 @@ void Editor::setup() setupControl(); setupBox(); + setupCheckBox(); setupDialogButtonBox(); setupDialogButton(); setupFocusIndicator(); @@ -177,6 +180,24 @@ void Editor::setupControl() qskShadedColor( m_pal.textColor, 0.6 ) ); } +void Editor::setupCheckBox() +{ + using Q = QskCheckBox; + + const qreal radius = qskDpiScaled( 18 ); + + setMargin( QskCheckBox::Tick, QMarginsF( 3, 5, 3, 3 ) ); + setStrutSize( Q::Box, radius, radius ); + + setBoxShape( Q::Box, 2 ); + + setColor( Q::Box, m_pal.baseColor); + setColor( Q::Box | Q::Checked, m_pal.accentColor ); + setGradient( Q::Box | Q::Checked | Q::Disabled, QskRgb::Grey ); + + setColor( Q::Tick, m_pal.contrastColor ); +} + void Editor::setupBox() { using Q = QskBox; diff --git a/skins/squiek/QskSquiekSkin.cpp b/skins/squiek/QskSquiekSkin.cpp index bfdd6706..a79fc20c 100644 --- a/skins/squiek/QskSquiekSkin.cpp +++ b/skins/squiek/QskSquiekSkin.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -132,6 +133,7 @@ namespace void setupControl(); void setupBox(); + void setupCheckBox(); void setupDialogButton(); void setupDialogButtonBox(); void setupFocusIndicator(); @@ -251,6 +253,7 @@ void Editor::setup() setupControl(); setupBox(); + setupCheckBox(); setupDialogButtonBox(); setupDialogButton(); setupFocusIndicator(); @@ -292,6 +295,23 @@ void Editor::setupBox() setPanel( QskBox::Panel, Plain ); } +void Editor::setupCheckBox() +{ + using Q = QskCheckBox; + + const qreal size = qskDpiScaled( 26 ); + + setMargin( Q::Tick, QskMargins( qskDpiScaled( 5 ) ) ); + + setStrutSize( Q::Box, QSizeF( size, size ) ); + setBoxShape( Q::Box, qskDpiScaled( 3 ) ); + setBoxBorderMetrics( Q::Box, qskDpiScaled( 1 ) ); + setBoxBorderColors( Q::Box, m_pal.darker125 ); + setColor( Q::Box, m_pal.lighter135 ); + setColor( Q::Box | Q::Checked, m_pal.highlighted ); + setColor( Q::Tick, m_pal.lighter135 ); +} + void Editor::setupPopup() { using A = QskAspect; diff --git a/src/controls/QskCheckBox.cpp b/src/controls/QskCheckBox.cpp new file mode 100644 index 00000000..6789900f --- /dev/null +++ b/src/controls/QskCheckBox.cpp @@ -0,0 +1,95 @@ +#include "QskCheckBox.h" + +#include "QskAspect.h" + +#include + +QSK_SUBCONTROL( QskCheckBox, Box ) +QSK_SUBCONTROL( QskCheckBox, Tick ) + +QSK_SYSTEM_STATE( QskCheckBox, PartiallyChecked, QskAspect::LastUserState << 2 ) + +struct QskCheckBox::PrivateData +{ + Qt::CheckState checkState; + bool checkStateChanging : 1; + bool toggleChanging : 1; + bool triState : 1; +}; + +QskCheckBox::QskCheckBox( QQuickItem* parent ) + : Inherited( parent ), m_data( new PrivateData{ Qt::CheckState::Unchecked, + false, false, false } ) { + setAcceptHoverEvents( true ); + initSizePolicy( QskSizePolicy::Fixed, QskSizePolicy::Fixed ); + + connect( this, &QskCheckBox::checkedChanged, this, [ this ]( bool t ) + { + setCheckStateInternal( t ? Qt::CheckState::Checked : + Qt::CheckState::Unchecked ); + } ); +} + +QskCheckBox::~QskCheckBox() +{ +} + +bool QskCheckBox::isCheckable() const +{ + return true; +} + +Qt::CheckState QskCheckBox::checkState() const +{ + return m_data->checkState; +} + +void QskCheckBox::setCheckStateInternal( Qt::CheckState checkState ) +{ + if( m_data->checkStateChanging ) + { + return; + } + + setSkinStateFlag( PartiallyChecked, + checkState == Qt::CheckState::PartiallyChecked ); + + m_data->checkState = checkState; + Q_EMIT checkStateChanged( checkState ); +} + +void QskCheckBox::setCheckState( Qt::CheckState checkState ) +{ + if( checkState == m_data->checkState ) + return; + + m_data->checkStateChanging = true; + if( checkState == Qt::CheckState::PartiallyChecked ) + { + setChecked( true ); + setTriState( true ); + } + else + { + setChecked( checkState == Qt::CheckState::Checked ); + } + m_data->checkStateChanging = false; + + setCheckStateInternal( checkState ); +} + +bool QskCheckBox::isTriState() const +{ + return m_data->triState; +} + +void QskCheckBox::setTriState( bool triState ) +{ + if( m_data->triState != triState ) + { + m_data->triState = triState; + Q_EMIT isTriStateChanged( triState ); + } +} + +#include "moc_QskCheckBox.cpp" diff --git a/src/controls/QskCheckBox.h b/src/controls/QskCheckBox.h new file mode 100644 index 00000000..87f39e7a --- /dev/null +++ b/src/controls/QskCheckBox.h @@ -0,0 +1,42 @@ +#ifndef QSK_CHECK_BOX_H +#define QSK_CHECK_BOX_H + +#include "QskAbstractButton.h" + +class QSK_EXPORT QskCheckBox : public QskAbstractButton +{ + Q_OBJECT + + Q_PROPERTY( Qt::CheckState checkState READ checkState + WRITE setCheckState NOTIFY checkStateChanged FINAL ) + Q_PROPERTY( bool isTriState READ isTriState + WRITE setTriState NOTIFY isTriStateChanged FINAL ) + + using Inherited = QskAbstractButton; + + public: + QSK_SUBCONTROLS( Box, Tick ) + QSK_STATES( PartiallyChecked ) + + QskCheckBox( QQuickItem* parent = nullptr ); + ~QskCheckBox() override; + + Qt::CheckState checkState() const; + bool isTriState() const; + bool isCheckable() const override final; + + public Q_SLOTS: + void setCheckState( Qt::CheckState ); + void setTriState( bool triState = true ); + + Q_SIGNALS: + void checkStateChanged( Qt::CheckState ); + void isTriStateChanged( bool ); + private: + void setCheckStateInternal( Qt::CheckState ); + + struct PrivateData; + std::unique_ptr< PrivateData > m_data; +}; + +#endif // QSK_CHECK_BOX_H diff --git a/src/controls/QskCheckBoxSkinlet.cpp b/src/controls/QskCheckBoxSkinlet.cpp new file mode 100644 index 00000000..946394b6 --- /dev/null +++ b/src/controls/QskCheckBoxSkinlet.cpp @@ -0,0 +1,134 @@ +#include "QskCheckBoxSkinlet.h" +#include "QskCheckBox.h" + +#include +#include + +class Tic : public QSGGeometryNode { + QSGFlatColorMaterial material; + QSGGeometry geometry = QSGGeometry( + QSGGeometry::defaultAttributes_Point2D(), 3 ); + const QRectF& target; + public: + Tic( const QRectF& rect, const QColor& color ): target( rect ) { + geometry.setDrawingMode( QSGGeometry::DrawLineStrip ); + geometry.setLineWidth( 2 ); + setGeometry( &geometry ); + + material.setColor( color ); + setMaterial( &material ); + + markDirty( QSGNode::DirtyGeometry ); + } + + void setColor( const QColor& color ) { + material.setColor( color ); + markDirty( QSGNode::DirtyMaterial ); + } + + void makeTic() { + const auto& size = target.size(); + const auto x = target.x(); + const auto y = target.y(); + + geometry.vertexDataAsPoint2D()[0].set( x, y + size.height() / 2 ); + geometry.vertexDataAsPoint2D()[1].set( x + size.width() / 3, + y + size.height() ); + geometry.vertexDataAsPoint2D()[2].set( x + size.width(), y ); + markDirty( QSGNode::DirtyGeometry ); + } + + void makePartially() { + const auto& size = target.size(); + const auto x = target.x(); + const auto y = target.y(); + + geometry.vertexDataAsPoint2D()[0].set( x, y + size.height() / 2 ); + geometry.vertexDataAsPoint2D()[1].set( x, y + size.height() / 2 ); + geometry.vertexDataAsPoint2D()[2].set( x + size.width(), + y + size.height() / 2 ); + + markDirty( QSGNode::DirtyGeometry ); + } + + void makeEmpty() { + const auto x = target.x(); + const auto y = target.y(); + + geometry.vertexDataAsPoint2D()[0].set( x, y ); + geometry.vertexDataAsPoint2D()[1].set( x, y ); + geometry.vertexDataAsPoint2D()[2].set( x, y ); + + markDirty( QSGNode::DirtyGeometry ); + } +}; + +QskCheckBoxSkinlet::QskCheckBoxSkinlet( QskSkin* skin ) + : QskSkinlet( skin ) +{ + setNodeRoles( { BoxRole, TickRole } ); +} + +QskCheckBoxSkinlet::~QskCheckBoxSkinlet() +{ +} + +QRectF QskCheckBoxSkinlet::subControlRect( + const QskSkinnable*, + const QRectF& contentsRect, + QskAspect::Subcontrol ) const +{ + return contentsRect; +} + +QSGNode* QskCheckBoxSkinlet::updateSubNode( + const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const +{ + switch( nodeRole ) { + case BoxRole: + return updateBoxNode( skinnable, node, + QskCheckBox::Box ); + case TickRole: + auto control = dynamic_cast< const QskCheckBox* >( skinnable ); + auto rect = control->subControlRect( QskCheckBox::Tick ); + rect = rect.marginsRemoved( + skinnable->marginHint( QskCheckBox::Tick ) ); + + Tic* tic; + if ( static_cast< Tic* >( node ) == nullptr ) + { + tic = new Tic( rect, skinnable->color( QskCheckBox::Tick ) ); + } + else + { + tic = static_cast< Tic* >( node ); + } + + switch ( control->checkState() ) { + case Qt::CheckState::Unchecked: + tic->setColor( skinnable->color( QskCheckBox::Tick ) ); + tic->makeEmpty(); + break; + case Qt::CheckState::PartiallyChecked: + tic->setColor( skinnable->color( + QskCheckBox::Tick | QskCheckBox::PartiallyChecked ) ); + tic->makePartially(); + break; + case Qt::CheckState::Checked: + tic->setColor( skinnable->color( + QskCheckBox::Tick | QskCheckBox::Checked ) ); + tic->makeTic(); + break; + } + + return tic; + } + + return Inherited::updateSubNode( skinnable, nodeRole, node ); +} + +QSizeF QskCheckBoxSkinlet::sizeHint( const QskSkinnable* skinnable, + Qt::SizeHint, const QSizeF& ) const +{ + return skinnable->strutSizeHint( QskCheckBox::Box ); +} diff --git a/src/controls/QskCheckBoxSkinlet.h b/src/controls/QskCheckBoxSkinlet.h new file mode 100644 index 00000000..db19bdd2 --- /dev/null +++ b/src/controls/QskCheckBoxSkinlet.h @@ -0,0 +1,32 @@ +#ifndef QSK_CHECK_BOX_SKINLET_H +#define QSK_CHECK_BOX_SKINLET_H + +#include "QskSkinlet.h" + +class QSK_EXPORT QskCheckBoxSkinlet : public QskSkinlet +{ + Q_GADGET + + using Inherited = QskSkinlet; + public: + enum NodeRole + { + BoxRole, + TickRole, + }; + + Q_INVOKABLE QskCheckBoxSkinlet( QskSkin* = nullptr ); + ~QskCheckBoxSkinlet() override; + + QRectF subControlRect( const QskSkinnable*, + const QRectF&, QskAspect::Subcontrol ) const override; + + QSizeF sizeHint( const QskSkinnable*, + Qt::SizeHint, const QSizeF& ) const override; + + protected: + QSGNode* updateSubNode( const QskSkinnable*, + quint8 nodeRole, QSGNode* ) const override; +}; + +#endif // QSK_CHECK_BOX_SKINLET_H diff --git a/src/controls/QskSkin.cpp b/src/controls/QskSkin.cpp index 1f8b67db..4a8e20e5 100644 --- a/src/controls/QskSkin.cpp +++ b/src/controls/QskSkin.cpp @@ -29,6 +29,9 @@ QSK_QT_PRIVATE_END #include "QskBox.h" #include "QskBoxSkinlet.h" +#include "QskCheckBox.h" +#include "QskCheckBoxSkinlet.h" + #include "QskFocusIndicator.h" #include "QskFocusIndicatorSkinlet.h" @@ -143,6 +146,7 @@ QskSkin::QskSkin( QObject* parent ) declareSkinlet< QskControl, QskSkinlet >(); declareSkinlet< QskBox, QskBoxSkinlet >(); + declareSkinlet< QskCheckBox, QskCheckBoxSkinlet >(); declareSkinlet< QskFocusIndicator, QskFocusIndicatorSkinlet >(); declareSkinlet< QskGraphicLabel, QskGraphicLabelSkinlet >(); declareSkinlet< QskListView, QskListViewSkinlet >(); diff --git a/src/src.pro b/src/src.pro index 0c00a0ec..2f51cb2b 100644 --- a/src/src.pro +++ b/src/src.pro @@ -141,6 +141,8 @@ HEADERS += \ controls/QskBoundedValueInput.h \ controls/QskBox.h \ controls/QskBoxSkinlet.h \ + controls/QskCheckBox.h \ + controls/QskCheckBoxSkinlet.h \ controls/QskControl.h \ controls/QskControlPrivate.h \ controls/QskDirtyItemFilter.h \ @@ -221,6 +223,8 @@ SOURCES += \ controls/QskBoundedValueInput.cpp \ controls/QskBox.cpp \ controls/QskBoxSkinlet.cpp \ + controls/QskCheckBox.cpp \ + controls/QskCheckBoxSkinlet.cpp \ controls/QskControl.cpp \ controls/QskControlPrivate.cpp \ controls/QskDirtyItemFilter.cpp \