Merge branch 'rick-vogel-qskspinbox'

This commit is contained in:
Uwe Rathmann 2023-02-19 11:15:57 +01:00
commit 3e608c1c09
6 changed files with 909 additions and 560 deletions

View File

@ -4,12 +4,14 @@
*****************************************************************************/ *****************************************************************************/
#include "SpinBoxPage.h" #include "SpinBoxPage.h"
#include <QskSpinBox.h>
#include <QskGridBox.h> #include <QskGridBox.h>
#include <QskTextLabel.h>
#include <QskLinearBox.h> #include <QskLinearBox.h>
#include <QskSlider.h>
#include <QskSpinBox.h>
#include <QskTextLabel.h>
SpinBoxPage::SpinBoxPage( QQuickItem* parent ) : Page( Qt::Horizontal, parent ) SpinBoxPage::SpinBoxPage( QQuickItem* parent )
: Page( Qt::Horizontal, parent )
{ {
setMargins( 10 ); setMargins( 10 );
setSpacing( 20 ); setSpacing( 20 );
@ -19,32 +21,85 @@ SpinBoxPage::SpinBoxPage( QQuickItem* parent ) : Page( Qt::Horizontal, parent )
void SpinBoxPage::populate() void SpinBoxPage::populate()
{ {
const QMap<Qt::Alignment, QString> layouts = const QMap< Qt::Alignment, QString > layouts = { { Qt::AlignLeft,
{ QStringLiteral( "Qt::AlignLeft" ) },
{ Qt::AlignLeft, QStringLiteral("Qt::AlignLeft") }, { Qt::AlignHCenter, QStringLiteral( "Qt::AlignHCenter" ) },
{ Qt::AlignHCenter, QStringLiteral("Qt::AlignHCenter") }, { Qt::AlignRight, QStringLiteral( "Qt::AlignRight" ) },
{ Qt::AlignRight, QStringLiteral("Qt::AlignRight") }, { Qt::AlignTop, QStringLiteral( "Qt::AlignTop" ) },
{ Qt::AlignTop, QStringLiteral("Qt::AlignTop") }, { Qt::AlignVCenter, QStringLiteral( "Qt::AlignVCenter" ) },
{ Qt::AlignVCenter, QStringLiteral("Qt::AlignVCenter") }, { Qt::AlignBottom, QStringLiteral( "Qt::AlignBottom" ) },
{ Qt::AlignBottom, QStringLiteral("Qt::AlignBottom") }, { Qt::AlignLeft | Qt::AlignVCenter, QStringLiteral( "Qt::AlignLeft | Qt::AlignVCenter" ) },
{ Qt::AlignLeft | Qt::AlignVCenter, QStringLiteral("Qt::AlignLeft | Qt::AlignVCenter") }, { Qt::AlignRight | Qt::AlignVCenter,
{ Qt::AlignRight | Qt::AlignVCenter, QStringLiteral("Qt::AlignRight | Qt::AlignVCenter") }, QStringLiteral( "Qt::AlignRight | Qt::AlignVCenter" ) },
{ Qt::AlignTop | Qt::AlignHCenter, QStringLiteral("Qt::AlignTop | Qt::AlignHCenter") }, { Qt::AlignTop | Qt::AlignHCenter, QStringLiteral( "Qt::AlignTop | Qt::AlignHCenter" ) },
{ Qt::AlignBottom | Qt::AlignHCenter, QStringLiteral("Qt::AlignBottom | Qt::AlignHCenter") } { Qt::AlignBottom | Qt::AlignHCenter,
}; QStringLiteral( "Qt::AlignBottom | Qt::AlignHCenter" ) } };
auto* const grid = new QskGridBox(this); auto* const grid = new QskGridBox( this );
constexpr int cols = 5; constexpr int cols = 5;
for(const auto& layout : layouts.keys())
QVector< QskSpinBox* > spinboxes;
for ( const auto& layout : layouts.keys() )
{ {
const auto x = grid->elementCount() % cols; const auto x = grid->elementCount() % cols;
const auto y = grid->elementCount() / cols; const auto y = grid->elementCount() / cols;
auto* const column = new QskLinearBox(Qt::Vertical, grid); 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 label = new QskTextLabel( layouts.value( layout ), column );
auto* const spinbox = new QskSpinBox( column ); auto* const spinbox = new QskSpinBox( column );
spinbox->setAlignmentHint(QskSpinBox::Layout, layout); spinbox->setAlignmentHint( QskSpinBox::Layout, layout );
grid->addItem(column, y, x); grid->addItem( column, y, x );
column->setStretchFactor(label, 1); column->setStretchFactor( label, 1 );
column->setStretchFactor(spinbox, 99); 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 );
} }

View File

@ -717,20 +717,19 @@ void Editor::setupSpinBox()
setSpacing(QskSpinBox::Layout, 4_dp); setSpacing(QskSpinBox::Layout, 4_dp);
setStrutSize(QskSpinBox::TextPanel | QskAspect::Size, {80_dp,40_dp}); setStrutSize(QskSpinBox::TextPanel | QskAspect::Size, {80_dp,40_dp});
setStrutSize(QskSpinBox::Inc | QskAspect::Size, {40_dp,40_dp}); setStrutSize(QskSpinBox::IncrementPanel | QskAspect::Size, {40_dp,40_dp});
setStrutSize(QskSpinBox::Dec | QskAspect::Size, {40_dp,40_dp}); setStrutSize(QskSpinBox::DecrementPanel | QskAspect::Size, {40_dp,40_dp});
setAlignment(QskSpinBox::Layout, Qt::AlignHCenter); setAlignment(QskSpinBox::Layout, Qt::AlignHCenter);
setAlignment(Q::Text, Qt::AlignCenter); 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); setBoxShape(state, 4_dp);
setBoxBorderColors(state, QColor("#79747E"));
setBoxBorderMetrics(state, 1_dp); 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, m_pal.primary );
setGradient( state | Q::Disabled, m_pal.onSurface12 ); setGradient( state | Q::Disabled, m_pal.onSurface12 );
@ -745,7 +744,7 @@ void Editor::setupSpinBox()
setShadowColor( state | Q::Hovered, m_pal.shadow ); 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, m_pal.onPrimary );
setColor( state | Q::Disabled, m_pal.onSurface38 ); setColor( state | Q::Disabled, m_pal.onSurface38 );

