From 13faf53495d9222d57f4e893d768593a5858bbd5 Mon Sep 17 00:00:00 2001 From: Clemens Manert Date: Mon, 2 Aug 2021 13:22:37 +0200 Subject: [PATCH] Add SwitchButton (#121) --- examples/examples.pro | 1 + examples/switchbuttons/main.cpp | 54 ++++++++++ examples/switchbuttons/switchbuttons.pro | 4 + skins/material/QskMaterialSkin.cpp | 45 ++++++++- skins/squiek/QskSquiekSkin.cpp | 43 ++++++++ src/controls/QskSkin.cpp | 4 + src/controls/QskSwitchButton.cpp | 57 +++++++++++ src/controls/QskSwitchButton.h | 39 ++++++++ src/controls/QskSwitchButtonSkinlet.cpp | 120 +++++++++++++++++++++++ src/controls/QskSwitchButtonSkinlet.h | 34 +++++++ src/src.pro | 4 + 11 files changed, 404 insertions(+), 1 deletion(-) create mode 100644 examples/switchbuttons/main.cpp create mode 100644 examples/switchbuttons/switchbuttons.pro create mode 100644 src/controls/QskSwitchButton.cpp create mode 100644 src/controls/QskSwitchButton.h create mode 100644 src/controls/QskSwitchButtonSkinlet.cpp create mode 100644 src/controls/QskSwitchButtonSkinlet.h diff --git a/examples/examples.pro b/examples/examples.pro index 0402f44c..9e943dad 100644 --- a/examples/examples.pro +++ b/examples/examples.pro @@ -3,6 +3,7 @@ TEMPLATE = subdirs # c++ SUBDIRS += \ desktop \ + switchbuttons \ gallery \ layouts \ listbox \ diff --git a/examples/switchbuttons/main.cpp b/examples/switchbuttons/main.cpp new file mode 100644 index 00000000..88f5205f --- /dev/null +++ b/examples/switchbuttons/main.cpp @@ -0,0 +1,54 @@ +#include + +#include +#include +#include +#include + +#include + +int main(int argc, char* argv[]) +{ + QGuiApplication app( argc, argv ); + QskWindow window; + + SkinnyShortcut::enable( SkinnyShortcut::Quit | + SkinnyShortcut::DebugShortcuts ); + + auto vbox = new QskLinearBox(Qt::Vertical); + auto hbox = new QskLinearBox(vbox); + new QskTextLabel("Disable the boxes: ", hbox); + auto disabler = new QskSwitchButton(hbox); + + auto targets = new QskLinearBox(Qt::Horizontal, vbox); + + auto target1 = new QskSwitchButton(targets); + target1->setOrientation(Qt::Vertical); + + auto target2 = new QskSwitchButton(targets); + target2->setOrientation(Qt::Horizontal); + + auto target3 = new QskSwitchButton(targets); + target3->setChecked(true); + target3->setOrientation(Qt::Vertical); + + auto target4 = new QskSwitchButton(targets); + target4->setChecked(true); + target4->setOrientation(Qt::Horizontal); + + QObject::connect(disabler, &QskSwitchButton::checkedChanged, + targets, [target1, target2, target3, target4](bool c){ + target1->setEnabled(!c); + target2->setEnabled(!c); + target3->setEnabled(!c); + target4->setEnabled(!c); + }); + + targets->setExtraSpacingAt(Qt::RightEdge); + vbox->setExtraSpacingAt(Qt::BottomEdge); + + window.addItem( vbox ); + window.show(); + + return app.exec(); +} diff --git a/examples/switchbuttons/switchbuttons.pro b/examples/switchbuttons/switchbuttons.pro new file mode 100644 index 00000000..4539fa38 --- /dev/null +++ b/examples/switchbuttons/switchbuttons.pro @@ -0,0 +1,4 @@ +CONFIG += qskexample + +SOURCES += \ + main.cpp\ diff --git a/skins/material/QskMaterialSkin.cpp b/skins/material/QskMaterialSkin.cpp index c6b5c5db..e7d69570 100644 --- a/skins/material/QskMaterialSkin.cpp +++ b/skins/material/QskMaterialSkin.cpp @@ -20,6 +20,8 @@ #include #include #include +#include +#include #include #include #include @@ -107,7 +109,7 @@ namespace private: void setupControl(); - + void setupBox(); void setupDialogButtonBox(); void setupDialogButton(); @@ -123,6 +125,7 @@ namespace void setupSeparator(); void setupSubWindow(); void setupSlider(); + void setupSwitchButton(); void setupTabButton(); void setupTabBar(); void setupTabView(); @@ -152,6 +155,7 @@ void Editor::setup() setupSeparator(); setupSlider(); setupSubWindow(); + setupSwitchButton(); setupTabButton(); setupTabBar(); setupTabView(); @@ -483,6 +487,45 @@ void Editor::setupSlider() setAnimation( Q::Handle | A::Metric | A::Position | Q::Pressed, 0 ); } +void Editor::setupSwitchButton() +{ + using A = QskAspect; + using Q = QskSwitchButton; + + const qreal radius = qskDpiScaled( 18 ); + const qreal knopLength = radius - 4; + + setBoxShape( Q::Groove, radius); + setStrutSize(Q::Groove, 3.4 * radius, 2 * radius); + setColor( Q::Groove, m_pal.accentColor); + setBoxBorderColors(Q::Groove, m_pal.darker200); + setColor(Q::Groove | Q::Disabled, m_pal.lighter125); + setBoxBorderMetrics(Q::Groove, 2); + setBoxBorderColors(Q::Groove | Q::Disabled, m_pal.darker125); + + setBoxShape( Q::Knop, knopLength); + setMetric(Q::Knop | A::Size,knopLength); + setGradient( Q::Knop, QskGradient(Qt::Vertical, m_pal.lighter150, m_pal.lighter125) ); + setBoxBorderMetrics(Q::Knop, 2); + setColor(Q::Knop | Q::Disabled, m_pal.lighter125); + setBoxBorderColors(Q::Knop, m_pal.darker200); + setBoxBorderColors(Q::Knop | Q::Disabled, m_pal.darker125); + + for( auto state : { A::NoState, Q::Disabled } ) + { + auto aspect = Q::Knop | state | A::Position; + + setMetric( aspect | Q::Checked, 0 ); + setMetric( aspect, 1 ); + + aspect = Q::Groove | state | A::Color; + setColor( aspect | Q::Checked, m_pal.baseColor); + } + + setAnimation( Q::Knop | A::Metric, qskDuration ); + setAnimation( Q::Groove | A::Color, qskDuration ); +} + void Editor::setupTabButton() { using A = QskAspect; diff --git a/skins/squiek/QskSquiekSkin.cpp b/skins/squiek/QskSquiekSkin.cpp index 288c4956..a5d79ae4 100644 --- a/skins/squiek/QskSquiekSkin.cpp +++ b/skins/squiek/QskSquiekSkin.cpp @@ -21,6 +21,8 @@ #include #include #include +#include +#include #include #include #include @@ -143,6 +145,7 @@ namespace void setupSeparator(); void setupSlider(); void setupSubWindow(); + void setupSwitchButton(); void setupTabButton(); void setupTabBar(); void setupTabView(); @@ -260,6 +263,7 @@ void Editor::setup() setupSeparator(); setupSlider(); setupSubWindow(); + setupSwitchButton(); setupTabButton(); setupTabBar(); setupTabView(); @@ -849,6 +853,45 @@ void Editor::setupSubWindow() setAnimation( subControl | A::Color, qskDuration ); } +void Editor::setupSwitchButton() +{ + using A = QskAspect; + using Q = QskSwitchButton; + + const qreal radius = qskDpiScaled( 18 ); + const qreal knopLength = radius - 4; + + setBoxShape( Q::Groove, radius); + setStrutSize(Q::Groove, 3.4 * radius, 2 * radius); + setColor( Q::Groove, m_pal.highlighted); + setBoxBorderColors(Q::Groove | Q::Disabled, m_pal.theme); + setColor(Q::Groove | Q::Disabled, m_pal.lighter110); + setBoxBorderMetrics(Q::Groove, 2); + setBoxBorderColors(Q::Groove, m_pal.darker200); + + setBoxShape( Q::Knop, knopLength); + setMetric(Q::Knop | A::Size,knopLength); + setGradient( Q::Knop, QskGradient(Qt::Vertical, m_pal.lighter150, m_pal.lighter110) ); + setBoxBorderMetrics(Q::Knop, 2); + setColor(Q::Knop | Q::Disabled, m_pal.lighter110); + setBoxBorderColors(Q::Knop, m_pal.darker200); + setBoxBorderColors(Q::Knop | Q::Disabled, m_pal.theme); + + for( auto state : { A::NoState, Q::Disabled } ) + { + auto aspect = Q::Knop | state | A::Position; + + setMetric( aspect | Q::Checked, 0 ); + setMetric( aspect, 1 ); + + aspect = Q::Groove | state | A::Color; + setColor( aspect | Q::Checked, m_pal.baseActive); + } + + setAnimation( Q::Knop | A::Metric, qskDuration ); + setAnimation( Q::Groove | A::Color, qskDuration ); +} + class QskSquiekSkin::PrivateData { public: diff --git a/src/controls/QskSkin.cpp b/src/controls/QskSkin.cpp index 422d4ad5..df26fca2 100644 --- a/src/controls/QskSkin.cpp +++ b/src/controls/QskSkin.cpp @@ -70,6 +70,9 @@ QSK_QT_PRIVATE_END #include "QskSubWindowArea.h" #include "QskSubWindowAreaSkinlet.h" +#include "QskSwitchButton.h" +#include "QskSwitchButtonSkinlet.h" + #include "QskPageIndicator.h" #include "QskPageIndicatorSkinlet.h" @@ -149,6 +152,7 @@ QskSkin::QskSkin( QObject* parent ) declareSkinlet< QskStatusIndicator, QskStatusIndicatorSkinlet >(); declareSkinlet< QskSubWindow, QskSubWindowSkinlet >(); declareSkinlet< QskSubWindowArea, QskSubWindowAreaSkinlet >(); + declareSkinlet< QskSwitchButton, QskSwitchButtonSkinlet >(); declareSkinlet< QskTabButton, QskTabButtonSkinlet >(); declareSkinlet< QskTabView, QskTabViewSkinlet >(); declareSkinlet< QskTextLabel, QskTextLabelSkinlet >(); diff --git a/src/controls/QskSwitchButton.cpp b/src/controls/QskSwitchButton.cpp new file mode 100644 index 00000000..e8e144a6 --- /dev/null +++ b/src/controls/QskSwitchButton.cpp @@ -0,0 +1,57 @@ +#include "QskSwitchButton.h" + +QSK_SUBCONTROL( QskSwitchButton, Knop ) +QSK_SUBCONTROL( QskSwitchButton, Groove ) + +struct QskSwitchButton::PrivateData +{ + PrivateData() + : orientation( Qt::Horizontal ) + , layoutDirection( Qt::LeftToRight) + { + } + + Qt::Orientation orientation; + Qt::LayoutDirection layoutDirection; +}; + +QskSwitchButton::QskSwitchButton( QQuickItem* parent ) + : QskAbstractButton(parent) + , m_data( new PrivateData() ) +{ + setCheckable( true ); +} + +QskSwitchButton::~QskSwitchButton() { +} + + +Qt::Orientation QskSwitchButton::orientation() const +{ + return m_data->orientation; +} +void QskSwitchButton::setOrientation(Qt::Orientation orientation) +{ + if(m_data->orientation != orientation) + { + m_data->orientation = orientation; + update(); + Q_EMIT orientationChanged( orientation ); + } +} + +Qt::LayoutDirection QskSwitchButton::layoutDirection() const +{ + return m_data->layoutDirection; +} +void QskSwitchButton::setLayoutDirection(Qt::LayoutDirection layoutDirection) +{ + if(m_data->layoutDirection != layoutDirection) + { + m_data->layoutDirection = layoutDirection; + update(); + Q_EMIT layoutDirectionChanged( layoutDirection ); + } +} + +#include "moc_QskSwitchButton.cpp" diff --git a/src/controls/QskSwitchButton.h b/src/controls/QskSwitchButton.h new file mode 100644 index 00000000..b836baea --- /dev/null +++ b/src/controls/QskSwitchButton.h @@ -0,0 +1,39 @@ +#ifndef QSK_SWITCH_BUTTON_H +#define QSK_SWITCH_BUTTON_H + + +#include "QskAbstractButton.h" +#include "QskNamespace.h" + +class QSK_EXPORT QskSwitchButton : public QskAbstractButton +{ + Q_OBJECT + + using Inherited = QskAbstractButton; + + Q_PROPERTY( Qt::Orientation orientation READ orientation + WRITE setOrientation NOTIFY orientationChanged FINAL ) + Q_PROPERTY( Qt::LayoutDirection layoutDirection READ layoutDirection + WRITE setLayoutDirection NOTIFY layoutDirectionChanged FINAL ) + + public: + QSK_SUBCONTROLS( Groove, Knop ) + + QskSwitchButton( QQuickItem* parent = nullptr ); + ~QskSwitchButton() override; + + Qt::Orientation orientation() const; + void setOrientation(Qt::Orientation); + + Qt::LayoutDirection layoutDirection() const; + void setLayoutDirection(Qt::LayoutDirection); + + Q_SIGNALS: + void orientationChanged( Qt::Orientation ); + void layoutDirectionChanged(Qt::LayoutDirection); + + private: + struct PrivateData; + std::unique_ptr< PrivateData > m_data; +}; +#endif diff --git a/src/controls/QskSwitchButtonSkinlet.cpp b/src/controls/QskSwitchButtonSkinlet.cpp new file mode 100644 index 00000000..6de73be4 --- /dev/null +++ b/src/controls/QskSwitchButtonSkinlet.cpp @@ -0,0 +1,120 @@ + +#include "QskSwitchButton.h" +#include "QskSwitchButtonSkinlet.h" +#include "QskSGNode.h" +QskSwitchButtonSkinlet::QskSwitchButtonSkinlet(QskSkin* skin) + : Inherited( skin ) +{ + setNodeRoles( { GrooveRole, KnopRole } ); +} + +QskSwitchButtonSkinlet::~QskSwitchButtonSkinlet() {} + +QRectF QskSwitchButtonSkinlet::subControlRect( const QskSkinnable* skinnable, + const QRectF& contentsRect, QskAspect::Subcontrol subControl) const +{ + using Q = QskSwitchButton; + const auto switchButton = static_cast< const Q* >( skinnable ); + + if (!switchButton) + { + return Inherited::subControlRect( skinnable, contentsRect, subControl ); + } + + if ( subControl == Q::Knop) + { + const auto diameter = 2 * skinnable->metric(QskSwitchButton::Knop | QskAspect::Size); + const auto grooveSize = skinnable->strutSizeHint(QskSwitchButton::Groove); + auto position = skinnable->metric( Q::Knop | QskAspect::Position ); + + auto rect = QRectF(0, 0, diameter, diameter); + + if(switchButton->layoutDirection() == Qt::RightToLeft) + { + position = 1 - position; + } + + if(switchButton->orientation() == Qt::Vertical) + { + if(diameter < grooveSize.height() ) + { + rect.moveLeft( ( grooveSize.height() - diameter ) / 2); + } + rect.moveTop( ( grooveSize.height() - diameter ) / 2 + + position * ( grooveSize.width() - diameter + - ( grooveSize.height() - diameter ) ) ); + } + else + { + if(diameter < grooveSize.height() ) + { + rect.moveTop( ( grooveSize.height() - diameter ) / 2); + } + rect.moveLeft( ( grooveSize.height() - diameter ) / 2 + + position * ( grooveSize.width() - diameter + - ( grooveSize.height() - diameter ) ) ); + } + + return rect; + } + else if ( subControl == Q::Groove ) + { + auto diameter = 2 * skinnable->metric(QskSwitchButton::Knop | QskAspect::Size); + const auto grooveSize = skinnable->strutSizeHint(QskSwitchButton::Groove); + auto result = contentsRect; + result.setSize( QSizeF(grooveSize.width(), grooveSize.height() ) ); + + if(switchButton->orientation() == Qt::Vertical ) + { + if(grooveSize.height() < diameter) + { + result.moveLeft( ( diameter - result.height() ) / 2); + } + return result.transposed(); + } + else + { + if(grooveSize.height() < diameter) + { + result.moveTop( ( diameter - result.height() ) / 2); + } + return result; + } + } + + return Inherited::subControlRect( skinnable, contentsRect, subControl ); +} + +QSizeF QskSwitchButtonSkinlet::sizeHint( const QskSkinnable* skinnable, + Qt::SizeHint, const QSizeF&) const +{ + const auto switchButton = static_cast< const QskSwitchButton* >( skinnable ); + const auto diameter = 2 * skinnable->metric(QskSwitchButton::Knop | QskAspect::Size); + const auto grooveSize = skinnable->strutSizeHint(QskSwitchButton::Groove); + + auto result = QSizeF(qMax(diameter, grooveSize.width() ), + qMax(diameter, grooveSize.height() ) ); + if(switchButton->orientation() == Qt::Vertical) + { + return result.transposed(); + } + + return result; +} + +QSGNode* QskSwitchButtonSkinlet::updateSubNode( const QskSkinnable* skinnable, + quint8 nodeRole, QSGNode* node) const +{ + const auto switchButton = static_cast< const QskSwitchButton* >( skinnable ); + + switch ( nodeRole ) + { + case KnopRole: + return updateBoxNode( switchButton, node, QskSwitchButton::Knop ); + + case GrooveRole: + return updateBoxNode( switchButton, node, QskSwitchButton::Groove ); + } + + return Inherited::updateSubNode( skinnable, nodeRole, node ); +} diff --git a/src/controls/QskSwitchButtonSkinlet.h b/src/controls/QskSwitchButtonSkinlet.h new file mode 100644 index 00000000..7810aa99 --- /dev/null +++ b/src/controls/QskSwitchButtonSkinlet.h @@ -0,0 +1,34 @@ +#ifndef QSK_SWITCH_BUTTON_SKINLET_H +#define QSK_SWITCH_BUTTON_SKINLET_H + + +#include "QskSkinlet.h" + +class QSK_EXPORT QskSwitchButtonSkinlet : public QskSkinlet +{ + Q_GADGET + + using Inherited = QskSkinlet; + + public: + enum NodeRole + { + KnopRole, + GrooveRole + }; + + Q_INVOKABLE QskSwitchButtonSkinlet( QskSkin* parent = nullptr ); + ~QskSwitchButtonSkinlet() override; + + + QRectF subControlRect( const QskSkinnable*, + const QRectF& rect, 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 diff --git a/src/src.pro b/src/src.pro index d044acdd..a9437e43 100644 --- a/src/src.pro +++ b/src/src.pro @@ -183,6 +183,8 @@ HEADERS += \ controls/QskSubWindowAreaSkinlet.h \ controls/QskSubWindow.h \ controls/QskSubWindowSkinlet.h \ + controls/QskSwitchButton.h \ + controls/QskSwitchButtonSkinlet.h \ controls/QskTabBar.h \ controls/QskTabButton.h \ controls/QskTabButtonSkinlet.h \ @@ -257,6 +259,8 @@ SOURCES += \ controls/QskSubWindowAreaSkinlet.cpp \ controls/QskSubWindow.cpp \ controls/QskSubWindowSkinlet.cpp \ + controls/QskSwitchButton.cpp \ + controls/QskSwitchButtonSkinlet.cpp \ controls/QskTabBar.cpp \ controls/QskTabButton.cpp \ controls/QskTabButtonSkinlet.cpp \