From ac8a3dea9bca002a4ecfa181081947eacaaebea2 Mon Sep 17 00:00:00 2001 From: "Vogel, Rick" Date: Fri, 17 Feb 2023 12:01:56 +0100 Subject: [PATCH 01/11] initial commit of QskSpinBox --- examples/gallery/gallery.pro | 6 + examples/gallery/main.cpp | 4 + examples/gallery/spinbox/SpinBoxPage.cpp | 50 ++++ examples/gallery/spinbox/SpinBoxPage.h | 17 ++ skins/material3/QskMaterial3Skin.cpp | 73 +++++ src/controls/QskSkin.cpp | 4 + src/controls/QskSpinBox.cpp | 353 +++++++++++++++++++++++ src/controls/QskSpinBox.h | 48 +++ src/controls/QskSpinBoxSkinlet.cpp | 221 ++++++++++++++ src/controls/QskSpinBoxSkinlet.h | 28 ++ src/src.pro | 4 + 11 files changed, 808 insertions(+) create mode 100644 examples/gallery/spinbox/SpinBoxPage.cpp create mode 100644 examples/gallery/spinbox/SpinBoxPage.h create mode 100644 src/controls/QskSpinBox.cpp create mode 100644 src/controls/QskSpinBox.h create mode 100644 src/controls/QskSpinBoxSkinlet.cpp create mode 100644 src/controls/QskSpinBoxSkinlet.h diff --git a/examples/gallery/gallery.pro b/examples/gallery/gallery.pro index cae45a37..0d5c9042 100644 --- a/examples/gallery/gallery.pro +++ b/examples/gallery/gallery.pro @@ -42,6 +42,12 @@ HEADERS += \ SOURCES += \ dialog/DialogPage.cpp \ +HEADERS += \ + spinbox/SpinBoxPage.h + +SOURCES += \ + spinbox/SpinBoxPage.cpp + HEADERS += \ Page.h diff --git a/examples/gallery/main.cpp b/examples/gallery/main.cpp index 0156bd06..9bc35a0a 100644 --- a/examples/gallery/main.cpp +++ b/examples/gallery/main.cpp @@ -10,6 +10,7 @@ #include "textinput/TextInputPage.h" #include "selector/SelectorPage.h" #include "dialog/DialogPage.h" +#include "spinbox/SpinBoxPage.h" #include #include @@ -199,6 +200,9 @@ namespace tabView->addTab( "Text\nInputs", new TextInputPage() ); tabView->addTab( "Selectors", new SelectorPage() ); tabView->addTab( "Dialogs", new DialogPage() ); + tabView->addTab( "SpinBoxes", new SpinBoxPage() ); + + tabView->setCurrentIndex(tabView->count() - 1); connect( header, &Header::enabledToggled, tabView, &TabView::setTabsEnabled ); diff --git a/examples/gallery/spinbox/SpinBoxPage.cpp b/examples/gallery/spinbox/SpinBoxPage.cpp new file mode 100644 index 00000000..ae559c6c --- /dev/null +++ b/examples/gallery/spinbox/SpinBoxPage.cpp @@ -0,0 +1,50 @@ +/****************************************************************************** + * Copyright (C) 2023 Edelhirsch Software GmbH + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#include "SpinBoxPage.h" +#include +#include +#include +#include + +SpinBoxPage::SpinBoxPage( QQuickItem* parent ) : Page( Qt::Horizontal, parent ) +{ + setMargins( 10 ); + setSpacing( 20 ); + + populate(); +} + +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") } + }; + + 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); + } +} diff --git a/examples/gallery/spinbox/SpinBoxPage.h b/examples/gallery/spinbox/SpinBoxPage.h new file mode 100644 index 00000000..2597e341 --- /dev/null +++ b/examples/gallery/spinbox/SpinBoxPage.h @@ -0,0 +1,17 @@ +/****************************************************************************** + * Copyright (C) 2023 Edelhirsch Software GmbH + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#pragma once + +#include "Page.h" + +class SpinBoxPage : public Page +{ + public: + SpinBoxPage( QQuickItem* = nullptr ); + + private: + void populate(); +}; diff --git a/skins/material3/QskMaterial3Skin.cpp b/skins/material3/QskMaterial3Skin.cpp index ffde2ef5..35ef6035 100644 --- a/skins/material3/QskMaterial3Skin.cpp +++ b/skins/material3/QskMaterial3Skin.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -144,6 +145,7 @@ namespace void setupSeparator(); void setupSubWindow(); void setupSlider(); + void setupSpinBox(); void setupSwitchButton(); void setupTabButton(); void setupTabBar(); @@ -201,6 +203,7 @@ void Editor::setup() setupSegmentedBar(); setupSeparator(); setupSlider(); + setupSpinBox(); setupSubWindow(); setupSwitchButton(); setupTabButton(); @@ -672,6 +675,76 @@ void Editor::setupSlider() setAnimation( Q::Handle | A::Metric | A::Position | Q::Pressed, 0 ); } +void Editor::setupSpinBox() +{ + using Q = QskSpinBox; + + 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}); + + setAlignment(QskSpinBox::Layout, Qt::AlignHCenter); + setAlignment(Q::Text, Qt::AlignCenter); + + for(const auto& state : {QskSpinBox::Dec, QskSpinBox::Inc, QskSpinBox::TextPanel}) + { + setBoxShape(state, 4_dp); + setBoxBorderColors(state, QColor("#79747E")); + setBoxBorderMetrics(state, 1_dp); + } + + for(const auto& state : {QskSpinBox::Dec, QskSpinBox::Inc}) + { + setGradient( state, m_pal.primary ); + setGradient( state | Q::Disabled, m_pal.onSurface12 ); + + const auto focusColor = flattenedColor( m_pal.onPrimary, m_pal.primary, 0.12 ); + setGradient( state | Q::Focused, focusColor ); + setGradient( state | Q::Pressed, focusColor ); + + const auto hoverColor = flattenedColor( m_pal.onPrimary, m_pal.primary, 0.08 ); + setGradient( state | Q::Hovered, hoverColor ); + setShadowMetrics( state | Q::Hovered, m_pal.elevationLight1 ); + setShadowColor( state | Q::Hovered, m_pal.shadow ); + } + + for(const auto& state : {QskSpinBox::DecText, QskSpinBox::IncText}) + { + setColor( state, m_pal.onPrimary ); + setColor( state | Q::Disabled, m_pal.onSurface38 ); + setAlignment(state, Qt::AlignCenter); + setFontRole( state, QskMaterial3Skin::M3LabelLarge ); + } + + setColor( Q::Text, m_pal.onBackground ); + setColor( Q::Text | Q::Disabled, m_pal.onSurface38 ); + + setPadding( Q::TextPanel, 5_dp ); + setBoxShape( Q::TextPanel, 4_dp, 4_dp, 0, 0 ); + setBoxBorderMetrics( Q::TextPanel, 0, 0, 0, 1_dp ); + setBoxBorderColors( Q::TextPanel, m_pal.onSurface ); + + setBoxBorderMetrics( Q::TextPanel | Q::Focused, 0, 0, 0, 2_dp ); + setBoxBorderColors( Q::TextPanel | Q::Focused, m_pal.primary ); + +// setBoxBorderMetrics( Q::TextPanel | Q::Editing, 0, 0, 0, 2_dp ); +// setBoxBorderColors( Q::TextPanel | Q::Editing, m_pal.primary ); + + setBoxBorderMetrics( Q::TextPanel | Q::Hovered, 0, 0, 0, 1_dp ); + setBoxBorderColors( Q::TextPanel | Q::Hovered, m_pal.onSurface ); + + setGradient( Q::TextPanel, m_pal.surfaceVariant ); + + const auto c1 = QskRgb::toTransparentF( m_pal.onSurface, 0.04 ); + setGradient( Q::TextPanel | Q::Disabled, c1 ); + setBoxBorderMetrics( Q::TextPanel | Q::Disabled, 0, 0, 0, 1_dp ); + + setColor( Q::TextPanel | Q::Disabled, m_pal.onSurface38 ); + setBoxBorderColors( Q::TextPanel | Q::Disabled, m_pal.onSurface38 ); +} + void Editor::setupSwitchButton() { using A = QskAspect; diff --git a/src/controls/QskSkin.cpp b/src/controls/QskSkin.cpp index b97308e3..ee56230f 100644 --- a/src/controls/QskSkin.cpp +++ b/src/controls/QskSkin.cpp @@ -68,6 +68,9 @@ QSK_QT_PRIVATE_END #include "QskSlider.h" #include "QskSliderSkinlet.h" +#include "QskSpinBox.h" +#include "QskSpinBoxSkinlet.h" + #include "QskSubWindow.h" #include "QskSubWindowSkinlet.h" @@ -161,6 +164,7 @@ QskSkin::QskSkin( QObject* parent ) declareSkinlet< QskSegmentedBar, QskSegmentedBarSkinlet >(); declareSkinlet< QskSeparator, QskSeparatorSkinlet >(); declareSkinlet< QskSlider, QskSliderSkinlet >(); + declareSkinlet< QskSpinBox, QskSpinBoxSkinlet >(); declareSkinlet< QskStatusIndicator, QskStatusIndicatorSkinlet >(); declareSkinlet< QskSubWindow, QskSubWindowSkinlet >(); declareSkinlet< QskSubWindowArea, QskSubWindowAreaSkinlet >(); diff --git a/src/controls/QskSpinBox.cpp b/src/controls/QskSpinBox.cpp new file mode 100644 index 00000000..b7d19198 --- /dev/null +++ b/src/controls/QskSpinBox.cpp @@ -0,0 +1,353 @@ +/****************************************************************************** + * Copyright (C) 2023 Edelhirsch Software GmbH + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#include "QskSpinBox.h" +#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_SYSTEM_STATE(QskSpinBox, Pressed, ( QskAspect::QskAspect::FirstSystemState << 0)) + +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)) + { + 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 Dec; + if(layout == Qt::AlignVCenter) return Inc; + if(layout == (Qt::AlignLeft | Qt::AlignVCenter)) return Text; + if(layout == (Qt::AlignRight | Qt::AlignVCenter)) return Inc; + + 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]; + + 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]; + + 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) + { + m_focusIndex = index; + Q_EMIT q->focusIndexChanged(m_focusIndex); // TODO register enum + } + } + + QRectF focusIndicatorRect() const + { + switch(m_focusIndex) + { + 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 {}; + } + } + + void saveMousePosition(const QPointF& pos) + { + q->setSkinHint(QskSpinBox::Layout | QskAspect::Metric | QskAspect::Position, pos ); + } + +private: + qreal m_value{0.0f}; + QskSpinBox* const q; + FocusIndeces m_focusIndex = FocusIndeces::None; +}; + +using S = QskSpinBox; + +QskSpinBox::QskSpinBox(QQuickItem* const parent) + : Inherited(parent) + , m_data(std::make_unique(this)) +{ + 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(); }); +} + +QskSpinBox::~QskSpinBox() = default; + +void QskSpinBox::hoverEnterEvent(QHoverEvent* event) +{ + m_data->saveMousePosition( qskHoverPosition( event ) ); +} + +void QskSpinBox::hoverLeaveEvent(QHoverEvent* event) +{ + m_data->saveMousePosition( {} ); +} + +void QskSpinBox::hoverMoveEvent(QHoverEvent *event) +{ + m_data->saveMousePosition( qskHoverPosition( event ) ); +} + +void QskSpinBox::mouseReleaseEvent(QMouseEvent *event) +{ + m_data->saveMousePosition( qskMousePosition( event ) ); + + const auto focus = ( focusPolicy() & Qt::ClickFocus ) == Qt::ClickFocus && !QGuiApplication::styleHints()->setFocusOnTouchRelease(); + + if(subControlRect(QskSpinBox::Inc).contains( event->pos() )) + { + increment(+stepSize()); + + if( focus ) + { + m_data->setFocus(PrivateData::Inc); + } + + 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) + { + m_data->focusNext(); + } + for(int i = steps; i < 0; ++i) + { + m_data->focusPrevious(); + } + } + } + } + Inherited::keyPressEvent( event ); +} + +void QskSpinBox::keyReleaseEvent( QKeyEvent* event ) +{ + if( event->key() == Qt::Key_Select || event->key() == Qt::Key_Space ) + { + return; + } + + Inherited::keyReleaseEvent( event ); +} + +void QskSpinBox::focusInEvent(QFocusEvent *event) +{ + switch( event->reason() ) + { + case Qt::TabFocusReason: + m_data->focusNext(); + break; + + case Qt::BacktabFocusReason: + m_data->focusPrevious(); + break; + + default: + if(m_data->focusIndex() == PrivateData::None) + { + m_data->focusDefault(); + return; + } + } + Inherited::focusInEvent( event ); +} + +QRectF QskSpinBox::focusIndicatorRect() const +{ + auto rect = m_data->focusIndicatorRect(); + return rect; +} + +void QskSpinBox::increment(qreal offset) +{ + m_data->setValue(m_data->value() + offset); +} + +qreal QskSpinBox::value() const +{ + return m_data->value(); +} diff --git a/src/controls/QskSpinBox.h b/src/controls/QskSpinBox.h new file mode 100644 index 00000000..9222b6e2 --- /dev/null +++ b/src/controls/QskSpinBox.h @@ -0,0 +1,48 @@ +/****************************************************************************** + * Copyright (C) 2023 Edelhirsch Software GmbH + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#pragma once + +#include +#include +#include + +class QSK_EXPORT QskSpinBox : public QskBoundedInput +{ + Q_OBJECT + using Inherited = QskBoundedInput; +public: + Q_PROPERTY(qreal value READ value NOTIFY valueChanged) + QSK_SUBCONTROLS(Inc, Dec, IncText, DecText, TextPanel, Text, Layout) + QSK_STATES( Pressed ) + + explicit QskSpinBox( QQuickItem* parent = nullptr ); + ~QskSpinBox() override; + + void increment( qreal offset ) override; + qreal value() const; + +Q_SIGNALS: + void valueChanged(qreal value); + +private: + Q_SIGNAL void focusIndexChanged(int index); + + void hoverEnterEvent( QHoverEvent* event) override; + void hoverLeaveEvent( QHoverEvent* event) override; + void hoverMoveEvent( QHoverEvent* event) override; + + void mouseReleaseEvent(QMouseEvent* event) override; + void mousePressEvent(QMouseEvent* event) override; + + void keyPressEvent( QKeyEvent* event ) override; + void keyReleaseEvent( QKeyEvent* event ) override; + + void focusInEvent(QFocusEvent* event) override; + QRectF focusIndicatorRect() const override; + + class PrivateData; + std::unique_ptr m_data; +}; diff --git a/src/controls/QskSpinBoxSkinlet.cpp b/src/controls/QskSpinBoxSkinlet.cpp new file mode 100644 index 00000000..1d280943 --- /dev/null +++ b/src/controls/QskSpinBoxSkinlet.cpp @@ -0,0 +1,221 @@ +/****************************************************************************** + * Copyright (C) 2023 Edelhirsch Software GmbH + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#include "QskSpinBoxSkinlet.h" +#include "QskSpinBox.h" +#include + +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}); +} + +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) + { + 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); + +} + +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(skinnable); + const auto layout = spinbox->alignmentHint(QskSpinBox::Layout); + const auto spacing = spinbox->spacingHint(QskSpinBox::Layout); + + 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)) + { + const auto dx = qMax(rects[Inc].width(), rects[Dec].width()); + const auto dy = qMax(rects[Inc].height(), rects[Dec].height()); + 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); +} + +QSGNode* QskSpinBoxSkinlet::updateSubNode(const QskSkinnable* const skinnable, const quint8 nodeRole, QSGNode* const node) 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); +} + +QSGNode *QskSpinBoxSkinlet::updateSampleNode(const QskSkinnable* const skinnable, QskAspect::Subcontrol subControl, const int index, 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 ); +} diff --git a/src/controls/QskSpinBoxSkinlet.h b/src/controls/QskSpinBoxSkinlet.h new file mode 100644 index 00000000..ab6cbf65 --- /dev/null +++ b/src/controls/QskSpinBoxSkinlet.h @@ -0,0 +1,28 @@ +/****************************************************************************** + * Copyright (C) 2023 Edelhirsch Software GmbH + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#pragma once + +#include + +class QSK_EXPORT QskSpinBoxSkinlet : public QskSkinlet +{ + Q_GADGET + using Inherited = QskSkinlet; +public: + enum NodeRole + { + IncPanel, IncText, DecPanel, DecText, TextPanel, TextText, RoleCount + }; + Q_INVOKABLE QskSpinBoxSkinlet( QskSkin* = nullptr ); +protected: + int sampleCount( const QskSkinnable*, QskAspect::Subcontrol ) const override; + QRectF sampleRect( const QskSkinnable*, const QRectF&, QskAspect::Subcontrol, int index ) const override; + 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; + QSGNode* updateSubNode( const QskSkinnable*, quint8 nodeRole, QSGNode* ) const override; + QSGNode* updateSampleNode( const QskSkinnable* skinnable, QskAspect::Subcontrol subControl, int index, QSGNode* node ) const override; +}; diff --git a/src/src.pro b/src/src.pro index eed2d401..9250a6b3 100644 --- a/src/src.pro +++ b/src/src.pro @@ -227,6 +227,8 @@ HEADERS += \ controls/QskSliderSkinlet.h \ controls/QskStatusIndicator.h \ controls/QskStatusIndicatorSkinlet.h \ + controls/QskSpinBox.h \ + controls/QskSpinBoxSkinlet.h \ controls/QskSubWindowArea.h \ controls/QskSubWindowAreaSkinlet.h \ controls/QskSubWindow.h \ @@ -309,6 +311,8 @@ SOURCES += \ controls/QskSkinnable.cpp \ controls/QskSlider.cpp \ controls/QskSliderSkinlet.cpp \ + controls/QskSpinBox.cpp \ + controls/QskSpinBoxSkinlet.cpp \ controls/QskStatusIndicator.cpp \ controls/QskStatusIndicatorSkinlet.cpp \ controls/QskSubWindowArea.cpp \ From c615e429cf58c36a652eb541a1f6a8bd3fb286f4 Mon Sep 17 00:00:00 2001 From: "Vogel, Rick" Date: Fri, 17 Feb 2023 12:37:53 +0100 Subject: [PATCH 02/11] complete focus chain for all layouts --- src/controls/QskSpinBox.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/controls/QskSpinBox.cpp b/src/controls/QskSpinBox.cpp index b7d19198..ce56b2ea 100644 --- a/src/controls/QskSpinBox.cpp +++ b/src/controls/QskSpinBox.cpp @@ -61,10 +61,12 @@ public: if(layout == Qt::AlignRight) return Dec; if(layout == Qt::AlignHCenter) return Dec; if(layout == Qt::AlignTop) return Text; - if(layout == Qt::AlignBottom) return Dec; + 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; } @@ -84,6 +86,8 @@ public: 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; } @@ -103,6 +107,8 @@ public: 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; } From ad7a20053c73a73e3e8559ffaa06801b0c3b2f56 Mon Sep 17 00:00:00 2001 From: "Vogel, Rick" Date: Fri, 17 Feb 2023 13:30:39 +0100 Subject: [PATCH 03/11] refactoring + focused state --- examples/gallery/spinbox/SpinBoxPage.cpp | 2 +- skins/material3/QskMaterial3Skin.cpp | 10 +- src/controls/QskSpinBox.cpp | 141 ++++++++++++----------- src/controls/QskSpinBox.h | 11 +- src/controls/QskSpinBoxSkinlet.cpp | 82 +++++++------ 5 files changed, 134 insertions(+), 112 deletions(-) diff --git a/examples/gallery/spinbox/SpinBoxPage.cpp b/examples/gallery/spinbox/SpinBoxPage.cpp index ae559c6c..cd04dfe2 100644 --- a/examples/gallery/spinbox/SpinBoxPage.cpp +++ b/examples/gallery/spinbox/SpinBoxPage.cpp @@ -40,7 +40,7 @@ void SpinBoxPage::populate() 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 label = new QskTextLabel(layouts.value(layout), column); auto* const spinbox = new QskSpinBox( column ); spinbox->setAlignmentHint(QskSpinBox::Layout, layout); grid->addItem(column, y, x); diff --git a/skins/material3/QskMaterial3Skin.cpp b/skins/material3/QskMaterial3Skin.cpp index 199d2cdf..5eefad48 100644 --- a/skins/material3/QskMaterial3Skin.cpp +++ b/skins/material3/QskMaterial3Skin.cpp @@ -682,20 +682,20 @@ 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 ); @@ -710,7 +710,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 ce56b2ea..456f7404 100644 --- a/src/controls/QskSpinBox.cpp +++ b/src/controls/QskSpinBox.cpp @@ -19,22 +19,28 @@ #include #include -QSK_SUBCONTROL(QskSpinBox, Inc) -QSK_SUBCONTROL(QskSpinBox, Dec) -QSK_SUBCONTROL(QskSpinBox, IncText) -QSK_SUBCONTROL(QskSpinBox, DecText) +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)) +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) { } @@ -57,16 +63,16 @@ public: { 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; + 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; } @@ -74,20 +80,21 @@ public: FocusIndeces nextFocusIndex() const { const auto layout = q->alignmentHint(QskSpinBox::Layout); + using namespace aliased_enum; - // [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]; + // [0][1][2][3] := index + // [D][T][I][N] := control + using LUT = std::array; + 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{I,D,N,T}[m_focusIndex]; + if(layout == Qt::AlignBottom) return LUT{I,N,T,D}[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; } @@ -95,20 +102,21 @@ public: FocusIndeces previousFocusIndex() const { const auto layout = q->alignmentHint(QskSpinBox::Layout); + using namespace aliased_enum; - // [0 ][1 ][2 ][3 ] - // [Dec][Text][Inc][None] + // [0][1][2][3] := index + // [D][T][I][N] := control 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]; + if(layout == Qt::AlignLeft) return LUT{N,D,T,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{T,N,D,I}[m_focusIndex]; + if(layout == Qt::AlignBottom) return LUT{N,I,D,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; } @@ -137,26 +145,22 @@ public: void setFocus(const FocusIndeces index) { - Q_ASSERT(index == Dec || index == Text || index == Inc || index == None); - if(index == Dec || index == Text || index == Inc || index == None) + 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); // TODO register enum + Q_EMIT q->focusIndexChanged(m_focusIndex); } } QRectF focusIndicatorRect() const { - switch(m_focusIndex) - { - 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 {}; - } + using namespace aliased_enum; + if(m_focusIndex == D) return q->subControlRect(QskSpinBox::DecrementPanel); + if(m_focusIndex == I) return q->subControlRect(QskSpinBox::IncrementPanel); + if(m_focusIndex == T) return q->subControlRect(QskSpinBox::TextPanel); + return {}; } void saveMousePosition(const QPointF& pos) @@ -209,25 +213,25 @@ void QskSpinBox::mouseReleaseEvent(QMouseEvent *event) const auto focus = ( focusPolicy() & Qt::ClickFocus ) == Qt::ClickFocus && !QGuiApplication::styleHints()->setFocusOnTouchRelease(); - if(subControlRect(QskSpinBox::Inc).contains( event->pos() )) + if(subControlRect(QskSpinBox::IncrementPanel).contains( event->pos() )) { increment(+stepSize()); if( focus ) { - m_data->setFocus(PrivateData::Inc); + m_data->setFocus(Increment); } return; } - if(subControlRect(QskSpinBox::Dec).contains( event->pos() )) + if(subControlRect(QskSpinBox::DecrementPanel).contains( event->pos() )) { increment(-stepSize()); if( focus ) { - m_data->setFocus(PrivateData::Dec); + m_data->setFocus(Decrement); } return; @@ -237,7 +241,7 @@ void QskSpinBox::mouseReleaseEvent(QMouseEvent *event) { if( focus ) { - m_data->setFocus(PrivateData::Text); + m_data->setFocus(Textbox); } return; @@ -252,20 +256,20 @@ void QskSpinBox::mousePressEvent(QMouseEvent *event) const auto focus = ( focusPolicy() & Qt::ClickFocus ) == Qt::ClickFocus && !QGuiApplication::styleHints()->setFocusOnTouchRelease(); - if(subControlRect(QskSpinBox::Inc).contains( event->pos() )) + if(subControlRect(QskSpinBox::IncrementPanel).contains( event->pos() )) { if( focus ) { - m_data->setFocus(PrivateData::Inc); + m_data->setFocus(QskSpinBox::Increment); } return; } - if(subControlRect(QskSpinBox::Dec).contains( event->pos() )) + if(subControlRect(QskSpinBox::DecrementPanel).contains( event->pos() )) { if( focus ) { - m_data->setFocus(PrivateData::Dec); + m_data->setFocus(QskSpinBox::Decrement); } return; } @@ -280,12 +284,12 @@ void QskSpinBox::keyPressEvent(QKeyEvent *event) case Qt::Key_Plus: case Qt::Key_Up: case Qt::Key_Right: - // TODO increment + increment(+stepSize()); break; case Qt::Key_Minus: case Qt::Key_Down: case Qt::Key_Left: - // TODO decrement + increment(-stepSize()); break; case Qt::Key_Select: case Qt::Key_Space: @@ -333,7 +337,7 @@ void QskSpinBox::focusInEvent(QFocusEvent *event) break; default: - if(m_data->focusIndex() == PrivateData::None) + if(m_data->focusIndex() == QskSpinBox::None) { m_data->focusDefault(); return; @@ -357,3 +361,8 @@ qreal QskSpinBox::value() const { return m_data->value(); } + +QskSpinBox::FocusIndeces QskSpinBox::focusIndex() const +{ + return m_data->focusIndex(); +} diff --git a/src/controls/QskSpinBox.h b/src/controls/QskSpinBox.h index 9222b6e2..d59a8d7c 100644 --- a/src/controls/QskSpinBox.h +++ b/src/controls/QskSpinBox.h @@ -14,8 +14,12 @@ class QSK_EXPORT QskSpinBox : public QskBoundedInput Q_OBJECT using Inherited = QskBoundedInput; public: + enum FocusIndeces : int { Decrement = 0, Textbox = 1, Increment = 2, None = 3 }; + Q_ENUM(FocusIndeces) + Q_PROPERTY(qreal value READ value NOTIFY valueChanged) - QSK_SUBCONTROLS(Inc, Dec, IncText, DecText, TextPanel, Text, Layout) + Q_PROPERTY(FocusIndeces focusIndex READ focusIndex NOTIFY focusIndexChanged) + QSK_SUBCONTROLS(IncrementPanel, DecrementPanel, IncrementText, DecrementText, TextPanel, Text, Layout) QSK_STATES( Pressed ) explicit QskSpinBox( QQuickItem* parent = nullptr ); @@ -24,12 +28,13 @@ public: void increment( qreal offset ) override; qreal value() const; + FocusIndeces focusIndex() const; + Q_SIGNALS: void valueChanged(qreal value); + void focusIndexChanged(int index); private: - Q_SIGNAL void focusIndexChanged(int index); - void hoverEnterEvent( QHoverEvent* event) override; void hoverLeaveEvent( QHoverEvent* event) override; void hoverMoveEvent( QHoverEvent* event) override; diff --git a/src/controls/QskSpinBoxSkinlet.cpp b/src/controls/QskSpinBoxSkinlet.cpp index 1d280943..41b89832 100644 --- a/src/controls/QskSpinBoxSkinlet.cpp +++ b/src/controls/QskSpinBoxSkinlet.cpp @@ -6,11 +6,12 @@ #include "QskSpinBoxSkinlet.h" #include "QskSpinBox.h" #include +#include -const auto INC_TEXT = QStringLiteral("+"); -const auto DEC_TEXT = QStringLiteral("-"); +const auto INCREMENT_TEXT = QStringLiteral("+"); +const auto DECREMENT_TEXT = QStringLiteral("-"); -enum SampleIndeces { Dec, Txt, Inc, Count }; +enum SampleIndeces { Dec = 0, Txt = 1, Inc = 2, Count }; QskSpinBoxSkinlet::QskSpinBoxSkinlet(QskSkin *) { @@ -34,18 +35,26 @@ QRectF QskSpinBoxSkinlet::sampleRect(const QskSkinnable* const skinnable, const QskAspect::States QskSpinBoxSkinlet::sampleStates(const QskSkinnable* const skinnable, QskAspect::Subcontrol subControl, int index) const { - auto states = Inherited::sampleStates( skinnable, subControl, index ); + using S = QskSpinBox; + 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(skinnable); - const auto cursorPos = spinbox->effectiveSkinHint(QskSpinBox::Layout | QskAspect::Metric | QskAspect::Position).toPointF(); + const auto* const spinbox = static_cast(skinnable); + const auto cursorPos = spinbox->effectiveSkinHint(S::Layout | QskAspect::Metric | QskAspect::Position).toPointF(); const QPointF cursorPosAbs{qAbs(cursorPos.x()), qAbs(cursorPos.y())}; + const auto focusIndex = spinbox->focusIndex(); + 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; + 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 states; @@ -55,11 +64,11 @@ QSizeF QskSpinBoxSkinlet::sizeHint(const QskSkinnable* const skinnable, Qt::Size { 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 layout = spinbox->alignmentHint(S::Layout); + const auto spacing = spinbox->spacingHint(S::Layout); - const auto strutInc = spinbox->strutSizeHint(S::Inc); - const auto strutDec = spinbox->strutSizeHint(S::Dec); + const auto strutInc = spinbox->strutSizeHint(S::IncrementPanel); + const auto strutDec = spinbox->strutSizeHint(S::DecrementPanel); const auto strutTxt = spinbox->strutSizeHint(S::TextPanel); if(sizeHint == Qt::MinimumSize || sizeHint == Qt::MaximumSize || Qt::PreferredSize) @@ -90,26 +99,25 @@ QSizeF QskSpinBoxSkinlet::sizeHint(const QskSkinnable* const skinnable, Qt::Size } } return Inherited::sizeHint(skinnable, sizeHint, size); - } 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(skinnable); - const auto layout = spinbox->alignmentHint(QskSpinBox::Layout); - const auto spacing = spinbox->spacingHint(QskSpinBox::Layout); - using S = QskSpinBox; - QRectF rects[Count] = + 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(skinnable); + const auto layout = spinbox->alignmentHint(S::Layout); + const auto spacing = spinbox->spacingHint(S::Layout); + + std::array rects = { - { {}, spinbox->strutSizeHint(S::Dec)}, - { {}, spinbox->strutSizeHint(S::TextPanel)}, - { {}, spinbox->strutSizeHint(S::Inc)}, + QRectF{ QPointF{}, spinbox->strutSizeHint(S::DecrementPanel)}, + QRectF{ QPointF{}, spinbox->strutSizeHint(S::TextPanel)}, + QRectF{ QPointF{}, spinbox->strutSizeHint(S::IncrementPanel)}, }; const auto center = rect.center(); @@ -173,14 +181,12 @@ QRectF QskSpinBoxSkinlet::subControlRect(const QskSkinnable* const skinnable, co } else if(layout == (Qt::AlignBottom | Qt::AlignHCenter)) { - const auto dx = qMax(rects[Inc].width(), rects[Dec].width()); - const auto dy = qMax(rects[Inc].height(), rects[Dec].height()); 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) + if(subControl == S::DecrementPanel) { return rects[Dec]; } @@ -188,7 +194,7 @@ QRectF QskSpinBoxSkinlet::subControlRect(const QskSkinnable* const skinnable, co { return rects[Txt]; } - if(subControl == S::Inc) + if(subControl == S::IncrementPanel) { return rects[Inc]; } @@ -198,20 +204,22 @@ QRectF QskSpinBoxSkinlet::subControlRect(const QskSkinnable* const skinnable, co QSGNode* QskSpinBoxSkinlet::updateSubNode(const QskSkinnable* const skinnable, const quint8 nodeRole, QSGNode* const node) 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 ); } + using S = QskSpinBox; + if(nodeRole == IncPanel) { return updateSeriesNode( skinnable, S::IncrementPanel, node); } + if(nodeRole == DecPanel) { return updateSeriesNode( skinnable, S::DecrementPanel, node ); } + if(nodeRole == IncText) { return updateTextNode( skinnable, node, INCREMENT_TEXT, S::IncrementText); } + if(nodeRole == DecText) { return updateTextNode( skinnable, node, DECREMENT_TEXT, S::DecrementText ); } + if(nodeRole == TextPanel) { return updateSeriesNode( skinnable, S::TextPanel, node ); } + if(nodeRole == TextText) { return updateTextNode( skinnable, node, QString::number(static_cast(skinnable)->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(skinnable); + using S = QskSpinBox; + const auto* const spinbox = static_cast(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); return updateBoxNode( skinnable, node, rect, subControl ); From 55b8da0b7e76d9f0a778fc30f289495849ce81f8 Mon Sep 17 00:00:00 2001 From: "Vogel, Rick" Date: Fri, 17 Feb 2023 14:36:30 +0100 Subject: [PATCH 04/11] fixed double inc/dec on key --- src/controls/QskSpinBox.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/controls/QskSpinBox.cpp b/src/controls/QskSpinBox.cpp index 456f7404..1060715a 100644 --- a/src/controls/QskSpinBox.cpp +++ b/src/controls/QskSpinBox.cpp @@ -285,16 +285,16 @@ void QskSpinBox::keyPressEvent(QKeyEvent *event) case Qt::Key_Up: case Qt::Key_Right: increment(+stepSize()); - break; + return; case Qt::Key_Minus: case Qt::Key_Down: case Qt::Key_Left: increment(-stepSize()); - break; + return; case Qt::Key_Select: case Qt::Key_Space: // TODO click currently focused -/+ - break; + return; default: { const int steps = qskFocusChainIncrement( event ); From 3d96541079213a509a2a5a601f96dd477e950e95 Mon Sep 17 00:00:00 2001 From: "Vogel, Rick" Date: Fri, 17 Feb 2023 14:37:10 +0100 Subject: [PATCH 05/11] fixed focus chain --- src/controls/QskSpinBox.cpp | 41 +++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/src/controls/QskSpinBox.cpp b/src/controls/QskSpinBox.cpp index 1060715a..4519a6f5 100644 --- a/src/controls/QskSpinBox.cpp +++ b/src/controls/QskSpinBox.cpp @@ -51,8 +51,7 @@ public: if(!qFuzzyCompare(m_value, value)) { m_value = value; - Q_EMIT q->valueChanged(m_value); - q->polish(); + Q_EMIT q->valueChanged(m_value); q->update(); } } @@ -88,8 +87,8 @@ public: 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{I,D,N,T}[m_focusIndex]; - if(layout == Qt::AlignBottom) return LUT{I,N,T,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]; @@ -104,14 +103,14 @@ public: const auto layout = q->alignmentHint(QskSpinBox::Layout); using namespace aliased_enum; - // [0][1][2][3] := index - // [D][T][I][N] := control + // [0][1][2][3] := index + // [D][T][I][N] := control using LUT = std::array; - if(layout == Qt::AlignLeft) return LUT{N,D,T,I}[m_focusIndex]; + 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{T,N,D,I}[m_focusIndex]; - if(layout == Qt::AlignBottom) return LUT{N,I,D,T}[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]; @@ -140,7 +139,8 @@ public: void focusDefault() { - setFocus(defaultFocusIndex()); + const auto index = defaultFocusIndex(); + setFocus(index); } void setFocus(const FocusIndeces index) @@ -151,6 +151,7 @@ public: { m_focusIndex = index; Q_EMIT q->focusIndexChanged(m_focusIndex); + q->update(); } } @@ -298,16 +299,26 @@ void QskSpinBox::keyPressEvent(QKeyEvent *event) default: { const int steps = qskFocusChainIncrement( event ); - if(steps != 0) + + 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(); } - for(int i = steps; i < 0; ++i) - { - m_data->focusPrevious(); - } + } + + if(steps != 0 && m_data->focusIndex() != None) + { + return; } } } From 13575678d6f12a0e3e6d97ad1fc13dd214312f6d Mon Sep 17 00:00:00 2001 From: "Vogel, Rick" Date: Fri, 17 Feb 2023 14:46:05 +0100 Subject: [PATCH 06/11] increment or decrement on space key --- src/controls/QskSpinBox.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/controls/QskSpinBox.cpp b/src/controls/QskSpinBox.cpp index 4519a6f5..7ba306e1 100644 --- a/src/controls/QskSpinBox.cpp +++ b/src/controls/QskSpinBox.cpp @@ -294,7 +294,8 @@ void QskSpinBox::keyPressEvent(QKeyEvent *event) return; case Qt::Key_Select: case Qt::Key_Space: - // TODO click currently focused -/+ + if(focusIndex() == Increment) increment(+stepSize()); + if(focusIndex() == Decrement) increment(-stepSize()); return; default: { From 04fa1c6dd66609b16ccbefd8e5221f63fd9cd945 Mon Sep 17 00:00:00 2001 From: "Vogel, Rick" Date: Fri, 17 Feb 2023 14:46:54 +0100 Subject: [PATCH 07/11] remove non-theme color --- skins/material3/QskMaterial3Skin.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/skins/material3/QskMaterial3Skin.cpp b/skins/material3/QskMaterial3Skin.cpp index 5eefad48..bb9317e6 100644 --- a/skins/material3/QskMaterial3Skin.cpp +++ b/skins/material3/QskMaterial3Skin.cpp @@ -691,7 +691,6 @@ void Editor::setupSpinBox() for(const auto& state : {QskSpinBox::DecrementPanel, QskSpinBox::IncrementPanel, QskSpinBox::TextPanel}) { setBoxShape(state, 4_dp); - setBoxBorderColors(state, QColor("#79747E")); setBoxBorderMetrics(state, 1_dp); } From 890ae646527bd8b3c931e9f3d3e0c81d644779b3 Mon Sep 17 00:00:00 2001 From: "Vogel, Rick" Date: Fri, 17 Feb 2023 14:57:33 +0100 Subject: [PATCH 08/11] using QskBoundedValueInput as base class --- src/controls/QskSpinBox.cpp | 26 -------------------------- src/controls/QskSpinBox.h | 12 +++--------- 2 files changed, 3 insertions(+), 35 deletions(-) diff --git a/src/controls/QskSpinBox.cpp b/src/controls/QskSpinBox.cpp index 7ba306e1..3eabea92 100644 --- a/src/controls/QskSpinBox.cpp +++ b/src/controls/QskSpinBox.cpp @@ -45,19 +45,6 @@ public: { } - 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->update(); - } - } - - qreal value() const { return m_value; } - FocusIndeces defaultFocusIndex() const { const auto layout = q->alignmentHint(QskSpinBox::Layout); @@ -170,7 +157,6 @@ public: } private: - qreal m_value{0.0f}; QskSpinBox* const q; FocusIndeces m_focusIndex = FocusIndeces::None; }; @@ -187,8 +173,6 @@ QskSpinBox::QskSpinBox(QQuickItem* const parent) 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(); }); } QskSpinBox::~QskSpinBox() = default; @@ -364,16 +348,6 @@ QRectF QskSpinBox::focusIndicatorRect() const return rect; } -void QskSpinBox::increment(qreal offset) -{ - m_data->setValue(m_data->value() + offset); -} - -qreal QskSpinBox::value() const -{ - return m_data->value(); -} - QskSpinBox::FocusIndeces QskSpinBox::focusIndex() const { return m_data->focusIndex(); diff --git a/src/controls/QskSpinBox.h b/src/controls/QskSpinBox.h index d59a8d7c..2d4e7efd 100644 --- a/src/controls/QskSpinBox.h +++ b/src/controls/QskSpinBox.h @@ -7,31 +7,25 @@ #include #include -#include +#include -class QSK_EXPORT QskSpinBox : public QskBoundedInput +class QSK_EXPORT QskSpinBox : public QskBoundedValueInput { Q_OBJECT - using Inherited = QskBoundedInput; + using Inherited = QskBoundedValueInput; public: enum FocusIndeces : int { Decrement = 0, Textbox = 1, Increment = 2, None = 3 }; Q_ENUM(FocusIndeces) - Q_PROPERTY(qreal value READ value NOTIFY valueChanged) Q_PROPERTY(FocusIndeces focusIndex READ focusIndex NOTIFY focusIndexChanged) QSK_SUBCONTROLS(IncrementPanel, DecrementPanel, IncrementText, DecrementText, TextPanel, Text, Layout) QSK_STATES( Pressed ) explicit QskSpinBox( QQuickItem* parent = nullptr ); ~QskSpinBox() override; - - void increment( qreal offset ) override; - qreal value() const; - FocusIndeces focusIndex() const; Q_SIGNALS: - void valueChanged(qreal value); void focusIndexChanged(int index); private: From 1faf2e091c76ee729029057a533fe6841d767978 Mon Sep 17 00:00:00 2001 From: "Vogel, Rick" Date: Fri, 17 Feb 2023 15:22:40 +0100 Subject: [PATCH 09/11] clang-format + clang-tidy improvements --- src/controls/QskSpinBox.cpp | 670 +++++++++++++++++------------ src/controls/QskSpinBox.h | 60 +-- src/controls/QskSpinBoxSkinlet.cpp | 467 +++++++++++--------- src/controls/QskSpinBoxSkinlet.h | 44 +- 4 files changed, 721 insertions(+), 520 deletions(-) diff --git a/src/controls/QskSpinBox.cpp b/src/controls/QskSpinBox.cpp index 3eabea92..1ed352b0 100644 --- a/src/controls/QskSpinBox.cpp +++ b/src/controls/QskSpinBox.cpp @@ -4,351 +4,463 @@ *****************************************************************************/ #include "QskSpinBox.h" -#include -#include -#include +#include #include -#include +#include #include #include -#include +#include #include -#include -#include -#include +#include #include +#include +#include +#include +#include #include -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_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; + constexpr auto D = QskSpinBox::Decrement; + constexpr auto T = QskSpinBox::Textbox; + constexpr auto I = QskSpinBox::Increment; + constexpr auto N = QskSpinBox::None; } class QskSpinBox::PrivateData { -public: - - explicit PrivateData(QskSpinBox* const parent) : q(parent) - { - } - - FocusIndeces defaultFocusIndex() const - { - const auto layout = q->alignmentHint(QskSpinBox::Layout); - - 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 - { - 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; - 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; - } - - FocusIndeces previousFocusIndex() const - { - 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; - 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) + public: + explicit PrivateData( QskSpinBox* const parent ) + : q( parent ) { - m_focusIndex = index; - Q_EMIT q->focusIndexChanged(m_focusIndex); - q->update(); } - } - QRectF focusIndicatorRect() const - { - using namespace aliased_enum; - if(m_focusIndex == D) return q->subControlRect(QskSpinBox::DecrementPanel); - if(m_focusIndex == I) return q->subControlRect(QskSpinBox::IncrementPanel); - if(m_focusIndex == T) return q->subControlRect(QskSpinBox::TextPanel); - return {}; - } + FocusIndeces defaultFocusIndex() const + { + const auto layout = q->alignmentHint( QskSpinBox::Layout ); - void saveMousePosition(const QPointF& pos) - { - q->setSkinHint(QskSpinBox::Layout | QskAspect::Metric | QskAspect::Position, pos ); - } + 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; + } -private: - QskSpinBox* const q; - FocusIndeces m_focusIndex = FocusIndeces::None; + return None; + } + + FocusIndeces nextFocusIndex() const + { + 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; + } + + FocusIndeces previousFocusIndex() const + { + 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< 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(std::make_unique(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::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* event) +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::IncrementPanel).contains( event->pos() )) - { - increment(+stepSize()); - - if( focus ) + if ( subControlRect( QskSpinBox::IncrementPanel ).contains( event->pos() ) ) { - m_data->setFocus(Increment); - } + increment( +stepSize() ); - return; - } - - 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::mousePressEvent(QMouseEvent *event) -{ - m_data->saveMousePosition( -1 * qskMousePosition( event ) ); - - const auto focus = ( focusPolicy() & Qt::ClickFocus ) == Qt::ClickFocus && !QGuiApplication::styleHints()->setFocusOnTouchRelease(); - - 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::keyPressEvent(QKeyEvent *event) -{ - 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: - { - const int steps = qskFocusChainIncrement( event ); - - if(steps < 0) - { - for(int i = 0; i < qAbs(steps); ++i) + if ( focus ) { - m_data->focusPrevious(); + m_data->setFocus( Increment ); } - } - if(steps > 0) - { - for(int i = 0; i < steps; ++i) - { - m_data->focusNext(); - } - } - - if(steps != 0 && m_data->focusIndex() != None) - { 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() == QskSpinBox::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(); } QskSpinBox::FocusIndeces QskSpinBox::focusIndex() const { - return m_data->focusIndex(); + return m_data->focusIndex(); } diff --git a/src/controls/QskSpinBox.h b/src/controls/QskSpinBox.h index 2d4e7efd..f0318815 100644 --- a/src/controls/QskSpinBox.h +++ b/src/controls/QskSpinBox.h @@ -5,43 +5,51 @@ #pragma once +#include #include #include -#include class QSK_EXPORT QskSpinBox : public QskBoundedValueInput { - Q_OBJECT - using Inherited = QskBoundedValueInput; -public: - enum FocusIndeces : int { Decrement = 0, Textbox = 1, Increment = 2, None = 3 }; - Q_ENUM(FocusIndeces) + Q_OBJECT + using Inherited = QskBoundedValueInput; - Q_PROPERTY(FocusIndeces focusIndex READ focusIndex NOTIFY focusIndexChanged) - QSK_SUBCONTROLS(IncrementPanel, DecrementPanel, IncrementText, DecrementText, TextPanel, Text, Layout) - QSK_STATES( Pressed ) + public: + enum FocusIndeces : int + { + Decrement = 0, + Textbox = 1, + Increment = 2, + None = 3 + }; + Q_ENUM( FocusIndeces ) - explicit QskSpinBox( QQuickItem* parent = nullptr ); - ~QskSpinBox() override; - FocusIndeces focusIndex() const; + Q_PROPERTY( FocusIndeces focusIndex READ focusIndex NOTIFY focusIndexChanged ) + QSK_SUBCONTROLS( + IncrementPanel, DecrementPanel, IncrementText, DecrementText, TextPanel, Text, Layout ) + QSK_STATES( Pressed ) -Q_SIGNALS: - void focusIndexChanged(int index); + explicit QskSpinBox( QQuickItem* parent = nullptr ); + ~QskSpinBox() override; + FocusIndeces focusIndex() const; -private: - void hoverEnterEvent( QHoverEvent* event) override; - void hoverLeaveEvent( QHoverEvent* event) override; - void hoverMoveEvent( QHoverEvent* event) override; + Q_SIGNALS: + void focusIndexChanged( int index ); - void mouseReleaseEvent(QMouseEvent* event) override; - void mousePressEvent(QMouseEvent* event) override; + private: + void hoverEnterEvent( QHoverEvent* event ) override; + void hoverLeaveEvent( QHoverEvent* event ) override; + void hoverMoveEvent( QHoverEvent* event ) override; - void keyPressEvent( QKeyEvent* event ) override; - void keyReleaseEvent( QKeyEvent* event ) override; + void mouseReleaseEvent( QMouseEvent* event ) override; + void mousePressEvent( QMouseEvent* event ) override; - void focusInEvent(QFocusEvent* event) override; - QRectF focusIndicatorRect() const override; + void keyPressEvent( QKeyEvent* event ) override; + void keyReleaseEvent( QKeyEvent* event ) override; - class PrivateData; - std::unique_ptr m_data; + void focusInEvent( QFocusEvent* event ) override; + QRectF focusIndicatorRect() const override; + + class PrivateData; + std::unique_ptr< PrivateData > m_data; }; diff --git a/src/controls/QskSpinBoxSkinlet.cpp b/src/controls/QskSpinBoxSkinlet.cpp index 41b89832..15c2e1f2 100644 --- a/src/controls/QskSpinBoxSkinlet.cpp +++ b/src/controls/QskSpinBoxSkinlet.cpp @@ -8,222 +8,291 @@ #include #include -const auto INCREMENT_TEXT = QStringLiteral("+"); -const auto DECREMENT_TEXT = QStringLiteral("-"); - -enum SampleIndeces { Dec = 0, Txt = 1, Inc = 2, 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 -{ - using S = QskSpinBox; - auto states = Inherited::sampleStates( skinnable, subControl, index ); - - if ( subControl == S::DecrementPanel || subControl == S::IncrementPanel || subControl == S::TextPanel) - { - const auto* const spinbox = static_cast(skinnable); - const auto cursorPos = spinbox->effectiveSkinHint(S::Layout | QskAspect::Metric | QskAspect::Position).toPointF(); - const QPointF cursorPosAbs{qAbs(cursorPos.x()), qAbs(cursorPos.y())}; - const auto focusIndex = spinbox->focusIndex(); - - 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; - 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 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(S::Layout); - const auto spacing = spinbox->spacingHint(S::Layout); - - const auto strutInc = spinbox->strutSizeHint(S::IncrementPanel); - const auto strutDec = spinbox->strutSizeHint(S::DecrementPanel); - 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( { 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 ) { - 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 { - using S = QskSpinBox; + 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 ); - 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 strutInc = spinbox->strutSizeHint( S::IncrementPanel ); + const auto strutDec = spinbox->strutSizeHint( S::DecrementPanel ); + const auto strutTxt = spinbox->strutSizeHint( S::TextPanel ); - const auto* const spinbox = static_cast(skinnable); - const auto layout = spinbox->alignmentHint(S::Layout); - const auto spacing = spinbox->spacingHint(S::Layout); - - std::array rects = - { - QRectF{ QPointF{}, spinbox->strutSizeHint(S::DecrementPanel)}, - QRectF{ QPointF{}, spinbox->strutSizeHint(S::TextPanel)}, - QRectF{ QPointF{}, spinbox->strutSizeHint(S::IncrementPanel)}, - }; - - 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::DecrementPanel) - { - return rects[Dec]; - } - if(subControl == S::TextPanel) - { - return rects[Txt]; - } - if(subControl == S::IncrementPanel) - { - 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 { - using S = QskSpinBox; - if(nodeRole == IncPanel) { return updateSeriesNode( skinnable, S::IncrementPanel, node); } - if(nodeRole == DecPanel) { return updateSeriesNode( skinnable, S::DecrementPanel, node ); } - if(nodeRole == IncText) { return updateTextNode( skinnable, node, INCREMENT_TEXT, S::IncrementText); } - if(nodeRole == DecText) { return updateTextNode( skinnable, node, DECREMENT_TEXT, S::DecrementText ); } - if(nodeRole == TextPanel) { return updateSeriesNode( skinnable, S::TextPanel, node ); } - if(nodeRole == TextText) { return updateTextNode( skinnable, node, QString::number(static_cast(skinnable)->value()), S::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 { - using S = QskSpinBox; - const auto* const spinbox = static_cast(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 ); + using S = QskSpinBox; + if ( nodeRole == IncPanel ) + { + return updateSeriesNode( skinnable, S::IncrementPanel, node ); + } + if ( nodeRole == DecPanel ) + { + return updateSeriesNode( skinnable, S::DecrementPanel, node ); + } + if ( nodeRole == IncText ) + { + return updateTextNode( skinnable, node, QStringLiteral( "+" ), S::IncrementText ); + } + if ( nodeRole == DecText ) + { + 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 ab6cbf65..1013d942 100644 --- a/src/controls/QskSpinBoxSkinlet.h +++ b/src/controls/QskSpinBoxSkinlet.h @@ -9,20 +9,32 @@ class QSK_EXPORT QskSpinBoxSkinlet : public QskSkinlet { - Q_GADGET - using Inherited = QskSkinlet; -public: - enum NodeRole - { - IncPanel, IncText, DecPanel, DecText, TextPanel, TextText, RoleCount - }; - Q_INVOKABLE QskSpinBoxSkinlet( QskSkin* = nullptr ); -protected: - int sampleCount( const QskSkinnable*, QskAspect::Subcontrol ) const override; - QRectF sampleRect( const QskSkinnable*, const QRectF&, QskAspect::Subcontrol, int index ) const override; - 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; - QSGNode* updateSubNode( const QskSkinnable*, quint8 nodeRole, QSGNode* ) const override; - QSGNode* updateSampleNode( const QskSkinnable* skinnable, QskAspect::Subcontrol subControl, int index, QSGNode* node ) const override; + Q_GADGET + using Inherited = QskSkinlet; + + public: + enum NodeRole + { + IncPanel, + IncText, + DecPanel, + DecText, + TextPanel, + TextText, + RoleCount + }; + Q_INVOKABLE QskSpinBoxSkinlet( QskSkin* = nullptr ); + + protected: + int sampleCount( const QskSkinnable*, QskAspect::Subcontrol ) const override; + QRectF sampleRect( + const QskSkinnable*, const QRectF&, QskAspect::Subcontrol, int index ) const override; + 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; + QSGNode* updateSubNode( const QskSkinnable*, quint8 nodeRole, QSGNode* ) const override; + QSGNode* updateSampleNode( const QskSkinnable* skinnable, QskAspect::Subcontrol subControl, + int index, QSGNode* node ) const override; }; From 767414fbc7ab58bb979815a1f65d158b1bd34420 Mon Sep 17 00:00:00 2001 From: "Vogel, Rick" Date: Fri, 17 Feb 2023 17:46:52 +0100 Subject: [PATCH 10/11] doxygen + sample count = 1 --- src/controls/QskSpinBox.h | 49 +++++++++++--- src/controls/QskSpinBoxSkinlet.cpp | 13 ++-- src/controls/QskSpinBoxSkinlet.h | 105 ++++++++++++++++++++++++----- 3 files changed, 136 insertions(+), 31 deletions(-) diff --git a/src/controls/QskSpinBox.h b/src/controls/QskSpinBox.h index f0318815..afa4b293 100644 --- a/src/controls/QskSpinBox.h +++ b/src/controls/QskSpinBox.h @@ -6,34 +6,67 @@ #pragma once #include -#include -#include +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// @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 using Inherited = QskBoundedValueInput; public: + /// Focus indeces for the visual subcontrols enum FocusIndeces : int { - Decrement = 0, - Textbox = 1, - Increment = 2, - None = 3 + 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, DecrementPanel, IncrementText, DecrementText, TextPanel, Text, Layout ) + 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; + + /// @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: + /// @brief Emitted when the property @c focusIndex changed. void focusIndexChanged( int index ); private: diff --git a/src/controls/QskSpinBoxSkinlet.cpp b/src/controls/QskSpinBoxSkinlet.cpp index 15c2e1f2..6fcd9618 100644 --- a/src/controls/QskSpinBoxSkinlet.cpp +++ b/src/controls/QskSpinBoxSkinlet.cpp @@ -27,12 +27,13 @@ namespace QskSpinBoxSkinlet::QskSpinBoxSkinlet( QskSkin* ) { - setNodeRoles( { IncPanel, IncText, DecPanel, DecText, TextPanel, TextText } ); + setNodeRoles( + { IncrementPanel, IncrementText, DecrementPanel, DecrementText, TextPanel, TextText } ); } int QskSpinBoxSkinlet::sampleCount( const QskSkinnable*, QskAspect::Subcontrol ) const { - return Count; + return 1; } QRectF QskSpinBoxSkinlet::sampleRect( const QskSkinnable* const skinnable, const QRectF& rect, @@ -253,19 +254,19 @@ QSGNode* QskSpinBoxSkinlet::updateSubNode( const QskSkinnable* const skinnable, const quint8 nodeRole, QSGNode* const node ) const { using S = QskSpinBox; - if ( nodeRole == IncPanel ) + if ( nodeRole == IncrementPanel ) { return updateSeriesNode( skinnable, S::IncrementPanel, node ); } - if ( nodeRole == DecPanel ) + if ( nodeRole == DecrementPanel ) { return updateSeriesNode( skinnable, S::DecrementPanel, node ); } - if ( nodeRole == IncText ) + if ( nodeRole == IncrementText ) { return updateTextNode( skinnable, node, QStringLiteral( "+" ), S::IncrementText ); } - if ( nodeRole == DecText ) + if ( nodeRole == DecrementText ) { return updateTextNode( skinnable, node, QStringLiteral( "-" ), S::DecrementText ); } diff --git a/src/controls/QskSpinBoxSkinlet.h b/src/controls/QskSpinBoxSkinlet.h index 1013d942..dc506693 100644 --- a/src/controls/QskSpinBoxSkinlet.h +++ b/src/controls/QskSpinBoxSkinlet.h @@ -7,34 +7,105 @@ #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; - QRectF sampleRect( - const QskSkinnable*, const QRectF&, QskAspect::Subcontrol, int index ) 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; + + /// @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; + + /// @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; - QSGNode* updateSubNode( const QskSkinnable*, quint8 nodeRole, QSGNode* ) 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; + + /// @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; }; From 8e8f8f5c2adf53edf1003f0e4630b139cf185b0d Mon Sep 17 00:00:00 2001 From: "Vogel, Rick" Date: Fri, 17 Feb 2023 18:34:18 +0100 Subject: [PATCH 11/11] added sliders to play with sizes --- examples/gallery/spinbox/SpinBoxPage.cpp | 115 +++++++++++++++++------ 1 file changed, 85 insertions(+), 30 deletions(-) diff --git a/examples/gallery/spinbox/SpinBoxPage.cpp b/examples/gallery/spinbox/SpinBoxPage.cpp index cd04dfe2..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); - 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 ); }