View File

@ -4,112 +4,196 @@
*****************************************************************************/ *****************************************************************************/
#include "QskSpinBox.h" #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 <QtMath>
#include <QGuiApplication> #include <QGuiApplication>
#include <QRegExpValidator>
#include <QStyleHints> #include <QStyleHints>
#include <QskBoxBorderColors.h>
#include <QskBoxBorderMetrics.h>
#include <QskBoxShapeMetrics.h>
#include <QskEvent.h>
#include <QskGridBox.h>
#include <QskIntervalF.h>
#include <QskLinearBox.h>
#include <QskSkinlet.h>
#include <QskTextInput.h>
#include <QtMath>
#include <array> #include <array>
QSK_SUBCONTROL(QskSpinBox, Inc) QSK_SUBCONTROL( QskSpinBox, IncrementPanel )
QSK_SUBCONTROL(QskSpinBox, Dec) QSK_SUBCONTROL( QskSpinBox, DecrementPanel )
QSK_SUBCONTROL(QskSpinBox, IncText) QSK_SUBCONTROL( QskSpinBox, IncrementText )
QSK_SUBCONTROL(QskSpinBox, DecText) QSK_SUBCONTROL( QskSpinBox, DecrementText )
QSK_SUBCONTROL(QskSpinBox, Text) QSK_SUBCONTROL( QskSpinBox, Text )
QSK_SUBCONTROL(QskSpinBox, TextPanel) QSK_SUBCONTROL( QskSpinBox, TextPanel )
QSK_SUBCONTROL(QskSpinBox, Layout) 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 class QskSpinBox::PrivateData
{ {
public: public:
explicit PrivateData( QskSpinBox* const parent )
enum FocusIndeces : int { Dec = 0, Text = 1, Inc = 2, None = 3 }; : q( parent )
explicit PrivateData(QskSpinBox* const parent) : q(parent)
{ {
} }
void setValue(qreal value)
{
value = qBound(q->minimum(), value, q->maximum());
if(!qFuzzyCompare(m_value, value))
{
m_value = value;
Q_EMIT q->valueChanged(m_value);
q->polish();
q->update();
}
}
qreal value() const { return m_value; }
FocusIndeces defaultFocusIndex() const FocusIndeces defaultFocusIndex() const
{ {
const auto layout = q->alignmentHint(QskSpinBox::Layout); const auto layout = q->alignmentHint( QskSpinBox::Layout );
if(layout == Qt::AlignLeft) return Text; if ( layout == Qt::AlignLeft )
if(layout == Qt::AlignRight) return Dec; {
if(layout == Qt::AlignHCenter) return Dec; return QskSpinBox::Textbox;
if(layout == Qt::AlignTop) return Text; }
if(layout == Qt::AlignBottom) return Inc; if ( layout == Qt::AlignRight )
if(layout == Qt::AlignVCenter) return Inc; {
if(layout == (Qt::AlignLeft | Qt::AlignVCenter)) return Text; return QskSpinBox::Decrement;
if(layout == (Qt::AlignRight | Qt::AlignVCenter)) return Inc; }
if(layout == (Qt::AlignTop | Qt::AlignHCenter)) return Text; if ( layout == Qt::AlignHCenter )
if(layout == (Qt::AlignBottom | Qt::AlignHCenter)) return Dec; {
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; return None;
} }
FocusIndeces nextFocusIndex() const FocusIndeces nextFocusIndex() const
{ {
const auto layout = q->alignmentHint(QskSpinBox::Layout); const auto layout = q->alignmentHint( QskSpinBox::Layout );
using namespace aliased_enum;
// [0 ][1 ][2 ][3 ] // [0][1][2][3] := index
// [Dec][Text][Inc][None] // [D][T][I][N] := control
using LUT = std::array<FocusIndeces,4>; using LUT = std::array< QskSpinBox::FocusIndeces, 4 >;
if(layout == Qt::AlignLeft) return LUT{Inc,Dec,None,Text}[m_focusIndex]; if ( layout == Qt::AlignLeft )
if(layout == Qt::AlignRight) return LUT{Inc,None,Text,Dec}[m_focusIndex]; {
if(layout == Qt::AlignHCenter) return LUT{Text,Inc,None,Dec}[m_focusIndex]; return LUT{ I, D, N, T }[ 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::AlignRight )
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]; return LUT{ I, N, T, D }[ 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::AlignHCenter )
if(layout == (Qt::AlignBottom | Qt::AlignHCenter)) return LUT{Inc,None,Text,Dec}[m_focusIndex]; {
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; return None;
} }
FocusIndeces previousFocusIndex() const FocusIndeces previousFocusIndex() const
{ {
const auto layout = q->alignmentHint(QskSpinBox::Layout); const auto layout = q->alignmentHint( QskSpinBox::Layout );
using namespace aliased_enum;
// [0 ][1 ][2 ][3 ] // [0][1][2][3] := index
// [Dec][Text][Inc][None] // [D][T][I][N] := control
using LUT = std::array<FocusIndeces,4>; using LUT = std::array< FocusIndeces, 4 >;
if(layout == Qt::AlignLeft) return LUT{None,Dec,Text,Inc}[m_focusIndex]; if ( layout == Qt::AlignLeft )
if(layout == Qt::AlignRight) return LUT{None,Inc,Dec,Text}[m_focusIndex]; {
if(layout == Qt::AlignHCenter) return LUT{None,Dec,Text,Inc}[m_focusIndex]; return LUT{ T, N, D, I }[ 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::AlignRight )
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]; return LUT{ N, I, D, T }[ 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::AlignHCenter )
if(layout == (Qt::AlignBottom | Qt::AlignHCenter)) return LUT{None,Inc,Dec,Text}[m_focusIndex]; {
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; return None;
} }
@ -122,123 +206,133 @@ public:
void focusNext() void focusNext()
{ {
const auto index = nextFocusIndex(); const auto index = nextFocusIndex();
setFocus(index); setFocus( index );
} }
void focusPrevious() void focusPrevious()
{ {
const auto index = previousFocusIndex(); const auto index = previousFocusIndex();
setFocus(index); setFocus( index );
} }
void focusDefault() void focusDefault()
{ {
setFocus(defaultFocusIndex()); const auto index = defaultFocusIndex();
setFocus( index );
} }
void setFocus(const FocusIndeces index) void setFocus( const FocusIndeces index )
{ {
Q_ASSERT(index == Dec || index == Text || index == Inc || index == None); using namespace aliased_enum;
if(index == Dec || index == Text || index == Inc || index == None) Q_ASSERT( index == D || index == T || index == I || index == N );
if ( index == D || index == T || index == I || index == N )
{ {
m_focusIndex = index; m_focusIndex = index;
Q_EMIT q->focusIndexChanged(m_focusIndex); // TODO register enum Q_EMIT q->focusIndexChanged( m_focusIndex );
q->update();
} }
} }
QRectF focusIndicatorRect() const QRectF focusIndicatorRect() const
{ {
switch(m_focusIndex) if ( m_focusIndex == QskSpinBox::Decrement )
{ {
case PrivateData::FocusIndeces::Dec: return q->subControlRect( QskSpinBox::DecrementPanel );
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 {};
} }
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) void saveMousePosition( const QPointF& pos )
{ {
q->setSkinHint(QskSpinBox::Layout | QskAspect::Metric | QskAspect::Position, pos ); q->setSkinHint( QskSpinBox::Layout | QskAspect::Metric | QskAspect::Position, pos );
} }
private: bool focusNow() const
qreal m_value{0.0f}; {
const auto focusOnClick = ( q->focusPolicy() & Qt::ClickFocus ) == Qt::ClickFocus;
const auto focusOnTouchRelease = QGuiApplication::styleHints()->setFocusOnTouchRelease();
return focusOnClick && !focusOnTouchRelease;
}
private:
QskSpinBox* const q; QskSpinBox* const q;
FocusIndeces m_focusIndex = FocusIndeces::None; FocusIndeces m_focusIndex = FocusIndeces::None;
}; };
using S = QskSpinBox; using S = QskSpinBox;
QskSpinBox::QskSpinBox(QQuickItem* const parent) QskSpinBox::QskSpinBox( QQuickItem* const parent )
: Inherited(parent) : Inherited( parent )
, m_data( new PrivateData( this ) ) , m_data( std::make_unique< PrivateData >( this ) )
{ {
setBoundaries(0.0,1.0); setBoundaries( 0.0, 1.0 );
setAcceptHoverEvents(true); setAcceptHoverEvents( true );
setAcceptedMouseButtons(Qt::LeftButton); setAcceptedMouseButtons( Qt::LeftButton );
setFocusPolicy( Qt::StrongFocus ); setFocusPolicy( Qt::StrongFocus );
connect( this, &S::focusIndexChanged, this, &S::focusIndicatorRectChanged ); 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(); });
} }
QskSpinBox::~QskSpinBox() = default; 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() )) if ( subControlRect( QskSpinBox::IncrementPanel ).contains( event->pos() ) )
{ {
increment(+stepSize()); increment( +stepSize() );
if( focus ) if ( focus )
{ {
m_data->setFocus(PrivateData::Inc); m_data->setFocus( Increment );
} }
return; return;
} }
if(subControlRect(QskSpinBox::Dec).contains( event->pos() )) if ( subControlRect( QskSpinBox::DecrementPanel ).contains( event->pos() ) )
{ {
increment(-stepSize()); increment( -stepSize() );
if( focus ) if ( focus )
{ {
m_data->setFocus(PrivateData::Dec); m_data->setFocus( Decrement );
} }
return; return;
} }
if(subControlRect(QskSpinBox::TextPanel).contains( event->pos() )) if ( subControlRect( QskSpinBox::TextPanel ).contains( event->pos() ) )
{ {
if( focus ) if ( focus )
{ {
m_data->setFocus(PrivateData::Text); m_data->setFocus( Textbox );
} }
return; return;
@ -247,26 +341,26 @@ void QskSpinBox::mouseReleaseEvent(QMouseEvent *event)
event->ignore(); event->ignore();
} }
void QskSpinBox::mousePressEvent(QMouseEvent *event) void QskSpinBox::mousePressEvent( QMouseEvent* const event )
{ {
m_data->saveMousePosition( -1 * qskMousePosition( event ) ); m_data->saveMousePosition( -1 * 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() )) if ( subControlRect( QskSpinBox::IncrementPanel ).contains( event->pos() ) )
{ {
if( focus ) if ( focus )
{ {
m_data->setFocus(PrivateData::Inc); m_data->setFocus( QskSpinBox::Increment );
} }
return; return;
} }
if(subControlRect(QskSpinBox::Dec).contains( event->pos() )) if ( subControlRect( QskSpinBox::DecrementPanel ).contains( event->pos() ) )
{ {
if( focus ) if ( focus )
{ {
m_data->setFocus(PrivateData::Dec); m_data->setFocus( QskSpinBox::Decrement );
} }
return; return;
} }
@ -274,46 +368,64 @@ void QskSpinBox::mousePressEvent(QMouseEvent *event)
event->ignore(); event->ignore();
} }
void QskSpinBox::keyPressEvent(QKeyEvent *event) void QskSpinBox::keyPressEvent( QKeyEvent* const event )
{ {
switch( event->key() ) switch ( event->key() )
{ {
case Qt::Key_Plus: case Qt::Key_Plus:
case Qt::Key_Up: case Qt::Key_Up:
case Qt::Key_Right: case Qt::Key_Right:
// TODO increment increment( +stepSize() );
break; return;
case Qt::Key_Minus: case Qt::Key_Minus:
case Qt::Key_Down: case Qt::Key_Down:
case Qt::Key_Left: case Qt::Key_Left:
// TODO decrement increment( -stepSize() );
break; return;
case Qt::Key_Select: case Qt::Key_Select:
case Qt::Key_Space: case Qt::Key_Space:
// TODO click currently focused -/+ if ( focusIndex() == Increment )
break;
default:
{ {
const int steps = qskFocusChainIncrement( event ); increment( +stepSize() );
if(steps != 0)
{
for(int i = 0; i < steps; ++i)
{
m_data->focusNext();
} }
for(int i = steps; i < 0; ++i) if ( focusIndex() == Decrement )
{
increment( -stepSize() );
}
return;
default:
break;
}
const int steps = qskFocusChainIncrement( event );
if ( steps < 0 )
{
for ( int i = 0; i < qAbs( steps ); ++i )
{ {
m_data->focusPrevious(); 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 ); Inherited::keyPressEvent( event );
} }
void QskSpinBox::keyReleaseEvent( QKeyEvent* event ) void QskSpinBox::keyReleaseEvent( QKeyEvent* const event )
{ {
if( event->key() == Qt::Key_Select || event->key() == Qt::Key_Space ) if ( event->key() == Qt::Key_Select || event->key() == Qt::Key_Space )
{ {
return; return;
} }
@ -321,40 +433,35 @@ void QskSpinBox::keyReleaseEvent( QKeyEvent* event )
Inherited::keyReleaseEvent( event ); Inherited::keyReleaseEvent( event );
} }
void QskSpinBox::focusInEvent(QFocusEvent *event) void QskSpinBox::focusInEvent( QFocusEvent* const event )
{ {
switch( event->reason() ) if ( event->reason() == Qt::TabFocusReason )
{ {
case Qt::TabFocusReason:
m_data->focusNext(); m_data->focusNext();
break; return;
}
case Qt::BacktabFocusReason: if ( event->reason() == Qt::BacktabFocusReason )
{
m_data->focusPrevious(); m_data->focusPrevious();
break; return;
}
default: if ( m_data->focusIndex() == QskSpinBox::None )
if(m_data->focusIndex() == PrivateData::None)
{ {
m_data->focusDefault(); m_data->focusDefault();
return; return;
} }
}
Inherited::focusInEvent( event ); Inherited::focusInEvent( event );
} }
QRectF QskSpinBox::focusIndicatorRect() const QRectF QskSpinBox::focusIndicatorRect() const
{ {
auto rect = m_data->focusIndicatorRect(); return m_data->focusIndicatorRect();
return rect;
} }
void QskSpinBox::increment(qreal offset) QskSpinBox::FocusIndeces QskSpinBox::focusIndex() const
{ {
m_data->setValue(m_data->value() + offset); return m_data->focusIndex();
}
qreal QskSpinBox::value() const
{
return m_data->value();
} }

View File

@ -6,44 +6,87 @@
#ifndef QSK_SPIN_BOX_H #ifndef QSK_SPIN_BOX_H
#define QSK_SPIN_BOX_H #define QSK_SPIN_BOX_H
#include <QskBoundedInput.h> #include <QskBoundedValueInput.h>
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_OBJECT
Q_PROPERTY(qreal value READ value NOTIFY valueChanged) using Inherited = QskBoundedValueInput;
using Inherited = QskBoundedInput; public:
public: /// Focus indeces for the visual subcontrols
QSK_SUBCONTROLS(Inc, Dec, IncText, DecText, TextPanel, Text, Layout) 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 ) QSK_STATES( Pressed )
/// @brief C-TOR
/// @param parent This object's parent
explicit QskSpinBox( QQuickItem* parent = nullptr ); explicit QskSpinBox( QQuickItem* parent = nullptr );
/// @brief D-TOR defaulted but required for std::unique_ptr
~QskSpinBox() override; ~QskSpinBox() override;
void increment( qreal offset ) override; /// @brief Getter for property focusIndex.
qreal value() const; /// @returns Returns the currently focused subcontrol's index.
/// @retval Return FocusIndeces::None if no subcontrol is currently focused.
FocusIndeces focusIndex() const;
Q_SIGNALS: Q_SIGNALS:
void valueChanged( qreal ); /// @brief Emitted when the property @c focusIndex changed.
void focusIndexChanged( int ); void focusIndexChanged( int index );
private: private:
void hoverEnterEvent( QHoverEvent* ) override; void hoverEnterEvent( QHoverEvent* event ) override;
void hoverLeaveEvent( QHoverEvent* ) override; void hoverLeaveEvent( QHoverEvent* event ) override;
void hoverMoveEvent( QHoverEvent* ) override; void hoverMoveEvent( QHoverEvent* event ) override;
void mouseReleaseEvent( QMouseEvent* ) override; void mouseReleaseEvent( QMouseEvent* event ) override;
void mousePressEvent( QMouseEvent* ) override; void mousePressEvent( QMouseEvent* event ) override;
void keyPressEvent( QKeyEvent* ) override; void keyPressEvent( QKeyEvent* event ) override;
void keyReleaseEvent( QKeyEvent* ) override; void keyReleaseEvent( QKeyEvent* event ) override;
void focusInEvent( QFocusEvent* ) override; void focusInEvent( QFocusEvent* event ) override;
QRectF focusIndicatorRect() const override; QRectF focusIndicatorRect() const override;
class PrivateData; class PrivateData;
std::unique_ptr<PrivateData> m_data; std::unique_ptr< PrivateData > m_data;
}; };
#endif #endif

View File

@ -5,214 +5,293 @@
#include "QskSpinBoxSkinlet.h" #include "QskSpinBoxSkinlet.h"
#include "QskSpinBox.h" #include "QskSpinBox.h"
#include <QFontMetrics>
#include <array>
#include <qfontmetrics.h> namespace
const auto INC_TEXT = QStringLiteral("+");
const auto DEC_TEXT = QStringLiteral("-");
enum SampleIndeces { Dec, Txt, Inc, Count };
QskSpinBoxSkinlet::QskSpinBoxSkinlet(QskSkin *)
{ {
setNodeRoles({IncPanel, IncText, DecPanel, DecText, TextPanel, TextText}); inline QPointF cursorPosSkinHint( const QskSpinBox& spinbox )
}
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); const auto aspect = QskSpinBox::Layout | QskAspect::Metric | QskAspect::Position;
return spinbox.effectiveSkinHint( aspect ).toPointF();
}
enum SampleIndeces
{
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 )
{
return subControlRect( skinnable, rect, subControl );
} }
return Inherited::sampleRect( skinnable, rect, subControl, index ); return Inherited::sampleRect( skinnable, rect, subControl, index );
} }
QskAspect::States QskSpinBoxSkinlet::sampleStates(const QskSkinnable* const skinnable, QskAspect::Subcontrol subControl, int index) const QskAspect::States QskSpinBoxSkinlet::sampleStates(
const QskSkinnable* const skinnable, QskAspect::Subcontrol subControl, int index ) const
{ {
using S = QskSpinBox;
auto states = Inherited::sampleStates( skinnable, subControl, index ); auto states = Inherited::sampleStates( skinnable, subControl, index );
if ( subControl == QskSpinBox::Dec || subControl == QskSpinBox::Inc || subControl == QskSpinBox::TextPanel) if ( subControl == S::DecrementPanel || subControl == S::IncrementPanel ||
subControl == S::TextPanel )
{ {
const auto* const spinbox = static_cast<const QskSpinBox*>(skinnable); const auto* const spinbox = static_cast< const S* >( skinnable );
const auto cursorPos = spinbox->effectiveSkinHint(QskSpinBox::Layout | QskAspect::Metric | QskAspect::Position).toPointF(); const auto cursorPos = cursorPosSkinHint( *spinbox );
const QPointF cursorPosAbs{qAbs(cursorPos.x()), qAbs(cursorPos.y())}; const QPointF cursorPosAbs{ qAbs( cursorPos.x() ), qAbs( cursorPos.y() ) };
const auto contain = !cursorPosAbs.isNull() && spinbox->subControlRect(subControl).contains(cursorPosAbs); const auto focusIndex = spinbox->focusIndex();
const auto pressed = contain && (cursorPos.x() < 0 || cursorPos.y() < 0); 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 hovered = contain && !pressed;
states.setFlag(QskControl::Hovered, hovered); const auto focused = ( subControl == S::IncrementPanel && focusIndex == S::Increment ) ||
states.setFlag(QskSpinBox::Pressed, pressed); ( 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 states; return states;
} }
QSizeF QskSpinBoxSkinlet::sizeHint(const QskSkinnable* const skinnable, Qt::SizeHint sizeHint, const QSizeF& size) const QSizeF QskSpinBoxSkinlet::sizeHint(
const QskSkinnable* const skinnable, Qt::SizeHint sizeHint, const QSizeF& size ) const
{ {
using S = QskSpinBox; using S = QskSpinBox;
const auto* const spinbox = static_cast<const S*>(skinnable); const auto* const spinbox = static_cast< const S* >( skinnable );
const auto layout = spinbox->alignmentHint(QskSpinBox::Layout); const auto layout = spinbox->alignmentHint( S::Layout );
const auto spacing = spinbox->spacingHint(QskSpinBox::Layout); const auto spacing = spinbox->spacingHint( S::Layout );
const auto strutInc = spinbox->strutSizeHint(S::Inc); const auto strutInc = spinbox->strutSizeHint( S::IncrementPanel );
const auto strutDec = spinbox->strutSizeHint(S::Dec); const auto strutDec = spinbox->strutSizeHint( S::DecrementPanel );
const auto strutTxt = spinbox->strutSizeHint(S::TextPanel); const auto strutTxt = spinbox->strutSizeHint( S::TextPanel );
if(sizeHint == Qt::MinimumSize || sizeHint == Qt::MaximumSize || Qt::PreferredSize) if ( sizeHint == Qt::MinimumSize || sizeHint == Qt::MaximumSize || Qt::PreferredSize )
{ {
if(layout == Qt::AlignTop || layout == Qt::AlignBottom || layout == Qt::AlignVCenter) if ( layout == Qt::AlignTop || layout == Qt::AlignBottom || layout == Qt::AlignVCenter )
{ {
const auto w = qMax(strutDec.width(), qMax( strutTxt.width() , strutInc.width())); const auto w = qMax( strutDec.width(), qMax( strutTxt.width(), strutInc.width() ) );
const auto h = strutDec.height() + strutTxt.height() + strutInc.height(); const auto h = strutDec.height() + strutTxt.height() + strutInc.height();
return {w,h + 2.0 * spacing}; return { w, h + 2.0 * spacing };
} }
if(layout == Qt::AlignLeft || layout == Qt::AlignRight || layout == Qt::AlignHCenter) if ( layout == Qt::AlignLeft || layout == Qt::AlignRight || layout == Qt::AlignHCenter )
{ {
const auto w = strutDec.width() + strutTxt.width() + strutInc.width(); const auto w = strutDec.width() + strutTxt.width() + strutInc.width();
const auto h = qMax(strutDec.height(), qMax( strutTxt.height() , strutInc.height())); const auto h = qMax( strutDec.height(), qMax( strutTxt.height(), strutInc.height() ) );
return {w + 2.0 * spacing,h}; return { w + 2.0 * spacing, h };
} }
if(layout == (Qt::AlignLeft | Qt::AlignVCenter) || layout == (Qt::AlignRight | Qt::AlignVCenter)) if ( layout == ( Qt::AlignLeft | Qt::AlignVCenter ) ||
layout == ( Qt::AlignRight | Qt::AlignVCenter ) )
{ {
const auto w = strutTxt.width() + qMax(strutInc.width(), strutDec.width()); const auto w = strutTxt.width() + qMax( strutInc.width(), strutDec.width() );
const auto h = qMax(2.0 * qMax(strutInc.height(), strutDec.height()), strutTxt.height()); const auto h =
return {w + spacing ,h + spacing}; 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)) if ( layout == ( Qt::AlignTop | Qt::AlignHCenter ) ||
layout == ( Qt::AlignTop | Qt::AlignHCenter ) )
{ {
const auto w = qMax(strutTxt.width() , strutInc.width() + strutDec.width()); const auto w = qMax( strutTxt.width(), strutInc.width() + strutDec.width() );
const auto h = strutTxt.height() + qMax(strutInc.height() , strutDec.height()); const auto h = strutTxt.height() + qMax( strutInc.height(), strutDec.height() );
return {w + spacing, h + spacing}; return { w + spacing, h + spacing };
} }
} }
return Inherited::sizeHint(skinnable, sizeHint, size); return Inherited::sizeHint( skinnable, sizeHint, size );
} }
QRectF QskSpinBoxSkinlet::subControlRect(const QskSkinnable* const skinnable, const QRectF& rect, QskAspect::Subcontrol subControl) const QRectF QskSpinBoxSkinlet::subControlRect( const QskSkinnable* const skinnable, const QRectF& rect,
QskAspect::Subcontrol subControl ) 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);
const auto* const spinbox = static_cast<const QskSpinBox*>(skinnable);
const auto layout = spinbox->alignmentHint(QskSpinBox::Layout);
const auto spacing = spinbox->spacingHint(QskSpinBox::Layout);
using S = QskSpinBox; using S = QskSpinBox;
QRectF rects[Count] = if ( subControl == S::DecrementText )
{ {
{ {}, spinbox->strutSizeHint(S::Dec)}, return subControlRect( skinnable, rect, S::DecrementPanel );
{ {}, spinbox->strutSizeHint(S::TextPanel)}, }
{ {}, spinbox->strutSizeHint(S::Inc)}, 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(); const auto center = rect.center();
// TODO center everything if ( layout == Qt::AlignLeft )
if(layout == Qt::AlignLeft)
{ {
rects[Txt].moveTopLeft({0.0 /************/, center.y() - rects[Txt].height() * 0.5}); 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[ Dec ].moveTopLeft(
rects[Inc].moveTopLeft({rects[Dec].right() + spacing, center.y() - rects[Inc].height() * 0.5}); { 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) else if ( layout == Qt::AlignRight )
{ {
rects[Dec].moveTopLeft({0.0 /************/, center.y() - rects[Dec].height() * 0.5}); 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[ Inc ].moveTopLeft(
rects[Txt].moveTopLeft({rects[Inc].right() + spacing, center.y() - rects[Txt].height() * 0.5}); { 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) else if ( layout == Qt::AlignTop )
{ {
rects[Txt].moveTopLeft({center.x() - rects[Txt].width() * 0.5, 0.0 }); 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[ Inc ].moveTopLeft(
rects[Dec].moveTopLeft({center.x() - rects[Dec].width() * 0.5, rects[Inc].bottom() + spacing}); { 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) else if ( layout == Qt::AlignBottom )
{ {
rects[Inc].moveTopLeft({center.x() - rects[Inc].width() * 0.5, 0.0}); 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[ Dec ].moveTopLeft(
rects[Txt].moveTopLeft({center.x() - rects[Txt].width() * 0.5, rects[Dec].bottom() + spacing}); { 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) else if ( layout == Qt::AlignHCenter )
{ {
rects[Dec].moveTopLeft({0.0 /************/, center.y() - rects[Dec].height() * 0.5}); 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[ Txt ].moveTopLeft(
rects[Inc].moveTopLeft({rects[Txt].right() + spacing, center.y() - rects[Inc].height() * 0.5}); { 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) else if ( layout == Qt::AlignVCenter )
{ {
rects[Inc].moveTopLeft({center.x() - rects[Inc].width() * 0.5, 0.0}); 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[ Txt ].moveTopLeft(
rects[Dec].moveTopLeft({center.x() - rects[Dec].width() * 0.5, rects[Txt].bottom() + spacing}); { 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)) else if ( layout == ( Qt::AlignLeft | Qt::AlignVCenter ) )
{ {
rects[Txt].moveTopLeft({0.0 /************/, center.y() - rects[Txt].height() * 0.5 }); 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[ Inc ].moveTopLeft( { rects[ Txt ].right() + spacing,
rects[Dec].moveTopLeft({rects[Txt].right() + spacing, center.y() + spacing * 0.5}); 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)) else if ( layout == ( Qt::AlignRight | Qt::AlignVCenter ) )
{ {
const auto dx = qMax(rects[Inc].width(), rects[Dec].width()); 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[ Inc ].moveTopLeft(
rects[Dec].moveTopLeft({dx - rects[Dec].width(), center.y() + spacing * 0.5}); { dx - rects[ Inc ].width(), center.y() - spacing * 0.5 - rects[ Inc ].height() } );
rects[Txt].moveTopLeft({dx + spacing, center.y() - rects[Txt].height() * 0.5 }); 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)) else if ( layout == ( Qt::AlignTop | Qt::AlignHCenter ) )
{ {
rects[Txt].moveTopLeft({center.x() - rects[Txt].width() * 0.5, 0.0 }); 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[ Dec ].moveTopLeft(
rects[Inc].moveTopLeft({rects[Txt].center().x() + spacing * 0.5, rects[Txt].bottom() + spacing }); { 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)) 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[ Txt ].moveTopLeft(
rects[Dec].moveTopLeft({center.x() - spacing * 0.5 - rects[Dec].width() , rects[Txt].top() - spacing - rects[Dec].height() }); { center.x() - rects[ Txt ].width() * 0.5, center.y() - rects[ Txt ].height() * 0.5 } );
rects[Inc].moveTopLeft({center.x() + spacing * 0.5, rects[Txt].top() - spacing - rects[Inc].height() }); 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) if ( subControl == S::DecrementPanel )
{ {
return rects[Dec]; return rects[ Dec ];
} }
if(subControl == S::TextPanel) if ( subControl == S::TextPanel )
{ {
return rects[Txt]; return rects[ Txt ];
} }
if(subControl == S::Inc) if ( subControl == S::IncrementPanel )
{ {
return rects[Inc]; return rects[ Inc ];
} }
return Inherited::subControlRect(skinnable, rect, subControl); return Inherited::subControlRect( skinnable, rect, subControl );
} }
QSGNode* QskSpinBoxSkinlet::updateSubNode(const QskSkinnable* const skinnable, const quint8 nodeRole, QSGNode* const node) const QSGNode* QskSpinBoxSkinlet::updateSubNode(
const QskSkinnable* const skinnable, const quint8 nodeRole, QSGNode* const node ) const
{ {
if(nodeRole == IncPanel) { return updateSeriesNode( skinnable, QskSpinBox::Inc, node); } using S = QskSpinBox;
if(nodeRole == DecPanel) { return updateSeriesNode( skinnable, QskSpinBox::Dec, node ); } if ( nodeRole == IncrementPanel )
if(nodeRole == IncText) { return updateTextNode( skinnable, node, INC_TEXT, QskSpinBox::IncText); } {
if(nodeRole == DecText) { return updateTextNode( skinnable, node, DEC_TEXT, QskSpinBox::DecText ); } return updateSeriesNode( skinnable, S::IncrementPanel, node );
if(nodeRole == TextPanel) { return updateSeriesNode( skinnable, QskSpinBox::TextPanel, node ); } }
if(nodeRole == TextText) { return updateTextNode( skinnable, node, QString::number(static_cast<const QskSpinBox*>(skinnable)->value()), QskSpinBox::Text ); } if ( nodeRole == DecrementPanel )
return Inherited::updateSubNode(skinnable, nodeRole, node); {
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 QSGNode* QskSpinBoxSkinlet::updateSampleNode( const QskSkinnable* const skinnable,
QskAspect::Subcontrol subControl, const int index, QSGNode* const node ) const
{ {
const auto* const spinbox = static_cast<const QskSpinBox*>(skinnable); using S = QskSpinBox;
const auto* const spinbox = static_cast< const S* >( skinnable );
if ( subControl == QskSpinBox::Dec || subControl == QskSpinBox::Inc || subControl == QskSpinBox::TextPanel ) if ( subControl == S::DecrementPanel || subControl == S::IncrementPanel ||
subControl == S::TextPanel )
{ {
const auto rect = sampleRect(spinbox, spinbox->contentsRect(), subControl, index); const auto rect = sampleRect( spinbox, spinbox->contentsRect(), subControl, index );
return updateBoxNode( skinnable, node, rect, subControl ); return updateBoxNode( skinnable, node, rect, subControl );
} }

View File

@ -8,41 +8,107 @@
#include <QskSkinlet.h> #include <QskSkinlet.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
/// @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 class QSK_EXPORT QskSpinBoxSkinlet : public QskSkinlet
{ {
Q_GADGET Q_GADGET
using Inherited = QskSkinlet; using Inherited = QskSkinlet;
public: public:
enum NodeRole /// @brief C-TOR defining the correct node's role order (e.g. panel before text)
{
IncPanel,
IncText,
DecPanel,
DecText,
TextPanel,
TextText,
RoleCount
};
Q_INVOKABLE QskSpinBoxSkinlet( QskSkin* = nullptr ); 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: 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&, /// @brief Getter for a subcontrol's sample rectangle.
QskAspect::Subcontrol, int index ) const override; /// @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*, /// @brief Getter for a subcontrol's sample states.
QskAspect::Subcontrol, int index ) const override; /// @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; /// @brief Getter for the skinnable object's size hints.
QRectF subControlRect( const QskSkinnable*, /// @param skinnable The skinnable object.
const QRectF&, QskAspect::Subcontrol ) const override; /// @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; /// @brief Getter for the subcontrol's rectangle.
QSGNode* updateSampleNode( const QskSkinnable*, /// @param skinnable The skinnable object.
QskAspect::Subcontrol, int index, QSGNode* node ) const override; /// @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 #endif