diff --git a/examples/gallery/spinbox/SpinBoxPage.cpp b/examples/gallery/spinbox/SpinBoxPage.cpp index ae559c6c..5f29bfd3 100644 --- a/examples/gallery/spinbox/SpinBoxPage.cpp +++ b/examples/gallery/spinbox/SpinBoxPage.cpp @@ -4,12 +4,14 @@ *****************************************************************************/ #include "SpinBoxPage.h" -#include #include -#include #include +#include +#include +#include -SpinBoxPage::SpinBoxPage( QQuickItem* parent ) : Page( Qt::Horizontal, parent ) +SpinBoxPage::SpinBoxPage( QQuickItem* parent ) + : Page( Qt::Horizontal, parent ) { setMargins( 10 ); setSpacing( 20 ); @@ -19,32 +21,85 @@ SpinBoxPage::SpinBoxPage( QQuickItem* parent ) : Page( Qt::Horizontal, parent ) void SpinBoxPage::populate() { - const QMap layouts = - { - { Qt::AlignLeft, QStringLiteral("Qt::AlignLeft") }, - { Qt::AlignHCenter, QStringLiteral("Qt::AlignHCenter") }, - { Qt::AlignRight, QStringLiteral("Qt::AlignRight") }, - { Qt::AlignTop, QStringLiteral("Qt::AlignTop") }, - { Qt::AlignVCenter, QStringLiteral("Qt::AlignVCenter") }, - { Qt::AlignBottom, QStringLiteral("Qt::AlignBottom") }, - { Qt::AlignLeft | Qt::AlignVCenter, QStringLiteral("Qt::AlignLeft | Qt::AlignVCenter") }, - { Qt::AlignRight | Qt::AlignVCenter, QStringLiteral("Qt::AlignRight | Qt::AlignVCenter") }, - { Qt::AlignTop | Qt::AlignHCenter, QStringLiteral("Qt::AlignTop | Qt::AlignHCenter") }, - { Qt::AlignBottom | Qt::AlignHCenter, QStringLiteral("Qt::AlignBottom | Qt::AlignHCenter") } - }; + const QMap< Qt::Alignment, QString > layouts = { { Qt::AlignLeft, + QStringLiteral( "Qt::AlignLeft" ) }, + { Qt::AlignHCenter, QStringLiteral( "Qt::AlignHCenter" ) }, + { Qt::AlignRight, QStringLiteral( "Qt::AlignRight" ) }, + { Qt::AlignTop, QStringLiteral( "Qt::AlignTop" ) }, + { Qt::AlignVCenter, QStringLiteral( "Qt::AlignVCenter" ) }, + { Qt::AlignBottom, QStringLiteral( "Qt::AlignBottom" ) }, + { Qt::AlignLeft | Qt::AlignVCenter, QStringLiteral( "Qt::AlignLeft | Qt::AlignVCenter" ) }, + { Qt::AlignRight | Qt::AlignVCenter, + QStringLiteral( "Qt::AlignRight | Qt::AlignVCenter" ) }, + { Qt::AlignTop | Qt::AlignHCenter, QStringLiteral( "Qt::AlignTop | Qt::AlignHCenter" ) }, + { Qt::AlignBottom | Qt::AlignHCenter, + QStringLiteral( "Qt::AlignBottom | Qt::AlignHCenter" ) } }; - auto* const grid = new QskGridBox(this); - constexpr int cols = 5; - for(const auto& layout : layouts.keys()) - { - const auto x = grid->elementCount() % cols; - const auto y = grid->elementCount() / cols; - auto* const column = new QskLinearBox(Qt::Vertical, grid); - auto* const label = new QskTextLabel(layouts.value(layout), column); // TODO put label either on top or on the bottom - auto* const spinbox = new QskSpinBox( column ); - spinbox->setAlignmentHint(QskSpinBox::Layout, layout); - grid->addItem(column, y, x); - column->setStretchFactor(label, 1); - column->setStretchFactor(spinbox, 99); - } + auto* const grid = new QskGridBox( this ); + constexpr int cols = 5; + + QVector< QskSpinBox* > spinboxes; + for ( const auto& layout : layouts.keys() ) + { + const auto x = grid->elementCount() % cols; + const auto y = grid->elementCount() / cols; + auto* const column = new QskLinearBox( Qt::Vertical, grid ); + auto* const label = new QskTextLabel( layouts.value( layout ), column ); + auto* const spinbox = new QskSpinBox( column ); + spinbox->setAlignmentHint( QskSpinBox::Layout, layout ); + grid->addItem( column, y, x ); + column->setStretchFactor( label, 1 ); + column->setStretchFactor( spinbox, 99 ); + spinboxes << spinbox; + } + + const auto strutInc = spinboxes[ 0 ]->strutSizeHint( QskSpinBox::IncrementPanel ); + const auto strutDec = spinboxes[ 0 ]->strutSizeHint( QskSpinBox::DecrementPanel ); + + auto* const columnIncW = new QskLinearBox( Qt::Vertical, this ); + auto* const sliderIncW = new QskSlider( Qt::Vertical, columnIncW ); + auto* const labelsIncW = new QskTextLabel( "+W", columnIncW ); + + auto* const columnIncH = new QskLinearBox( Qt::Vertical, this ); + auto* const sliderIncH = new QskSlider( Qt::Vertical, columnIncH ); + auto* const labelsIncH = new QskTextLabel( "+H", columnIncH ); + + auto* const columnDecW = new QskLinearBox( Qt::Vertical, this ); + auto* const sliderDecW = new QskSlider( Qt::Vertical, columnDecW ); + auto* const labelsDecW = new QskTextLabel( "-W", columnDecW ); + + auto* const columnDecH = new QskLinearBox( Qt::Vertical, this ); + auto* const sliderDecH = new QskSlider( Qt::Vertical, columnDecH ); + auto* const labelsDecH = new QskTextLabel( "-H", columnDecH ); + + setStretchFactor( columnIncW, 1 ); + setStretchFactor( columnIncH, 1 ); + setStretchFactor( columnDecW, 1 ); + setStretchFactor( columnDecH, 1 ); + setStretchFactor( grid, 99 ); + + sliderIncW->setBoundaries( 2, strutInc.width() * 4 ); + sliderIncH->setBoundaries( 2, strutInc.height() * 4 ); + sliderDecW->setBoundaries( 2, strutDec.width() * 4 ); + sliderDecH->setBoundaries( 2, strutDec.height() * 4 ); + + sliderIncW->setValue( strutInc.width() ); + sliderIncH->setValue( strutInc.height() ); + sliderDecW->setValue( strutDec.width() ); + sliderDecH->setValue( strutDec.height() ); + + auto update = [ spinboxes, sliderIncW, sliderIncH, sliderDecW, sliderDecH ]( qreal v ) { + const auto incSize = QSizeF{ sliderIncW->value(), sliderIncH->value() }; + const auto decSize = QSizeF{ sliderDecW->value(), sliderDecH->value() }; + for ( auto* spinbox : spinboxes ) + { + spinbox->setStrutSizeHint( QskSpinBox::IncrementPanel, incSize ); + spinbox->setStrutSizeHint( QskSpinBox::DecrementPanel, decSize ); + } + }; + + connect( sliderIncW, &QskSlider::valueChanged, this, update ); + connect( sliderIncH, &QskSlider::valueChanged, this, update ); + connect( sliderDecW, &QskSlider::valueChanged, this, update ); + connect( sliderDecH, &QskSlider::valueChanged, this, update ); } diff --git a/skins/material3/QskMaterial3Skin.cpp b/skins/material3/QskMaterial3Skin.cpp index 8b5f0f52..4ed9b714 100644 --- a/skins/material3/QskMaterial3Skin.cpp +++ b/skins/material3/QskMaterial3Skin.cpp @@ -717,20 +717,19 @@ void Editor::setupSpinBox() setSpacing(QskSpinBox::Layout, 4_dp); setStrutSize(QskSpinBox::TextPanel | QskAspect::Size, {80_dp,40_dp}); - setStrutSize(QskSpinBox::Inc | QskAspect::Size, {40_dp,40_dp}); - setStrutSize(QskSpinBox::Dec | QskAspect::Size, {40_dp,40_dp}); + setStrutSize(QskSpinBox::IncrementPanel | QskAspect::Size, {40_dp,40_dp}); + setStrutSize(QskSpinBox::DecrementPanel | QskAspect::Size, {40_dp,40_dp}); setAlignment(QskSpinBox::Layout, Qt::AlignHCenter); setAlignment(Q::Text, Qt::AlignCenter); - for(const auto& state : {QskSpinBox::Dec, QskSpinBox::Inc, QskSpinBox::TextPanel}) + for(const auto& state : {QskSpinBox::DecrementPanel, QskSpinBox::IncrementPanel, QskSpinBox::TextPanel}) { setBoxShape(state, 4_dp); - setBoxBorderColors(state, QColor("#79747E")); setBoxBorderMetrics(state, 1_dp); } - for(const auto& state : {QskSpinBox::Dec, QskSpinBox::Inc}) + for(const auto& state : {QskSpinBox::DecrementPanel, QskSpinBox::IncrementPanel}) { setGradient( state, m_pal.primary ); setGradient( state | Q::Disabled, m_pal.onSurface12 ); @@ -745,7 +744,7 @@ void Editor::setupSpinBox() setShadowColor( state | Q::Hovered, m_pal.shadow ); } - for(const auto& state : {QskSpinBox::DecText, QskSpinBox::IncText}) + for(const auto& state : {QskSpinBox::DecrementText, QskSpinBox::IncrementText}) { setColor( state, m_pal.onPrimary ); setColor( state | Q::Disabled, m_pal.onSurface38 ); diff --git a/src/controls/QskSpinBox.cpp b/src/controls/QskSpinBox.cpp index 186d4db6..d062cbc8 100644 --- a/src/controls/QskSpinBox.cpp +++ b/src/controls/QskSpinBox.cpp @@ -4,357 +4,464 @@ *****************************************************************************/ #include "QskSpinBox.h" -#include "QskLinearBox.h" -#include "QskGridBox.h" -#include "QskTextInput.h" -#include "QskBoxShapeMetrics.h" -#include "QskBoxBorderColors.h" -#include "QskBoxBorderMetrics.h" -#include "QskSkinlet.h" -#include "QskIntervalF.h" -#include "QskEvent.h" - -#include #include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -QSK_SUBCONTROL(QskSpinBox, Inc) -QSK_SUBCONTROL(QskSpinBox, Dec) -QSK_SUBCONTROL(QskSpinBox, IncText) -QSK_SUBCONTROL(QskSpinBox, DecText) -QSK_SUBCONTROL(QskSpinBox, Text) -QSK_SUBCONTROL(QskSpinBox, TextPanel) -QSK_SUBCONTROL(QskSpinBox, Layout) +QSK_SUBCONTROL( QskSpinBox, IncrementPanel ) +QSK_SUBCONTROL( QskSpinBox, DecrementPanel ) +QSK_SUBCONTROL( QskSpinBox, IncrementText ) +QSK_SUBCONTROL( QskSpinBox, DecrementText ) +QSK_SUBCONTROL( QskSpinBox, Text ) +QSK_SUBCONTROL( QskSpinBox, TextPanel ) +QSK_SUBCONTROL( QskSpinBox, Layout ) -QSK_SYSTEM_STATE(QskSpinBox, Pressed, ( QskAspect::QskAspect::FirstSystemState << 0)) +QSK_SYSTEM_STATE( QskSpinBox, Pressed, ( QskAspect::QskAspect::FirstSystemState << 0 ) ) + +namespace aliased_enum +{ + constexpr auto D = QskSpinBox::Decrement; + constexpr auto T = QskSpinBox::Textbox; + constexpr auto I = QskSpinBox::Increment; + constexpr auto N = QskSpinBox::None; +} class QskSpinBox::PrivateData { -public: - - enum FocusIndeces : int { Dec = 0, Text = 1, Inc = 2, None = 3 }; - - explicit PrivateData(QskSpinBox* const parent) : q(parent) - { - } - - void setValue(qreal value) - { - value = qBound(q->minimum(), value, q->maximum()); - if(!qFuzzyCompare(m_value, value)) + public: + explicit PrivateData( QskSpinBox* const parent ) + : q( parent ) { - m_value = value; - Q_EMIT q->valueChanged(m_value); - q->polish(); - q->update(); } - } - qreal value() const { return m_value; } - - FocusIndeces defaultFocusIndex() const - { - const auto layout = q->alignmentHint(QskSpinBox::Layout); - - if(layout == Qt::AlignLeft) return Text; - if(layout == Qt::AlignRight) return Dec; - if(layout == Qt::AlignHCenter) return Dec; - if(layout == Qt::AlignTop) return Text; - if(layout == Qt::AlignBottom) return Inc; - if(layout == Qt::AlignVCenter) return Inc; - if(layout == (Qt::AlignLeft | Qt::AlignVCenter)) return Text; - if(layout == (Qt::AlignRight | Qt::AlignVCenter)) return Inc; - if(layout == (Qt::AlignTop | Qt::AlignHCenter)) return Text; - if(layout == (Qt::AlignBottom | Qt::AlignHCenter)) return Dec; - - return None; - } - - FocusIndeces nextFocusIndex() const - { - const auto layout = q->alignmentHint(QskSpinBox::Layout); - - // [0 ][1 ][2 ][3 ] - // [Dec][Text][Inc][None] - using LUT = std::array; - if(layout == Qt::AlignLeft) return LUT{Inc,Dec,None,Text}[m_focusIndex]; - if(layout == Qt::AlignRight) return LUT{Inc,None,Text,Dec}[m_focusIndex]; - if(layout == Qt::AlignHCenter) return LUT{Text,Inc,None,Dec}[m_focusIndex]; - if(layout == Qt::AlignTop) return LUT{Inc,Dec,None,Text}[m_focusIndex]; - if(layout == Qt::AlignBottom) return LUT{Inc,None,Text,Dec}[m_focusIndex]; - if(layout == Qt::AlignVCenter) return LUT{None,Dec,Text,Inc}[m_focusIndex]; - if(layout == (Qt::AlignLeft | Qt::AlignVCenter)) return LUT{None,Inc,Dec,Text}[m_focusIndex]; - if(layout == (Qt::AlignRight | Qt::AlignVCenter)) return LUT{Text,None,Dec,Inc}[m_focusIndex]; - if(layout == (Qt::AlignTop | Qt::AlignHCenter)) return LUT{Inc,Dec,None,Text}[m_focusIndex]; - if(layout == (Qt::AlignBottom | Qt::AlignHCenter)) return LUT{Inc,None,Text,Dec}[m_focusIndex]; - - return None; - } - - FocusIndeces previousFocusIndex() const - { - const auto layout = q->alignmentHint(QskSpinBox::Layout); - - // [0 ][1 ][2 ][3 ] - // [Dec][Text][Inc][None] - using LUT = std::array; - if(layout == Qt::AlignLeft) return LUT{None,Dec,Text,Inc}[m_focusIndex]; - if(layout == Qt::AlignRight) return LUT{None,Inc,Dec,Text}[m_focusIndex]; - if(layout == Qt::AlignHCenter) return LUT{None,Dec,Text,Inc}[m_focusIndex]; - if(layout == Qt::AlignTop) return LUT{Text,None,Dec,Inc}[m_focusIndex]; - if(layout == Qt::AlignBottom) return LUT{None,Inc,Dec,Text}[m_focusIndex]; - if(layout == Qt::AlignVCenter) return LUT{Text,Inc,None,Dec}[m_focusIndex]; - if(layout == (Qt::AlignLeft | Qt::AlignVCenter)) return LUT{Inc,None,Text,Dec}[m_focusIndex]; - if(layout == (Qt::AlignRight | Qt::AlignVCenter)) return LUT{Inc,Dec,None,Text}[m_focusIndex]; - if(layout == (Qt::AlignTop | Qt::AlignHCenter)) return LUT{Text,None,Dec,Inc}[m_focusIndex]; - if(layout == (Qt::AlignBottom | Qt::AlignHCenter)) return LUT{None,Inc,Dec,Text}[m_focusIndex]; - - return None; - } - - FocusIndeces focusIndex() const - { - return m_focusIndex; - } - - void focusNext() - { - const auto index = nextFocusIndex(); - setFocus(index); - } - - void focusPrevious() - { - const auto index = previousFocusIndex(); - setFocus(index); - } - - void focusDefault() - { - setFocus(defaultFocusIndex()); - } - - void setFocus(const FocusIndeces index) - { - Q_ASSERT(index == Dec || index == Text || index == Inc || index == None); - if(index == Dec || index == Text || index == Inc || index == None) + FocusIndeces defaultFocusIndex() const { - m_focusIndex = index; - Q_EMIT q->focusIndexChanged(m_focusIndex); // TODO register enum - } - } + const auto layout = q->alignmentHint( QskSpinBox::Layout ); - QRectF focusIndicatorRect() const - { - switch(m_focusIndex) + if ( layout == Qt::AlignLeft ) + { + return QskSpinBox::Textbox; + } + if ( layout == Qt::AlignRight ) + { + return QskSpinBox::Decrement; + } + if ( layout == Qt::AlignHCenter ) + { + return QskSpinBox::Decrement; + } + if ( layout == Qt::AlignTop ) + { + return QskSpinBox::Textbox; + } + if ( layout == Qt::AlignBottom ) + { + return QskSpinBox::Increment; + } + if ( layout == Qt::AlignVCenter ) + { + return QskSpinBox::Increment; + } + if ( layout == ( Qt::AlignLeft | Qt::AlignVCenter ) ) + { + return QskSpinBox::Textbox; + } + if ( layout == ( Qt::AlignRight | Qt::AlignVCenter ) ) + { + return QskSpinBox::Increment; + } + if ( layout == ( Qt::AlignTop | Qt::AlignHCenter ) ) + { + return QskSpinBox::Textbox; + } + if ( layout == ( Qt::AlignBottom | Qt::AlignHCenter ) ) + { + return QskSpinBox::Decrement; + } + + return None; + } + + FocusIndeces nextFocusIndex() const { - case PrivateData::FocusIndeces::Dec: - return q->subControlRect(QskSpinBox::Dec); - case PrivateData::FocusIndeces::Text: - return q->subControlRect(QskSpinBox::TextPanel); - case PrivateData::FocusIndeces::Inc: - return q->subControlRect(QskSpinBox::Inc); - default: return {}; + const auto layout = q->alignmentHint( QskSpinBox::Layout ); + using namespace aliased_enum; + + // [0][1][2][3] := index + // [D][T][I][N] := control + using LUT = std::array< QskSpinBox::FocusIndeces, 4 >; + if ( layout == Qt::AlignLeft ) + { + return LUT{ I, D, N, T }[ m_focusIndex ]; + } + if ( layout == Qt::AlignRight ) + { + return LUT{ I, N, T, D }[ m_focusIndex ]; + } + if ( layout == Qt::AlignHCenter ) + { + return LUT{ T, I, N, D }[ m_focusIndex ]; + } + if ( layout == Qt::AlignTop ) + { + return LUT{ N, I, D, T }[ m_focusIndex ]; + } + if ( layout == Qt::AlignBottom ) + { + return LUT{ T, N, D, I }[ m_focusIndex ]; + } + if ( layout == Qt::AlignVCenter ) + { + return LUT{ N, D, T, I }[ m_focusIndex ]; + } + if ( layout == ( Qt::AlignLeft | Qt::AlignVCenter ) ) + { + return LUT{ N, I, D, T }[ m_focusIndex ]; + } + if ( layout == ( Qt::AlignRight | Qt::AlignVCenter ) ) + { + return LUT{ T, N, D, I }[ m_focusIndex ]; + } + if ( layout == ( Qt::AlignTop | Qt::AlignHCenter ) ) + { + return LUT{ I, D, N, T }[ m_focusIndex ]; + } + if ( layout == ( Qt::AlignBottom | Qt::AlignHCenter ) ) + { + return LUT{ I, N, T, D }[ m_focusIndex ]; + } + + return None; } - } - void saveMousePosition(const QPointF& pos) - { - q->setSkinHint(QskSpinBox::Layout | QskAspect::Metric | QskAspect::Position, pos ); - } + FocusIndeces previousFocusIndex() const + { + const auto layout = q->alignmentHint( QskSpinBox::Layout ); + using namespace aliased_enum; -private: - qreal m_value{0.0f}; - QskSpinBox* const q; - FocusIndeces m_focusIndex = FocusIndeces::None; + // [0][1][2][3] := index + // [D][T][I][N] := control + using LUT = std::array< FocusIndeces, 4 >; + if ( layout == Qt::AlignLeft ) + { + return LUT{ T, N, D, I }[ m_focusIndex ]; + } + if ( layout == Qt::AlignRight ) + { + return LUT{ N, I, D, T }[ m_focusIndex ]; + } + if ( layout == Qt::AlignHCenter ) + { + return LUT{ N, D, T, I }[ m_focusIndex ]; + } + if ( layout == Qt::AlignTop ) + { + return LUT{ I, N, T, D }[ m_focusIndex ]; + } + if ( layout == Qt::AlignBottom ) + { + return LUT{ I, D, N, T }[ m_focusIndex ]; + } + if ( layout == Qt::AlignVCenter ) + { + return LUT{ T, I, N, D }[ m_focusIndex ]; + } + if ( layout == ( Qt::AlignLeft | Qt::AlignVCenter ) ) + { + return LUT{ I, N, T, D }[ m_focusIndex ]; + } + if ( layout == ( Qt::AlignRight | Qt::AlignVCenter ) ) + { + return LUT{ I, D, N, T }[ m_focusIndex ]; + } + if ( layout == ( Qt::AlignTop | Qt::AlignHCenter ) ) + { + return LUT{ T, N, D, I }[ m_focusIndex ]; + } + if ( layout == ( Qt::AlignBottom | Qt::AlignHCenter ) ) + { + return LUT{ N, I, D, T }[ m_focusIndex ]; + } + + return None; + } + + FocusIndeces focusIndex() const + { + return m_focusIndex; + } + + void focusNext() + { + const auto index = nextFocusIndex(); + setFocus( index ); + } + + void focusPrevious() + { + const auto index = previousFocusIndex(); + setFocus( index ); + } + + void focusDefault() + { + const auto index = defaultFocusIndex(); + setFocus( index ); + } + + void setFocus( const FocusIndeces index ) + { + using namespace aliased_enum; + Q_ASSERT( index == D || index == T || index == I || index == N ); + if ( index == D || index == T || index == I || index == N ) + { + m_focusIndex = index; + Q_EMIT q->focusIndexChanged( m_focusIndex ); + q->update(); + } + } + + QRectF focusIndicatorRect() const + { + if ( m_focusIndex == QskSpinBox::Decrement ) + { + return q->subControlRect( QskSpinBox::DecrementPanel ); + } + if ( m_focusIndex == QskSpinBox::Increment ) + { + return q->subControlRect( QskSpinBox::IncrementPanel ); + } + if ( m_focusIndex == QskSpinBox::Textbox ) + { + return q->subControlRect( QskSpinBox::TextPanel ); + } + return {}; + } + + void saveMousePosition( const QPointF& pos ) + { + q->setSkinHint( QskSpinBox::Layout | QskAspect::Metric | QskAspect::Position, pos ); + } + + bool focusNow() const + { + const auto focusOnClick = ( q->focusPolicy() & Qt::ClickFocus ) == Qt::ClickFocus; + const auto focusOnTouchRelease = QGuiApplication::styleHints()->setFocusOnTouchRelease(); + return focusOnClick && !focusOnTouchRelease; + } + + private: + QskSpinBox* const q; + FocusIndeces m_focusIndex = FocusIndeces::None; }; using S = QskSpinBox; -QskSpinBox::QskSpinBox(QQuickItem* const parent) - : Inherited(parent) - , m_data( new PrivateData( this ) ) +QskSpinBox::QskSpinBox( QQuickItem* const parent ) + : Inherited( parent ) + , m_data( std::make_unique< PrivateData >( this ) ) { - setBoundaries(0.0,1.0); - setAcceptHoverEvents(true); - setAcceptedMouseButtons(Qt::LeftButton); - setFocusPolicy( Qt::StrongFocus ); + setBoundaries( 0.0, 1.0 ); + setAcceptHoverEvents( true ); + setAcceptedMouseButtons( Qt::LeftButton ); + setFocusPolicy( Qt::StrongFocus ); - connect( this, &S::focusIndexChanged, this, &S::focusIndicatorRectChanged ); - connect( this, &S::boundariesChanged, this, [this](const QskIntervalF& interval){ if(!interval.contains(value())) m_data->setValue(minimum()); }); - connect( this, &S::valueChanged, this, [this](){ polish(); }); + connect( this, &S::focusIndexChanged, this, &S::focusIndicatorRectChanged ); } QskSpinBox::~QskSpinBox() = default; -void QskSpinBox::hoverEnterEvent(QHoverEvent* event) +void QskSpinBox::hoverEnterEvent( QHoverEvent* const event ) { - m_data->saveMousePosition( qskHoverPosition( event ) ); + m_data->saveMousePosition( qskHoverPosition( event ) ); } -void QskSpinBox::hoverLeaveEvent(QHoverEvent* ) +void QskSpinBox::hoverLeaveEvent( QHoverEvent* /*const event */ ) { - m_data->saveMousePosition( {} ); + m_data->saveMousePosition( {} ); } -void QskSpinBox::hoverMoveEvent(QHoverEvent *event) +void QskSpinBox::hoverMoveEvent( QHoverEvent* const event ) { - m_data->saveMousePosition( qskHoverPosition( event ) ); + m_data->saveMousePosition( qskHoverPosition( event ) ); } -void QskSpinBox::mouseReleaseEvent(QMouseEvent *event) +void QskSpinBox::mouseReleaseEvent( QMouseEvent* const event ) { - m_data->saveMousePosition( qskMousePosition( event ) ); + m_data->saveMousePosition( qskMousePosition( event ) ); - const auto focus = ( focusPolicy() & Qt::ClickFocus ) == Qt::ClickFocus && !QGuiApplication::styleHints()->setFocusOnTouchRelease(); + const auto focus = m_data->focusNow(); - if(subControlRect(QskSpinBox::Inc).contains( event->pos() )) - { - increment(+stepSize()); - - if( focus ) + if ( subControlRect( QskSpinBox::IncrementPanel ).contains( event->pos() ) ) { - m_data->setFocus(PrivateData::Inc); - } + increment( +stepSize() ); - return; - } - - if(subControlRect(QskSpinBox::Dec).contains( event->pos() )) - { - increment(-stepSize()); - - if( focus ) - { - m_data->setFocus(PrivateData::Dec); - } - - return; - } - - if(subControlRect(QskSpinBox::TextPanel).contains( event->pos() )) - { - if( focus ) - { - m_data->setFocus(PrivateData::Text); - } - - return; - } - - event->ignore(); -} - -void QskSpinBox::mousePressEvent(QMouseEvent *event) -{ - m_data->saveMousePosition( -1 * qskMousePosition( event ) ); - - const auto focus = ( focusPolicy() & Qt::ClickFocus ) == Qt::ClickFocus && !QGuiApplication::styleHints()->setFocusOnTouchRelease(); - - if(subControlRect(QskSpinBox::Inc).contains( event->pos() )) - { - if( focus ) - { - m_data->setFocus(PrivateData::Inc); - } - return; - } - - if(subControlRect(QskSpinBox::Dec).contains( event->pos() )) - { - if( focus ) - { - m_data->setFocus(PrivateData::Dec); - } - return; - } - - event->ignore(); -} - -void QskSpinBox::keyPressEvent(QKeyEvent *event) -{ - switch( event->key() ) - { - case Qt::Key_Plus: - case Qt::Key_Up: - case Qt::Key_Right: - // TODO increment - break; - case Qt::Key_Minus: - case Qt::Key_Down: - case Qt::Key_Left: - // TODO decrement - break; - case Qt::Key_Select: - case Qt::Key_Space: - // TODO click currently focused -/+ - break; - default: - { - const int steps = qskFocusChainIncrement( event ); - if(steps != 0) - { - for(int i = 0; i < steps; ++i) + if ( focus ) { - m_data->focusNext(); + m_data->setFocus( Increment ); } - for(int i = steps; i < 0; ++i) - { - m_data->focusPrevious(); - } - } + + return; } - } - Inherited::keyPressEvent( event ); + + if ( subControlRect( QskSpinBox::DecrementPanel ).contains( event->pos() ) ) + { + increment( -stepSize() ); + + if ( focus ) + { + m_data->setFocus( Decrement ); + } + + return; + } + + if ( subControlRect( QskSpinBox::TextPanel ).contains( event->pos() ) ) + { + if ( focus ) + { + m_data->setFocus( Textbox ); + } + + return; + } + + event->ignore(); } -void QskSpinBox::keyReleaseEvent( QKeyEvent* event ) +void QskSpinBox::mousePressEvent( QMouseEvent* const event ) { - if( event->key() == Qt::Key_Select || event->key() == Qt::Key_Space ) - { - return; - } + m_data->saveMousePosition( -1 * qskMousePosition( event ) ); - Inherited::keyReleaseEvent( event ); + const auto focus = m_data->focusNow(); + + if ( subControlRect( QskSpinBox::IncrementPanel ).contains( event->pos() ) ) + { + if ( focus ) + { + m_data->setFocus( QskSpinBox::Increment ); + } + return; + } + + if ( subControlRect( QskSpinBox::DecrementPanel ).contains( event->pos() ) ) + { + if ( focus ) + { + m_data->setFocus( QskSpinBox::Decrement ); + } + return; + } + + event->ignore(); } -void QskSpinBox::focusInEvent(QFocusEvent *event) +void QskSpinBox::keyPressEvent( QKeyEvent* const event ) { - switch( event->reason() ) - { - case Qt::TabFocusReason: - m_data->focusNext(); - break; + switch ( event->key() ) + { + case Qt::Key_Plus: + case Qt::Key_Up: + case Qt::Key_Right: + increment( +stepSize() ); + return; + case Qt::Key_Minus: + case Qt::Key_Down: + case Qt::Key_Left: + increment( -stepSize() ); + return; + case Qt::Key_Select: + case Qt::Key_Space: + if ( focusIndex() == Increment ) + { + increment( +stepSize() ); + } + if ( focusIndex() == Decrement ) + { + increment( -stepSize() ); + } + return; + default: + break; + } - case Qt::BacktabFocusReason: - m_data->focusPrevious(); - break; + const int steps = qskFocusChainIncrement( event ); - default: - if(m_data->focusIndex() == PrivateData::None) - { + if ( steps < 0 ) + { + for ( int i = 0; i < qAbs( steps ); ++i ) + { + m_data->focusPrevious(); + } + } + + if ( steps > 0 ) + { + for ( int i = 0; i < steps; ++i ) + { + m_data->focusNext(); + } + } + + if ( steps != 0 && m_data->focusIndex() != None ) + { + return; + } + + Inherited::keyPressEvent( event ); +} + +void QskSpinBox::keyReleaseEvent( QKeyEvent* const event ) +{ + if ( event->key() == Qt::Key_Select || event->key() == Qt::Key_Space ) + { + return; + } + + Inherited::keyReleaseEvent( event ); +} + +void QskSpinBox::focusInEvent( QFocusEvent* const event ) +{ + if ( event->reason() == Qt::TabFocusReason ) + { + m_data->focusNext(); + return; + } + + if ( event->reason() == Qt::BacktabFocusReason ) + { + m_data->focusPrevious(); + return; + } + + if ( m_data->focusIndex() == QskSpinBox::None ) + { m_data->focusDefault(); return; - } - } - Inherited::focusInEvent( event ); + } + + Inherited::focusInEvent( event ); } QRectF QskSpinBox::focusIndicatorRect() const { - auto rect = m_data->focusIndicatorRect(); - return rect; + return m_data->focusIndicatorRect(); } -void QskSpinBox::increment(qreal offset) +QskSpinBox::FocusIndeces QskSpinBox::focusIndex() const { - m_data->setValue(m_data->value() + offset); -} - -qreal QskSpinBox::value() const -{ - return m_data->value(); + return m_data->focusIndex(); } diff --git a/src/controls/QskSpinBox.h b/src/controls/QskSpinBox.h index 0f4db5d6..3cd17a6b 100644 --- a/src/controls/QskSpinBox.h +++ b/src/controls/QskSpinBox.h @@ -6,44 +6,87 @@ #ifndef QSK_SPIN_BOX_H #define QSK_SPIN_BOX_H -#include +#include -class QSK_EXPORT QskSpinBox : public QskBoundedInput + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// @brief +/// This control allows the user to increment and decrement a floating point value. +/// @details +/// The incement and decrement step size is configurable and the value's range can be limited +/// through an inclusive interval [min,max]. The value is being displayed on a readonly text label +/// surrounded by an increment and decrement button. +/// +/// - The value can be increased by: +/// - clicking the increment button +/// - pressing the plus, right or up key +/// - scrolling up the mouse wheel +/// - focusing the increment button and clicking space or select +/// - The value can be decreased by: +/// - clicking the decrement button +/// - pressing the minus, left or down key +/// - scrolling down the mouse wheel +/// - focusing the decrement button and clicking space or select +//////////////////////////////////////////////////////////////////////////////////////////////////// +class QSK_EXPORT QskSpinBox : public QskBoundedValueInput { Q_OBJECT - Q_PROPERTY(qreal value READ value NOTIFY valueChanged) + using Inherited = QskBoundedValueInput; - using Inherited = QskBoundedInput; -public: - QSK_SUBCONTROLS(Inc, Dec, IncText, DecText, TextPanel, Text, Layout) + public: + /// Focus indeces for the visual subcontrols + enum FocusIndeces : int + { + Decrement = 0, ///< the decrement buttons index + Textbox = 1, ///< the textbox' index + Increment = 2, ///< the increment button's index + None = 3 ///< index for when no subcontrol is focused (e.g. focus in/out ) + }; + Q_ENUM( FocusIndeces ) + + /// The currently focused subcontrol's index + Q_PROPERTY( FocusIndeces focusIndex READ focusIndex NOTIFY focusIndexChanged ) + QSK_SUBCONTROLS( IncrementPanel ) ///< Use this to style the increment button. + QSK_SUBCONTROLS( DecrementPanel ) ///< Use this to style the decrement button. + QSK_SUBCONTROLS( IncrementText ) ///< Use this to style the increment button's text. + QSK_SUBCONTROLS( DecrementText ) ///< Use this to style the decrement button's text. + QSK_SUBCONTROLS( TextPanel ) ///< Use this to style the text's panel. + QSK_SUBCONTROLS( Text ) ///< Use this to style the text (e.g. font role). + QSK_SUBCONTROLS( Layout ) ///< Use this to style the spinbox's controls layout. QSK_STATES( Pressed ) + /// @brief C-TOR + /// @param parent This object's parent explicit QskSpinBox( QQuickItem* parent = nullptr ); + + /// @brief D-TOR defaulted but required for std::unique_ptr ~QskSpinBox() override; - void increment( qreal offset ) override; - qreal value() const; + /// @brief Getter for property focusIndex. + /// @returns Returns the currently focused subcontrol's index. + /// @retval Return FocusIndeces::None if no subcontrol is currently focused. + FocusIndeces focusIndex() const; -Q_SIGNALS: - void valueChanged( qreal ); - void focusIndexChanged( int ); + Q_SIGNALS: + /// @brief Emitted when the property @c focusIndex changed. + void focusIndexChanged( int index ); -private: - void hoverEnterEvent( QHoverEvent* ) override; - void hoverLeaveEvent( QHoverEvent* ) override; - void hoverMoveEvent( QHoverEvent* ) override; + private: + void hoverEnterEvent( QHoverEvent* event ) override; + void hoverLeaveEvent( QHoverEvent* event ) override; + void hoverMoveEvent( QHoverEvent* event ) override; - void mouseReleaseEvent( QMouseEvent* ) override; - void mousePressEvent( QMouseEvent* ) override; + void mouseReleaseEvent( QMouseEvent* event ) override; + void mousePressEvent( QMouseEvent* event ) override; - void keyPressEvent( QKeyEvent* ) override; - void keyReleaseEvent( QKeyEvent* ) override; + void keyPressEvent( QKeyEvent* event ) override; + void keyReleaseEvent( QKeyEvent* event ) override; - void focusInEvent( QFocusEvent* ) override; + void focusInEvent( QFocusEvent* event ) override; QRectF focusIndicatorRect() const override; class PrivateData; - std::unique_ptr m_data; + std::unique_ptr< PrivateData > m_data; }; #endif diff --git a/src/controls/QskSpinBoxSkinlet.cpp b/src/controls/QskSpinBoxSkinlet.cpp index b0d9961f..45973a46 100644 --- a/src/controls/QskSpinBoxSkinlet.cpp +++ b/src/controls/QskSpinBoxSkinlet.cpp @@ -5,216 +5,295 @@ #include "QskSpinBoxSkinlet.h" #include "QskSpinBox.h" +#include +#include -#include - -const auto INC_TEXT = QStringLiteral("+"); -const auto DEC_TEXT = QStringLiteral("-"); - -enum SampleIndeces { Dec, Txt, Inc, Count }; - -QskSpinBoxSkinlet::QskSpinBoxSkinlet(QskSkin *) +namespace { - setNodeRoles({IncPanel, IncText, DecPanel, DecText, TextPanel, TextText}); -} - -int QskSpinBoxSkinlet::sampleCount(const QskSkinnable *, QskAspect::Subcontrol) const -{ - return Count; -} - -QRectF QskSpinBoxSkinlet::sampleRect(const QskSkinnable* const skinnable, const QRectF& rect, QskAspect::Subcontrol subControl, int index) const -{ - if(index == Dec || index == Inc || index == Txt) - { - return subControlRect(skinnable, rect, subControl); - } - - return Inherited::sampleRect( skinnable, rect, subControl, index ); -} - -QskAspect::States QskSpinBoxSkinlet::sampleStates(const QskSkinnable* const skinnable, QskAspect::Subcontrol subControl, int index) const -{ - auto states = Inherited::sampleStates( skinnable, subControl, index ); - - if ( subControl == QskSpinBox::Dec || subControl == QskSpinBox::Inc || subControl == QskSpinBox::TextPanel) - { - const auto* const spinbox = static_cast(skinnable); - const auto cursorPos = spinbox->effectiveSkinHint(QskSpinBox::Layout | QskAspect::Metric | QskAspect::Position).toPointF(); - const QPointF cursorPosAbs{qAbs(cursorPos.x()), qAbs(cursorPos.y())}; - const auto contain = !cursorPosAbs.isNull() && spinbox->subControlRect(subControl).contains(cursorPosAbs); - const auto pressed = contain && (cursorPos.x() < 0 || cursorPos.y() < 0); - const auto hovered = contain && !pressed; - states.setFlag(QskControl::Hovered, hovered); - states.setFlag(QskSpinBox::Pressed, pressed); - } - - return states; -} - -QSizeF QskSpinBoxSkinlet::sizeHint(const QskSkinnable* const skinnable, Qt::SizeHint sizeHint, const QSizeF& size) const -{ - using S = QskSpinBox; - const auto* const spinbox = static_cast(skinnable); - const auto layout = spinbox->alignmentHint(QskSpinBox::Layout); - const auto spacing = spinbox->spacingHint(QskSpinBox::Layout); - - const auto strutInc = spinbox->strutSizeHint(S::Inc); - const auto strutDec = spinbox->strutSizeHint(S::Dec); - const auto strutTxt = spinbox->strutSizeHint(S::TextPanel); - - if(sizeHint == Qt::MinimumSize || sizeHint == Qt::MaximumSize || Qt::PreferredSize) - { - if(layout == Qt::AlignTop || layout == Qt::AlignBottom || layout == Qt::AlignVCenter) + inline QPointF cursorPosSkinHint( const QskSpinBox& spinbox ) { - const auto w = qMax(strutDec.width(), qMax( strutTxt.width() , strutInc.width())); - const auto h = strutDec.height() + strutTxt.height() + strutInc.height(); - return {w,h + 2.0 * spacing}; + const auto aspect = QskSpinBox::Layout | QskAspect::Metric | QskAspect::Position; + return spinbox.effectiveSkinHint( aspect ).toPointF(); } - if(layout == Qt::AlignLeft || layout == Qt::AlignRight || layout == Qt::AlignHCenter) + + enum SampleIndeces { - const auto w = strutDec.width() + strutTxt.width() + strutInc.width(); - const auto h = qMax(strutDec.height(), qMax( strutTxt.height() , strutInc.height())); - return {w + 2.0 * spacing,h}; - } - if(layout == (Qt::AlignLeft | Qt::AlignVCenter) || layout == (Qt::AlignRight | Qt::AlignVCenter)) + Dec = 0, + Txt = 1, + Inc = 2, + Count + }; +} + +QskSpinBoxSkinlet::QskSpinBoxSkinlet( QskSkin* ) +{ + setNodeRoles( + { IncrementPanel, IncrementText, DecrementPanel, DecrementText, TextPanel, TextText } ); +} + +int QskSpinBoxSkinlet::sampleCount( const QskSkinnable*, QskAspect::Subcontrol ) const +{ + return 1; +} + +QRectF QskSpinBoxSkinlet::sampleRect( const QskSkinnable* const skinnable, const QRectF& rect, + QskAspect::Subcontrol subControl, int index ) const +{ + if ( index == Dec || index == Inc || index == Txt ) { - const auto w = strutTxt.width() + qMax(strutInc.width(), strutDec.width()); - const auto h = qMax(2.0 * qMax(strutInc.height(), strutDec.height()), strutTxt.height()); - return {w + spacing ,h + spacing}; + return subControlRect( skinnable, rect, subControl ); } - if(layout == (Qt::AlignTop | Qt::AlignHCenter) || layout == (Qt::AlignTop | Qt::AlignHCenter)) + + return Inherited::sampleRect( skinnable, rect, subControl, index ); +} + +QskAspect::States QskSpinBoxSkinlet::sampleStates( + const QskSkinnable* const skinnable, QskAspect::Subcontrol subControl, int index ) const +{ + using S = QskSpinBox; + auto states = Inherited::sampleStates( skinnable, subControl, index ); + + if ( subControl == S::DecrementPanel || subControl == S::IncrementPanel || + subControl == S::TextPanel ) { - const auto w = qMax(strutTxt.width() , strutInc.width() + strutDec.width()); - const auto h = strutTxt.height() + qMax(strutInc.height() , strutDec.height()); - return {w + spacing, h + spacing}; + const auto* const spinbox = static_cast< const S* >( skinnable ); + const auto cursorPos = cursorPosSkinHint( *spinbox ); + const QPointF cursorPosAbs{ qAbs( cursorPos.x() ), qAbs( cursorPos.y() ) }; + const auto focusIndex = spinbox->focusIndex(); + const auto subControlRect = spinbox->subControlRect( subControl ); + + const auto contain = !cursorPosAbs.isNull() && subControlRect.contains( cursorPosAbs ); + const auto pressed = contain && ( cursorPos.x() < 0 || cursorPos.y() < 0 ); + const auto hovered = contain && !pressed; + const auto focused = ( subControl == S::IncrementPanel && focusIndex == S::Increment ) || + ( subControl == S::DecrementPanel && focusIndex == S::Decrement ) || + ( subControl == S::TextPanel && focusIndex == S::Textbox ); + + states.setFlag( QskControl::Hovered, hovered ); + states.setFlag( QskSpinBox::Pressed, pressed ); + states.setFlag( QskControl::Focused, focused ); } - } - return Inherited::sizeHint(skinnable, sizeHint, size); + return states; } -QRectF QskSpinBoxSkinlet::subControlRect(const QskSkinnable* const skinnable, const QRectF& rect, QskAspect::Subcontrol subControl) const +QSizeF QskSpinBoxSkinlet::sizeHint( + const QskSkinnable* const skinnable, Qt::SizeHint sizeHint, const QSizeF& size ) const { - if(subControl == QskSpinBox::DecText) return subControlRect(skinnable, rect, QskSpinBox::Dec); - if(subControl == QskSpinBox::IncText) return subControlRect(skinnable, rect, QskSpinBox::Inc); - if(subControl == QskSpinBox::Text) return subControlRect(skinnable, rect, QskSpinBox::TextPanel); + using S = QskSpinBox; + const auto* const spinbox = static_cast< const S* >( skinnable ); + const auto layout = spinbox->alignmentHint( S::Layout ); + const auto spacing = spinbox->spacingHint( S::Layout ); - const auto* const spinbox = static_cast(skinnable); - const auto layout = spinbox->alignmentHint(QskSpinBox::Layout); - const auto spacing = spinbox->spacingHint(QskSpinBox::Layout); + const auto strutInc = spinbox->strutSizeHint( S::IncrementPanel ); + const auto strutDec = spinbox->strutSizeHint( S::DecrementPanel ); + const auto strutTxt = spinbox->strutSizeHint( S::TextPanel ); - using S = QskSpinBox; - - QRectF rects[Count] = - { - { {}, spinbox->strutSizeHint(S::Dec)}, - { {}, spinbox->strutSizeHint(S::TextPanel)}, - { {}, spinbox->strutSizeHint(S::Inc)}, - }; - - const auto center = rect.center(); - - // TODO center everything - - if(layout == Qt::AlignLeft) - { - rects[Txt].moveTopLeft({0.0 /************/, center.y() - rects[Txt].height() * 0.5}); - rects[Dec].moveTopLeft({rects[Txt].right() + spacing, center.y() - rects[Dec].height() * 0.5}); - rects[Inc].moveTopLeft({rects[Dec].right() + spacing, center.y() - rects[Inc].height() * 0.5}); - } - else if(layout == Qt::AlignRight) - { - rects[Dec].moveTopLeft({0.0 /************/, center.y() - rects[Dec].height() * 0.5}); - rects[Inc].moveTopLeft({rects[Dec].right() + spacing, center.y() - rects[Inc].height() * 0.5}); - rects[Txt].moveTopLeft({rects[Inc].right() + spacing, center.y() - rects[Txt].height() * 0.5}); - } - else if(layout == Qt::AlignTop) - { - rects[Txt].moveTopLeft({center.x() - rects[Txt].width() * 0.5, 0.0 }); - rects[Inc].moveTopLeft({center.x() - rects[Inc].width() * 0.5, rects[Txt].bottom() + spacing}); - rects[Dec].moveTopLeft({center.x() - rects[Dec].width() * 0.5, rects[Inc].bottom() + spacing}); - } - else if(layout == Qt::AlignBottom) - { - rects[Inc].moveTopLeft({center.x() - rects[Inc].width() * 0.5, 0.0}); - rects[Dec].moveTopLeft({center.x() - rects[Dec].width() * 0.5, rects[Inc].bottom() + spacing}); - rects[Txt].moveTopLeft({center.x() - rects[Txt].width() * 0.5, rects[Dec].bottom() + spacing}); - } - else if(layout == Qt::AlignHCenter) - { - rects[Dec].moveTopLeft({0.0 /************/, center.y() - rects[Dec].height() * 0.5}); - rects[Txt].moveTopLeft({rects[Dec].right() + spacing, center.y() - rects[Txt].height() * 0.5}); - rects[Inc].moveTopLeft({rects[Txt].right() + spacing, center.y() - rects[Inc].height() * 0.5}); - } - else if(layout == Qt::AlignVCenter) - { - rects[Inc].moveTopLeft({center.x() - rects[Inc].width() * 0.5, 0.0}); - rects[Txt].moveTopLeft({center.x() - rects[Txt].width() * 0.5, rects[Inc].bottom() + spacing}); - rects[Dec].moveTopLeft({center.x() - rects[Dec].width() * 0.5, rects[Txt].bottom() + spacing}); - } - else if(layout == (Qt::AlignLeft | Qt::AlignVCenter)) - { - rects[Txt].moveTopLeft({0.0 /************/, center.y() - rects[Txt].height() * 0.5 }); - rects[Inc].moveTopLeft({rects[Txt].right() + spacing, center.y() - spacing * 0.5 - rects[Inc].height()}); - rects[Dec].moveTopLeft({rects[Txt].right() + spacing, center.y() + spacing * 0.5}); - } - else if(layout == (Qt::AlignRight | Qt::AlignVCenter)) - { - const auto dx = qMax(rects[Inc].width(), rects[Dec].width()); - rects[Inc].moveTopLeft({dx - rects[Inc].width(), center.y() - spacing * 0.5 - rects[Inc].height()}); - rects[Dec].moveTopLeft({dx - rects[Dec].width(), center.y() + spacing * 0.5}); - rects[Txt].moveTopLeft({dx + spacing, center.y() - rects[Txt].height() * 0.5 }); - } - else if(layout == (Qt::AlignTop | Qt::AlignHCenter)) - { - rects[Txt].moveTopLeft({center.x() - rects[Txt].width() * 0.5, 0.0 }); - rects[Dec].moveTopLeft({rects[Txt].center().x() - spacing * 0.5 - rects[Dec].width(), rects[Txt].bottom() + spacing }); - rects[Inc].moveTopLeft({rects[Txt].center().x() + spacing * 0.5, rects[Txt].bottom() + spacing }); - } - else if(layout == (Qt::AlignBottom | Qt::AlignHCenter)) - { - rects[Txt].moveTopLeft({center.x() - rects[Txt].width() * 0.5, center.y() - rects[Txt].height() * 0.5}); - rects[Dec].moveTopLeft({center.x() - spacing * 0.5 - rects[Dec].width() , rects[Txt].top() - spacing - rects[Dec].height() }); - rects[Inc].moveTopLeft({center.x() + spacing * 0.5, rects[Txt].top() - spacing - rects[Inc].height() }); - } - - if(subControl == S::Dec) - { - return rects[Dec]; - } - if(subControl == S::TextPanel) - { - return rects[Txt]; - } - if(subControl == S::Inc) - { - return rects[Inc]; - } - - return Inherited::subControlRect(skinnable, rect, subControl); + if ( sizeHint == Qt::MinimumSize || sizeHint == Qt::MaximumSize || Qt::PreferredSize ) + { + if ( layout == Qt::AlignTop || layout == Qt::AlignBottom || layout == Qt::AlignVCenter ) + { + const auto w = qMax( strutDec.width(), qMax( strutTxt.width(), strutInc.width() ) ); + const auto h = strutDec.height() + strutTxt.height() + strutInc.height(); + return { w, h + 2.0 * spacing }; + } + if ( layout == Qt::AlignLeft || layout == Qt::AlignRight || layout == Qt::AlignHCenter ) + { + const auto w = strutDec.width() + strutTxt.width() + strutInc.width(); + const auto h = qMax( strutDec.height(), qMax( strutTxt.height(), strutInc.height() ) ); + return { w + 2.0 * spacing, h }; + } + if ( layout == ( Qt::AlignLeft | Qt::AlignVCenter ) || + layout == ( Qt::AlignRight | Qt::AlignVCenter ) ) + { + const auto w = strutTxt.width() + qMax( strutInc.width(), strutDec.width() ); + const auto h = + qMax( 2.0 * qMax( strutInc.height(), strutDec.height() ), strutTxt.height() ); + return { w + spacing, h + spacing }; + } + if ( layout == ( Qt::AlignTop | Qt::AlignHCenter ) || + layout == ( Qt::AlignTop | Qt::AlignHCenter ) ) + { + const auto w = qMax( strutTxt.width(), strutInc.width() + strutDec.width() ); + const auto h = strutTxt.height() + qMax( strutInc.height(), strutDec.height() ); + return { w + spacing, h + spacing }; + } + } + return Inherited::sizeHint( skinnable, sizeHint, size ); } -QSGNode* QskSpinBoxSkinlet::updateSubNode(const QskSkinnable* const skinnable, const quint8 nodeRole, QSGNode* const node) const +QRectF QskSpinBoxSkinlet::subControlRect( const QskSkinnable* const skinnable, const QRectF& rect, + QskAspect::Subcontrol subControl ) const { - if(nodeRole == IncPanel) { return updateSeriesNode( skinnable, QskSpinBox::Inc, node); } - if(nodeRole == DecPanel) { return updateSeriesNode( skinnable, QskSpinBox::Dec, node ); } - if(nodeRole == IncText) { return updateTextNode( skinnable, node, INC_TEXT, QskSpinBox::IncText); } - if(nodeRole == DecText) { return updateTextNode( skinnable, node, DEC_TEXT, QskSpinBox::DecText ); } - if(nodeRole == TextPanel) { return updateSeriesNode( skinnable, QskSpinBox::TextPanel, node ); } - if(nodeRole == TextText) { return updateTextNode( skinnable, node, QString::number(static_cast(skinnable)->value()), QskSpinBox::Text ); } - return Inherited::updateSubNode(skinnable, nodeRole, node); + using S = QskSpinBox; + + if ( subControl == S::DecrementText ) + { + return subControlRect( skinnable, rect, S::DecrementPanel ); + } + if ( subControl == S::IncrementText ) + { + return subControlRect( skinnable, rect, S::IncrementPanel ); + } + if ( subControl == S::Text ) + { + return subControlRect( skinnable, rect, S::TextPanel ); + } + + const auto* const spinbox = static_cast< const S* >( skinnable ); + const auto layout = spinbox->alignmentHint( S::Layout ); + const auto spacing = spinbox->spacingHint( S::Layout ); + + std::array< QRectF, Count > rects = { + QRectF{ QPointF{}, spinbox->strutSizeHint( S::DecrementPanel ) }, + QRectF{ QPointF{}, spinbox->strutSizeHint( S::TextPanel ) }, + QRectF{ QPointF{}, spinbox->strutSizeHint( S::IncrementPanel ) }, + }; + + const auto center = rect.center(); + + if ( layout == Qt::AlignLeft ) + { + rects[ Txt ].moveTopLeft( { 0.0, center.y() - rects[ Txt ].height() * 0.5 } ); + rects[ Dec ].moveTopLeft( + { rects[ Txt ].right() + spacing, center.y() - rects[ Dec ].height() * 0.5 } ); + rects[ Inc ].moveTopLeft( + { rects[ Dec ].right() + spacing, center.y() - rects[ Inc ].height() * 0.5 } ); + } + else if ( layout == Qt::AlignRight ) + { + rects[ Dec ].moveTopLeft( { 0.0, center.y() - rects[ Dec ].height() * 0.5 } ); + rects[ Inc ].moveTopLeft( + { rects[ Dec ].right() + spacing, center.y() - rects[ Inc ].height() * 0.5 } ); + rects[ Txt ].moveTopLeft( + { rects[ Inc ].right() + spacing, center.y() - rects[ Txt ].height() * 0.5 } ); + } + else if ( layout == Qt::AlignTop ) + { + rects[ Txt ].moveTopLeft( { center.x() - rects[ Txt ].width() * 0.5, 0.0 } ); + rects[ Inc ].moveTopLeft( + { center.x() - rects[ Inc ].width() * 0.5, rects[ Txt ].bottom() + spacing } ); + rects[ Dec ].moveTopLeft( + { center.x() - rects[ Dec ].width() * 0.5, rects[ Inc ].bottom() + spacing } ); + } + else if ( layout == Qt::AlignBottom ) + { + rects[ Inc ].moveTopLeft( { center.x() - rects[ Inc ].width() * 0.5, 0.0 } ); + rects[ Dec ].moveTopLeft( + { center.x() - rects[ Dec ].width() * 0.5, rects[ Inc ].bottom() + spacing } ); + rects[ Txt ].moveTopLeft( + { center.x() - rects[ Txt ].width() * 0.5, rects[ Dec ].bottom() + spacing } ); + } + else if ( layout == Qt::AlignHCenter ) + { + rects[ Dec ].moveTopLeft( { 0.0, center.y() - rects[ Dec ].height() * 0.5 } ); + rects[ Txt ].moveTopLeft( + { rects[ Dec ].right() + spacing, center.y() - rects[ Txt ].height() * 0.5 } ); + rects[ Inc ].moveTopLeft( + { rects[ Txt ].right() + spacing, center.y() - rects[ Inc ].height() * 0.5 } ); + } + else if ( layout == Qt::AlignVCenter ) + { + rects[ Inc ].moveTopLeft( { center.x() - rects[ Inc ].width() * 0.5, 0.0 } ); + rects[ Txt ].moveTopLeft( + { center.x() - rects[ Txt ].width() * 0.5, rects[ Inc ].bottom() + spacing } ); + rects[ Dec ].moveTopLeft( + { center.x() - rects[ Dec ].width() * 0.5, rects[ Txt ].bottom() + spacing } ); + } + else if ( layout == ( Qt::AlignLeft | Qt::AlignVCenter ) ) + { + rects[ Txt ].moveTopLeft( { 0.0, center.y() - rects[ Txt ].height() * 0.5 } ); + rects[ Inc ].moveTopLeft( { rects[ Txt ].right() + spacing, + center.y() - spacing * 0.5 - rects[ Inc ].height() } ); + rects[ Dec ].moveTopLeft( { rects[ Txt ].right() + spacing, center.y() + spacing * 0.5 } ); + } + else if ( layout == ( Qt::AlignRight | Qt::AlignVCenter ) ) + { + const auto dx = qMax( rects[ Inc ].width(), rects[ Dec ].width() ); + rects[ Inc ].moveTopLeft( + { dx - rects[ Inc ].width(), center.y() - spacing * 0.5 - rects[ Inc ].height() } ); + rects[ Dec ].moveTopLeft( { dx - rects[ Dec ].width(), center.y() + spacing * 0.5 } ); + rects[ Txt ].moveTopLeft( { dx + spacing, center.y() - rects[ Txt ].height() * 0.5 } ); + } + else if ( layout == ( Qt::AlignTop | Qt::AlignHCenter ) ) + { + rects[ Txt ].moveTopLeft( { center.x() - rects[ Txt ].width() * 0.5, 0.0 } ); + rects[ Dec ].moveTopLeft( + { rects[ Txt ].center().x() - spacing * 0.5 - rects[ Dec ].width(), + rects[ Txt ].bottom() + spacing } ); + rects[ Inc ].moveTopLeft( + { rects[ Txt ].center().x() + spacing * 0.5, rects[ Txt ].bottom() + spacing } ); + } + else if ( layout == ( Qt::AlignBottom | Qt::AlignHCenter ) ) + { + rects[ Txt ].moveTopLeft( + { center.x() - rects[ Txt ].width() * 0.5, center.y() - rects[ Txt ].height() * 0.5 } ); + rects[ Dec ].moveTopLeft( { center.x() - spacing * 0.5 - rects[ Dec ].width(), + rects[ Txt ].top() - spacing - rects[ Dec ].height() } ); + rects[ Inc ].moveTopLeft( + { center.x() + spacing * 0.5, rects[ Txt ].top() - spacing - rects[ Inc ].height() } ); + } + + if ( subControl == S::DecrementPanel ) + { + return rects[ Dec ]; + } + if ( subControl == S::TextPanel ) + { + return rects[ Txt ]; + } + if ( subControl == S::IncrementPanel ) + { + return rects[ Inc ]; + } + + return Inherited::subControlRect( skinnable, rect, subControl ); } -QSGNode *QskSpinBoxSkinlet::updateSampleNode(const QskSkinnable* const skinnable, QskAspect::Subcontrol subControl, const int index, QSGNode* const node) const +QSGNode* QskSpinBoxSkinlet::updateSubNode( + const QskSkinnable* const skinnable, const quint8 nodeRole, QSGNode* const node ) const { - const auto* const spinbox = static_cast(skinnable); - - if ( subControl == QskSpinBox::Dec || subControl == QskSpinBox::Inc || subControl == QskSpinBox::TextPanel ) - { - const auto rect = sampleRect(spinbox, spinbox->contentsRect(), subControl, index); - return updateBoxNode( skinnable, node, rect, subControl ); - } - - return Inherited::updateSampleNode( skinnable, subControl, index, node ); + using S = QskSpinBox; + if ( nodeRole == IncrementPanel ) + { + return updateSeriesNode( skinnable, S::IncrementPanel, node ); + } + if ( nodeRole == DecrementPanel ) + { + return updateSeriesNode( skinnable, S::DecrementPanel, node ); + } + if ( nodeRole == IncrementText ) + { + return updateTextNode( skinnable, node, QStringLiteral( "+" ), S::IncrementText ); + } + if ( nodeRole == DecrementText ) + { + return updateTextNode( skinnable, node, QStringLiteral( "-" ), S::DecrementText ); + } + if ( nodeRole == TextPanel ) + { + return updateSeriesNode( skinnable, S::TextPanel, node ); + } + if ( nodeRole == TextText ) + { + const auto* const spinbox = static_cast< const S* >( skinnable ); + return updateTextNode( skinnable, node, QString::number( spinbox->value() ), S::Text ); + } + return Inherited::updateSubNode( skinnable, nodeRole, node ); +} + +QSGNode* QskSpinBoxSkinlet::updateSampleNode( const QskSkinnable* const skinnable, + QskAspect::Subcontrol subControl, const int index, QSGNode* const node ) const +{ + using S = QskSpinBox; + const auto* const spinbox = static_cast< const S* >( skinnable ); + + if ( subControl == S::DecrementPanel || subControl == S::IncrementPanel || + subControl == S::TextPanel ) + { + const auto rect = sampleRect( spinbox, spinbox->contentsRect(), subControl, index ); + return updateBoxNode( skinnable, node, rect, subControl ); + } + + return Inherited::updateSampleNode( skinnable, subControl, index, node ); } diff --git a/src/controls/QskSpinBoxSkinlet.h b/src/controls/QskSpinBoxSkinlet.h index 7cbee6ec..6697c5de 100644 --- a/src/controls/QskSpinBoxSkinlet.h +++ b/src/controls/QskSpinBoxSkinlet.h @@ -8,41 +8,107 @@ #include +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// @brief +/// This skinlet's purpose is to draw a QskSpinBox instance. +/// @details +/// In order to manage individual subcontrol states this skinlet uses subcontrol sampling. Although +/// it is most usefull when dealing with dynamic or large numbers of subcontrols, it is a strategy +/// to index the subcontrol in order to have individual states instead of one collective state on +/// the skinnable object. +/// @note The placement and dimensions of all subcontrols depend on the following subctrontrol +/// aspects: +/// - QskSpinBox::Layout's alignment hint ( which affects the positions of all controls ) +/// - QskSpinBox::Layout's spacing hint +/// - QskSpinBox::IncrementPanel's strut size hint +/// - QskSpinBox::DecrementPanel's strut size hint +/// - QskSpinBox::TextPanel's strut size hint +//////////////////////////////////////////////////////////////////////////////////////////////////// class QSK_EXPORT QskSpinBoxSkinlet : public QskSkinlet { Q_GADGET using Inherited = QskSkinlet; public: - enum NodeRole - { - IncPanel, - IncText, - DecPanel, - DecText, - TextPanel, - TextText, - RoleCount - }; - + /// @brief C-TOR defining the correct node's role order (e.g. panel before text) Q_INVOKABLE QskSpinBoxSkinlet( QskSkin* = nullptr ); + /// @brief Roles for the subcontrols. + enum NodeRole + { + IncrementPanel, ///< Identifier for the increment button's panel. + IncrementText, ///< Identifier for the increment button's text. + DecrementPanel, ///< Identifier for the decrement button's panel. + DecrementText, ///< Identifier for the decrement button's text. + TextPanel, ///< Identifier for the text's panel. + TextText, ///< Identifier for the text's glyphs. + RoleCount ///< Number of all roles in this skinlet. + }; + protected: - int sampleCount( const QskSkinnable*, QskAspect::Subcontrol ) const override; + /// @brief Getter for the number of samples in this skinlet. + /// @param skinnable The skinnable object. + /// @param subControl The skinnable object's subcontrol. + /// @returns Returns the number of samples. + /// @retval Returns 1 since each subcontrol a sample; + int sampleCount( + const QskSkinnable* skinnable, QskAspect::Subcontrol subControl ) const override; - QRectF sampleRect( const QskSkinnable*, const QRectF&, - QskAspect::Subcontrol, int index ) const override; + /// @brief Getter for a subcontrol's sample rectangle. + /// @param skinnable The skinnable object. + /// @param rect The skinnable object's content rectangle. + /// @param subControl The skinnable object's subcontrol. + /// @param index The skinnable object's subcontrol sample index. + /// @returns Returns the subcontrol's rectangle within the @p skinnable's content rectangle. + QRectF sampleRect( const QskSkinnable* skinnable, const QRectF& rect, + QskAspect::Subcontrol subControl, int index ) const override; - QskAspect::States sampleStates( const QskSkinnable*, - QskAspect::Subcontrol, int index ) const override; + /// @brief Getter for a subcontrol's sample states. + /// @param skinnable The skinnable object. + /// @param subControl The skinnable object's subcontrol. + /// @param index The skinnable object's subcontrol sample index. + /// @return Returns the states of the subcontrol's sample at the given @p index. + /// @details Sets or unsets the @c pressed, @c hovered, @c focused bits in the returned states + /// object. + QskAspect::States sampleStates( + const QskSkinnable* skinnable, QskAspect::Subcontrol subControl, int index ) const override; - QSizeF sizeHint( const QskSkinnable*, Qt::SizeHint, const QSizeF& ) const override; - QRectF subControlRect( const QskSkinnable*, - const QRectF&, QskAspect::Subcontrol ) const override; + /// @brief Getter for the skinnable object's size hints. + /// @param skinnable The skinnable object. + /// @param sizeHint The size hint. + /// @param rect The skinnable object's available rectangle. + /// @details Calculates the minimum, maximum and preferred size of the skinnable. + QSizeF sizeHint( + const QskSkinnable* skinnable, Qt::SizeHint sizeHint, const QSizeF& rect ) const override; - QSGNode* updateSubNode( const QskSkinnable*, quint8 nodeRole, QSGNode* ) const override; - QSGNode* updateSampleNode( const QskSkinnable*, - QskAspect::Subcontrol, int index, QSGNode* node ) const override; + /// @brief Getter for the subcontrol's rectangle. + /// @param skinnable The skinnable object. + /// @param rect The skinnable object's content rectangle. + /// @param subControl The skinnable object's subcontrol. + /// @returns Returns the subcontrol's rectangle in the skinnable's content rectangle. + QRectF subControlRect( const QskSkinnable* skinnable, const QRectF& rect, + QskAspect::Subcontrol subControl ) const override; + + /// @brief Updates the scene graph @p node for the given @p role + /// @param skinnable The skinnable object. + /// @param role The node's role number ( see: QskSpinBoxSkinlet::NodeRole ). + /// @param node The scene graph node for the given @p role. + /// @returns Returns a new or updated scene graph node for the given @p role. + /// @details This functions updates the text nodes and mediates updates for sampled + /// subcontrols to QskSpinBoxSkinlet::updateSampleNode. + /// @see QskSpinBoxSkinlet::NodeRole + QSGNode* updateSubNode( + const QskSkinnable* skinnable, quint8 role, QSGNode* node ) const override; + + /// @brief Updates the scene graph @p node for the given @p subControl's sample @p index + /// @param skinnable The skinnable object. + /// @param subControl The skinnable object's subcontrol. + /// @param index The skinnable object's subcontrol sample index. + /// @param node The scene graph node for the @p subControl's sample @p index. + /// @returns Returns a new or updated scene graph node for the given @p subControl's sample @p + /// index. + QSGNode* updateSampleNode( const QskSkinnable* skinnable, QskAspect::Subcontrol subControl, + int index, QSGNode* node ) const override; }; #endif