From 020857332539a98ac14b319e899a660df9e6414e Mon Sep 17 00:00:00 2001 From: Peter Hartmann Date: Tue, 1 Oct 2024 12:32:05 +0200 Subject: [PATCH] text input: Add subcontrols needed for M3 and F2 --- designsystems/material3/QskMaterial3Icons.qrc | 3 + designsystems/material3/QskMaterial3Skin.cpp | 131 +++++++++-- designsystems/material3/QskMaterial3Skin.h | 2 + .../material3/icons/qvg/text_field_cancel.qvg | Bin 0 -> 1247 bytes .../material3/icons/qvg/text_field_error.qvg | Bin 0 -> 927 bytes .../material3/icons/qvg/text_field_search.qvg | Bin 0 -> 1107 bytes .../material3/icons/text_field_cancel.svg | 4 + .../material3/icons/text_field_error.svg | 4 + .../material3/icons/text_field_search.svg | 4 + examples/gallery/inputs/InputPage.cpp | 45 +++- src/controls/QskTextInput.cpp | 119 +++++++++- src/controls/QskTextInput.h | 44 +++- src/controls/QskTextInputSkinlet.cpp | 220 +++++++++++++++++- src/controls/QskTextInputSkinlet.h | 7 + 14 files changed, 530 insertions(+), 53 deletions(-) create mode 100644 designsystems/material3/icons/qvg/text_field_cancel.qvg create mode 100644 designsystems/material3/icons/qvg/text_field_error.qvg create mode 100644 designsystems/material3/icons/qvg/text_field_search.qvg create mode 100644 designsystems/material3/icons/text_field_cancel.svg create mode 100644 designsystems/material3/icons/text_field_error.svg create mode 100644 designsystems/material3/icons/text_field_search.svg diff --git a/designsystems/material3/QskMaterial3Icons.qrc b/designsystems/material3/QskMaterial3Icons.qrc index 72725b7a..2c44364a 100644 --- a/designsystems/material3/QskMaterial3Icons.qrc +++ b/designsystems/material3/QskMaterial3Icons.qrc @@ -17,5 +17,8 @@ icons/qvg/arrow_drop_up.qvg icons/qvg/check.qvg icons/qvg/remove.qvg + icons/qvg/text_field_search.qvg + icons/qvg/text_field_cancel.qvg + icons/qvg/text_field_error.qvg diff --git a/designsystems/material3/QskMaterial3Skin.cpp b/designsystems/material3/QskMaterial3Skin.cpp index 608f48fe..076850b6 100644 --- a/designsystems/material3/QskMaterial3Skin.cpp +++ b/designsystems/material3/QskMaterial3Skin.cpp @@ -452,33 +452,126 @@ void Editor::setupTextInput() { using Q = QskTextInput; - setStrutSize( Q::Panel, -1.0, 56_dp ); - setPadding( Q::Panel, { 12_dp, 8_dp, 12_dp, 8_dp } ); - setGradient( Q::Panel, m_pal.surfaceVariant ); - setBoxShape( Q::Panel, m_pal.shapeExtraSmallTop ); - setBoxBorderMetrics( Q::Panel, { 0, 0, 0, 1_dp } ); - setBoxBorderColors( Q::Panel, m_pal.onSurfaceVariant ); - setSpacing( Q::Panel, 8_dp ); + const QskStateCombination allStates( QskStateCombination::CombinationNoState, QskAspect::AllStates ); - const auto hoverColor = flattenedColor( m_pal.onSurfaceVariant, + // Panel + + setStrutSize( Q::Panel, -1.0, 56_dp ); + setGradient( Q::Panel, m_pal.surfaceVariant ); + + setBoxShape( Q::Panel, m_pal.shapeExtraSmallTop ); + + setBoxBorderMetrics( Q::Panel, { 0, 0, 0, 1_dp } ); + setBoxBorderMetrics( Q::Panel | Q::Focused, { 0, 0, 0, 2_dp }, allStates ); + + setBoxBorderColors( Q::Panel, m_pal.onSurfaceVariant ); + setBoxBorderColors( Q::Panel | Q::Error, m_pal.error, allStates ); + setBoxBorderColors( Q::Panel | Q::Error | Q::Hovered, m_pal.onErrorContainer, allStates ); + + const auto normalHoverColor = flattenedColor( m_pal.onSurfaceVariant, m_pal.surfaceVariant, m_pal.hoverOpacity ); - setGradient( Q::Panel | Q::Hovered, hoverColor ); + setGradient( Q::Panel | Q::Hovered, normalHoverColor, allStates ); + + const auto errorHoverColor = flattenedColor( m_pal.onSurface, + m_pal.surfaceVariant, m_pal.hoverOpacity ); + setGradient( Q::Panel | Q::Error | Q::Hovered, errorHoverColor, allStates ); const auto focusColor = flattenedColor( m_pal.onSurfaceVariant, m_pal.surfaceVariant, m_pal.focusOpacity ); - setGradient( Q::Panel | Q::Focused, focusColor ); - - // ### Also add a pressed state - - setColor( Q::InputText, m_pal.onSurface ); - setFontRole( Q::InputText, BodyMedium ); - setAlignment( Q::InputText, Qt::AlignLeft | Qt::AlignVCenter ); + setGradient( Q::Panel | Q::Focused, focusColor, allStates ); const auto disabledPanelColor = QskRgb::toTransparentF( m_pal.onSurface, 0.04 ); - setGradient( Q::Panel | Q::Disabled, disabledPanelColor ); - setBoxBorderColors( Q::Panel | Q::Disabled, m_pal.onSurface38 ); + setGradient( Q::Panel | Q::Disabled, disabledPanelColor, allStates ); + setBoxBorderColors( Q::Panel | Q::Disabled, m_pal.onSurface38, allStates ); + + + // LeadingIcon + + setStrutSize( Q::LeadingIcon, { 24_dp, 24_dp } ); + setMargin( Q::LeadingIcon, { 12_dp, 0, 0, 0 } ); + const auto leadingIcon = symbol( "text_field_search" ); + setSymbol( Q::LeadingIcon, leadingIcon ); + + setGraphicRole( Q::LeadingIcon, QskMaterial3Skin::GraphicRoleOnSurface ); + setGraphicRole( Q::LeadingIcon | Q::Error, QskMaterial3Skin::GraphicRoleOnSurfaceVariant, allStates ); + + + // LabelText + + setAlignment( Q::LabelText, Qt::AlignLeft | Qt::AlignVCenter ); + + const QskStateCombination textEmptyStates( QskStateCombination::CombinationNoState, + Q::Focused | Q::Hovered | Q::ReadOnly | Q::Disabled ); + + setFontRole( Q::LabelText | Q::TextEmpty, BodyMedium, textEmptyStates ); + setColor( Q::LabelText | Q::TextEmpty, m_pal.onSurfaceVariant, textEmptyStates ); + + const QskStateCombination editingHoveredFocused( QskStateCombination::CombinationNoState, + Q::Editing | Q::Hovered | Q::Focused ); + + setMargin( Q::LabelText, { 16_dp, 8_dp, 16_dp, 16_dp }, editingHoveredFocused ); + setColor( Q::LabelText, m_pal.primary, editingHoveredFocused ); + setFontRole( Q::LabelText, BodySmall, editingHoveredFocused ); + + setColor( Q::LabelText | Q::Error, m_pal.error, allStates ); + setColor( Q::LabelText | Q::Error | Q::Hovered, m_pal.onErrorContainer, allStates ); + + + // InputText + + setMargin( Q::InputText, { 16_dp, 8_dp, 16_dp, 8_dp } ); + setColor( Q::InputText, m_pal.onSurface ); + setFontRole( Q::InputText, BodyMedium ); + setAlignment( Q::InputText, Qt::AlignLeft | Qt::AlignBottom ); + + setColor( Q::InputText | Q::Error, m_pal.onSurface, allStates ); // same as with Hovered and Focused setColor( Q::InputText | Q::Disabled, m_pal.onSurface38 ); + + + // HintText + + setColor( Q::HintText, color( Q::InputText ) ); + setFontRole( Q::HintText, fontRole( Q::InputText ) ); + setAlignment( Q::HintText, alignment( Q::InputText ) ); + + + // TrailingIcon + + setStrutSize( Q::TrailingIcon, { 24_dp, 24_dp } ); + setMargin( Q::TrailingIcon, { 0, 0, 12_dp, 0 } ); + setGraphicRole( Q::TrailingIcon, QskMaterial3Skin::GraphicRoleOnSurface ); + const auto trailingIcon = symbol( "text_field_cancel" ); + setSymbol( Q::TrailingIcon, trailingIcon ); + + const auto errorIcon = symbol( "text_field_error" ); + setSymbol( Q::TrailingIcon | Q::Error, errorIcon, allStates ); + setGraphicRole( Q::TrailingIcon | Q::Error, QskMaterial3Skin::GraphicRoleError, allStates ); + setGraphicRole( Q::TrailingIcon | Q::Error | Q::Hovered, QskMaterial3Skin::GraphicRoleOnErrorContainer, allStates ); + + + // TrailingIconRipple + + setStrutSize( Q::TrailingIconRipple, { 45_dp, 45_dp } ); + setGradient( Q::TrailingIconRipple | Q::Hovered, m_pal.onSurface8, allStates ); + setBoxShape( Q::TrailingIconRipple, 100, Qt::RelativeSize ); + + + // SupportingText + + setMargin( Q::SupportingText, { 16_dp, 4_dp, 16_dp, 4_dp } ); + setColor( Q::SupportingText, m_pal.onSurfaceVariant ); + setColor( Q::SupportingText | Q::Error, m_pal.error, allStates ); + setFontRole( Q::SupportingText, BodySmall ); + setAlignment( Q::SupportingText, Qt::AlignLeft | Qt::AlignVCenter ); + + + // CharacterCount + + setMargin( Q::CharacterCount, margin( Q::SupportingText ) ); + setColor( Q::CharacterCount, color( Q::SupportingText ) ); + setFontRole( Q::CharacterCount, fontRole( Q::SupportingText ) ); + setAlignment( Q::CharacterCount, Qt::AlignRight | Qt::AlignVCenter ); } void Editor::setupProgressBar() @@ -1661,10 +1754,12 @@ void QskMaterial3Skin::setGraphicColor( GraphicRole role, QRgb rgb ) void QskMaterial3Skin::setupGraphicFilters( const QskMaterial3Theme& theme ) { + setGraphicColor( GraphicRoleError, theme.error ); setGraphicColor( GraphicRoleOnPrimary, theme.onPrimary ); setGraphicColor( GraphicRoleOnPrimaryContainer, theme.onPrimaryContainer ); setGraphicColor( GraphicRoleOnSecondaryContainer, theme.onSecondaryContainer ); setGraphicColor( GraphicRoleOnError, theme.onError ); + setGraphicColor( GraphicRoleOnErrorContainer, theme.onErrorContainer ); setGraphicColor( GraphicRoleOnSurface, theme.onSurface ); setGraphicColor( GraphicRoleOnSurface38, theme.onSurface38 ); setGraphicColor( GraphicRoleOnSurfaceVariant, theme.onSurfaceVariant ); diff --git a/designsystems/material3/QskMaterial3Skin.h b/designsystems/material3/QskMaterial3Skin.h index 309fd3cd..f50d9b73 100644 --- a/designsystems/material3/QskMaterial3Skin.h +++ b/designsystems/material3/QskMaterial3Skin.h @@ -114,7 +114,9 @@ class QSK_MATERIAL3_EXPORT QskMaterial3Skin : public QskSkin public: enum GraphicRole { + GraphicRoleError, GraphicRoleOnError, + GraphicRoleOnErrorContainer, GraphicRoleOnPrimary, GraphicRoleOnPrimaryContainer, GraphicRoleOnSecondaryContainer, diff --git a/designsystems/material3/icons/qvg/text_field_cancel.qvg b/designsystems/material3/icons/qvg/text_field_cancel.qvg new file mode 100644 index 0000000000000000000000000000000000000000..7607d209e923154d72a71b8f1080c90c75b4ee6d GIT binary patch literal 1247 zcmchXy-Gtt5QX=r5Nt#Uf);`xDi|V&tvKB+Y)owP5Z3nAd5Bo2PAB*VR@SL)Y^>w# z?3|s+t!&~g?(TdubLQ-ZoAKj)7kbazcLi8E$~oom5biV+<9dxIFvR`!uX*Y>mUtV3 zSazpqpO+V%miNY!Xn)l$2G1);GxjcL%rsJ~yV{#x{JhUN&gh{VWSMD?V~MoKv5Y-( zqNh60V3t~)dC#r~gWFtyvOZ%etolNA-y`svA0-f9~Es zy>b-ktlo$7Vs)w^b<{9 literal 0 HcmV?d00001 diff --git a/designsystems/material3/icons/qvg/text_field_error.qvg b/designsystems/material3/icons/qvg/text_field_error.qvg new file mode 100644 index 0000000000000000000000000000000000000000..013508ad1e5d1797e959563eab46398739a4d7cf GIT binary patch literal 927 zcmWFx_I77L1N%Qf1Yk6f$IQ&c%n-m}50znL{Qn=O2_^>PqtnF7JD_U>@o8ZZ&{%em z+2SC=K?Ovj044{)*R66p0^cBsGQ-$NatJYm8BB0>`dqdJi?4G5`OGjq4kj>JpcqsS zk{FW%R1cCEGmH(B1Dgjm2WAFPj0vs|;dW*uJur*m_QC`}w1X0a=b#4{WrT}qA&IF$ z)nOCErWYZGutNmNJea*ubAd994lp~giDA==5EBBK3`(bg^iQV@G{_J@-!K6HY$HV? literal 0 HcmV?d00001 diff --git a/designsystems/material3/icons/qvg/text_field_search.qvg b/designsystems/material3/icons/qvg/text_field_search.qvg new file mode 100644 index 0000000000000000000000000000000000000000..b289e352b383cb75bca25bd9bc273db0172073b6 GIT binary patch literal 1107 zcmchWAx}d=49D+Uu>=DNQwSJj0fr!eU#NRaW_ZD((bEv*3}zn!^fa0;fktC67|dwz z_5bejFql#CUi)kN?_HCtucuY$m|Qdfe~p-l!&SKaY%(#fR%il4-2MMGFWp9q*D*+C zd!{|Gj~vZ+U+Ry!3?Ij{r?(A7QO+OC9m|f1LoTu015AZB7~>3u zMrK)q+B&~un&EfHE)x5_=g{r|Q*D1+x+z_kWXAB3(^O{VS-eN~zjagn`&0+LLr+?- w_ef@ho`Nxa%dlgl%)}ZPow(w!#`7vEGJ$06Q~&kpKVy literal 0 HcmV?d00001 diff --git a/designsystems/material3/icons/text_field_cancel.svg b/designsystems/material3/icons/text_field_cancel.svg new file mode 100644 index 00000000..3c8de038 --- /dev/null +++ b/designsystems/material3/icons/text_field_cancel.svg @@ -0,0 +1,4 @@ + + + + diff --git a/designsystems/material3/icons/text_field_error.svg b/designsystems/material3/icons/text_field_error.svg new file mode 100644 index 00000000..3b7b117b --- /dev/null +++ b/designsystems/material3/icons/text_field_error.svg @@ -0,0 +1,4 @@ + + + + diff --git a/designsystems/material3/icons/text_field_search.svg b/designsystems/material3/icons/text_field_search.svg new file mode 100644 index 00000000..d10c97b6 --- /dev/null +++ b/designsystems/material3/icons/text_field_search.svg @@ -0,0 +1,4 @@ + + + + diff --git a/examples/gallery/inputs/InputPage.cpp b/examples/gallery/inputs/InputPage.cpp index e733edce..5b18b49a 100644 --- a/examples/gallery/inputs/InputPage.cpp +++ b/examples/gallery/inputs/InputPage.cpp @@ -67,26 +67,51 @@ namespace InputBox( QQuickItem* parent = nullptr ) : QskLinearBox( Qt::Horizontal, parent ) { - setSpacing( 20 ); + setSpacing( 40 ); + setDefaultAlignment( Qt::AlignHCenter | Qt::AlignTop ); { - new QskTextInput( "Edit Me", this ); + auto spinBox = new QskSpinBox( 0.0, 100.0, 1.0, this ); + spinBox->setSizePolicy( Qt::Horizontal, QskSizePolicy::Fixed ); + spinBox->setPageSize( 5 ); + spinBox->setValue( 35 ); } { - auto input = new QskTextInput( "Only Read Me", this ); + auto input = new QskTextInput( this ); + input->setLabelText( "filled" ); + input->setHintText( "hint text" ); + input->setSupportingText( "supporting text" ); + input->setMaxLength( 10 ); + } + + { + auto input = new QskTextInput( this ); + input->setLeadingIcon( {} ); + input->setLabelText( "no leading icon" ); + input->setHintText( "hint text" ); + input->setSupportingText( "supporting text" ); + } + auto input = new QskTextInput( this ); + input->setSkinStateFlag( QskTextInput::Error ); + input->setLabelText( "error" ); + input->setHintText( "hint text" ); + input->setSupportingText( "error text" ); + } + + { + auto input = new QskTextInput( this ); input->setReadOnly( true ); + input->setLabelText( "read only" ); input->setSizePolicy( Qt::Horizontal, QskSizePolicy::MinimumExpanding ); } { - auto input = new QskTextInput( "12345", this ); - input->setMaxLength( 5 ); - input->setEchoMode( QskTextInput::PasswordEchoOnEdit ); -#if 1 - input->setFixedWidth( 80 ); -#endif - } + auto input = new QskTextInput( this ); + input->setMaxLength( 15 ); + input->setLabelText( "password" ); + input->setEchoMode( QskTextInput::Password ); + input->setHintText( "better be strong" ); } }; } diff --git a/src/controls/QskTextInput.cpp b/src/controls/QskTextInput.cpp index 352d5ef9..3f33fdbc 100644 --- a/src/controls/QskTextInput.cpp +++ b/src/controls/QskTextInput.cpp @@ -4,6 +4,7 @@ *****************************************************************************/ #include "QskTextInput.h" +#include "QskEvent.h" #include "QskFontRole.h" #include "QskQuick.h" @@ -13,11 +14,20 @@ QSK_QT_PRIVATE_BEGIN QSK_QT_PRIVATE_END QSK_SUBCONTROL( QskTextInput, Panel ) +QSK_SUBCONTROL( QskTextInput, LeadingIcon ) +QSK_SUBCONTROL( QskTextInput, LabelText ) QSK_SUBCONTROL( QskTextInput, InputText ) +QSK_SUBCONTROL( QskTextInput, TrailingIconRipple ) +QSK_SUBCONTROL( QskTextInput, TrailingIcon ) +QSK_SUBCONTROL( QskTextInput, HintText ) +QSK_SUBCONTROL( QskTextInput, SupportingText ) +QSK_SUBCONTROL( QskTextInput, CharacterCount ) QSK_SYSTEM_STATE( QskTextInput, ReadOnly, QskAspect::FirstSystemState << 1 ) QSK_SYSTEM_STATE( QskTextInput, Editing, QskAspect::FirstSystemState << 2 ) QSK_SYSTEM_STATE( QskTextInput, Selected, QskAspect::FirstSystemState << 3 ) +QSK_SYSTEM_STATE( QskTextInput, Error, QskAspect::FirstSystemState << 4 ) +QSK_SYSTEM_STATE( QskTextInput, TextEmpty, QskAspect::LastUserState << 1 ) static inline void qskPropagateReadOnly( QskTextInput* input ) { @@ -284,7 +294,9 @@ class QskTextInput::PrivateData { public: TextInput* textInput; - QString description; // f.e. used as prompt in QskInputPanel + QString labelText; + QString hintText; + QString supportingText; unsigned int activationModes : 3; bool hasPanel : 1; @@ -318,12 +330,20 @@ QskTextInput::QskTextInput( QQuickItem* parent ) m_data->textInput->setAcceptedMouseButtons( Qt::NoButton ); initSizePolicy( QskSizePolicy::Expanding, QskSizePolicy::Fixed ); + + setSkinStateFlag( TextEmpty ); + + connect( m_data->textInput, &QQuickTextInput::textChanged, this, [this]() + { + setSkinStateFlag( TextEmpty, m_data->textInput->text().isEmpty() ); + update(); // character count might have changed + } ); } QskTextInput::QskTextInput( const QString& text, QQuickItem* parent ) : QskTextInput( parent ) { - m_data->textInput->setText( text ); + setInputText( text ); } QskTextInput::~QskTextInput() @@ -420,7 +440,14 @@ void QskTextInput::keyReleaseEvent( QKeyEvent* event ) void QskTextInput::mousePressEvent( QMouseEvent* event ) { - m_data->textInput->handleEvent( event ); + if( !isReadOnly() && subControlContentsRect( TrailingIcon ).contains( event->position() ) ) + { + setInputText( {} ); + } + else + { + m_data->textInput->handleEvent( event ); + } if ( !isReadOnly() && !qGuiApp->styleHints()->setFocusOnTouchRelease() ) setEditing( true ); @@ -489,6 +516,36 @@ void QskTextInput::focusOutEvent( QFocusEvent* event ) Inherited::focusOutEvent( event ); } +void QskTextInput::hoverEnterEvent( QHoverEvent* event ) +{ + using A = QskAspect; + + setSkinHint( TrailingIconRipple | Hovered | A::Metric | A::Position, qskHoverPosition( event ) ); + update(); + + Inherited::hoverEnterEvent( event ); +} + +void QskTextInput::hoverMoveEvent( QHoverEvent* event ) +{ + using A = QskAspect; + + setSkinHint( TrailingIconRipple | Hovered | A::Metric | A::Position, qskHoverPosition( event ) ); + update(); + + Inherited::hoverMoveEvent( event ); +} + +void QskTextInput::hoverLeaveEvent( QHoverEvent* event ) +{ + using A = QskAspect; + + setSkinHint( TrailingIconRipple | Hovered | A::Metric | A::Position, QPointF() ); + update(); + + Inherited::hoverLeaveEvent( event ); +} + QSizeF QskTextInput::layoutSizeHint( Qt::SizeHint which, const QSizeF& ) const { if ( which != Qt::PreferredSize ) @@ -506,6 +563,12 @@ QSizeF QskTextInput::layoutSizeHint( Qt::SizeHint which, const QSizeF& ) const hint = hint.expandedTo( strutSizeHint( Panel ) ); } + if( !supportingText().isEmpty() || maxLength() != 32767 ) // magic number hardcoded in qquicktextinput.cpp + { + const auto margins = marginHint( SupportingText ); + hint.rheight() += margins.top() + effectiveFontHeight( SupportingText ) + margins.bottom(); + } + return hint; } @@ -534,18 +597,56 @@ void QskTextInput::setInputText( const QString& text ) m_data->textInput->setText( text ); } -void QskTextInput::setDescription( const QString& text ) +QString QskTextInput::labelText() const { - if ( m_data->description != text ) + return m_data->labelText; +} + +void QskTextInput::setLabelText( const QString& text ) +{ + if ( m_data->labelText != text ) { - m_data->description = text; - Q_EMIT descriptionChanged( text ); + m_data->labelText = text; + Q_EMIT labelTextChanged( text ); } } -QString QskTextInput::description() const +QskGraphic QskTextInput::leadingIcon() const { - return m_data->description; + return symbolHint( LeadingIcon ); +} + +void QskTextInput::setLeadingIcon( const QskGraphic& icon ) +{ + setSymbolHint( LeadingIcon, icon ); +} + +void QskTextInput::setHintText( const QString& text ) +{ + if ( m_data->hintText != text ) + { + m_data->hintText = text; + Q_EMIT hintTextChanged( text ); + } +} + +QString QskTextInput::hintText() const +{ + return m_data->hintText; +} + +void QskTextInput::setSupportingText( const QString& text ) +{ + if ( m_data->supportingText != text ) + { + m_data->supportingText = text; + Q_EMIT supportingTextChanged( text ); + } +} + +QString QskTextInput::supportingText() const +{ + return m_data->supportingText; } QskTextInput::ActivationModes QskTextInput::activationModes() const diff --git a/src/controls/QskTextInput.h b/src/controls/QskTextInput.h index de8aa429..f4ab77f4 100644 --- a/src/controls/QskTextInput.h +++ b/src/controls/QskTextInput.h @@ -7,6 +7,7 @@ #define QSK_TEXT_INPUT_H #include "QskControl.h" +#include "QskGraphic.h" #include "QskTextOptions.h" class QValidator; @@ -18,13 +19,18 @@ class QSK_EXPORT QskTextInput : public QskControl Q_PROPERTY( QString inputText READ inputText WRITE setInputText NOTIFY inputTextChanged USER true ) - Q_PROPERTY( QString description READ description - WRITE setDescription NOTIFY descriptionChanged ) + Q_PROPERTY( QString labelText READ labelText WRITE setLabelText NOTIFY labelTextChanged ) + + Q_PROPERTY( QString hintText READ hintText + WRITE setHintText NOTIFY hintTextChanged ) + + Q_PROPERTY( QString supportingText READ supportingText + WRITE setSupportingText NOTIFY supportingTextChanged ) Q_PROPERTY( QskFontRole fontRole READ fontRole WRITE setFontRole RESET resetFontRole NOTIFY fontRoleChanged ) - Q_PROPERTY( QFont font READ font ) + Q_PROPERTY( QFont font READ font CONSTANT ) Q_PROPERTY( Qt::Alignment alignment READ alignment WRITE setAlignment RESET resetAlignment NOTIFY alignmentChanged ) @@ -55,8 +61,11 @@ class QSK_EXPORT QskTextInput : public QskControl using Inherited = QskControl; public: - QSK_SUBCONTROLS( Panel, InputText ) - QSK_STATES( ReadOnly, Editing, Selected ) + QSK_SUBCONTROLS( Panel, LeadingIcon, LabelText, InputText, + TrailingIconRipple, TrailingIcon, HintText, SupportingText, + CharacterCount ) + + QSK_STATES( ReadOnly, Editing, Selected, Error, TextEmpty ) enum ActivationMode { @@ -84,7 +93,7 @@ class QSK_EXPORT QskTextInput : public QskControl Q_ENUM( EchoMode ) QskTextInput( QQuickItem* parent = nullptr ); - QskTextInput( const QString&, QQuickItem* parent = nullptr ); + QskTextInput( const QString&, QQuickItem* parent = nullptr ); // ### do we need this constructor? ~QskTextInput() override; @@ -92,8 +101,16 @@ class QSK_EXPORT QskTextInput : public QskControl QString inputText() const; - void setDescription( const QString& ); - QString description() const; + QString labelText() const; + + QskGraphic leadingIcon() const; + void setLeadingIcon( const QskGraphic& ); + + void setHintText( const QString& ); + QString hintText() const; + + void setSupportingText( const QString& ); + QString supportingText() const; void setPanel( bool ); bool hasPanel() const; @@ -164,6 +181,8 @@ class QSK_EXPORT QskTextInput : public QskControl public Q_SLOTS: void setInputText( const QString& ); + void setLabelText( const QString& ); + void setEditing( bool ); Q_SIGNALS: @@ -174,10 +193,13 @@ class QSK_EXPORT QskTextInput : public QskControl void panelChanged( bool ); void inputTextChanged( const QString& ); + void labelTextChanged( const QString& ); + void displayTextChanged( const QString& ); void textEdited( const QString& ); - void descriptionChanged( const QString& ); + void hintTextChanged( const QString& ); + void supportingTextChanged( const QString& ); void fontRoleChanged(); void alignmentChanged(); @@ -201,6 +223,10 @@ class QSK_EXPORT QskTextInput : public QskControl void focusInEvent( QFocusEvent* ) override; void focusOutEvent( QFocusEvent* ) override; + void hoverEnterEvent( QHoverEvent* ) override; + void hoverMoveEvent( QHoverEvent* ) override; + void hoverLeaveEvent( QHoverEvent* ) override; + void mousePressEvent( QMouseEvent* ) override; void mouseMoveEvent( QMouseEvent* ) override; void mouseReleaseEvent( QMouseEvent* ) override; diff --git a/src/controls/QskTextInputSkinlet.cpp b/src/controls/QskTextInputSkinlet.cpp index 027cce92..40756b58 100644 --- a/src/controls/QskTextInputSkinlet.cpp +++ b/src/controls/QskTextInputSkinlet.cpp @@ -6,10 +6,35 @@ #include "QskTextInputSkinlet.h" #include "QskTextInput.h" +#include "QskFunctions.h" + +#include + +using Q = QskTextInput; + +namespace +{ + QString maxLengthString( const QskTextInput* input ) + { + QString s = QString::number( input->inputText().length() ) + + " / " + QString::number( input->maxLength() ); + return s; + } +} + QskTextInputSkinlet::QskTextInputSkinlet( QskSkin* skin ) : Inherited( skin ) { - setNodeRoles( { PanelRole } ); + setNodeRoles( { + PanelRole, + LeadingIconRole, + LabelTextRole, + HintTextRole, + SupportingTextRole, + CharacterCountRole, + TrailingIconRippleRole, + TrailingIconRole, + } ); } QskTextInputSkinlet::~QskTextInputSkinlet() @@ -19,13 +44,158 @@ QskTextInputSkinlet::~QskTextInputSkinlet() QRectF QskTextInputSkinlet::subControlRect( const QskSkinnable* skinnable, const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const { - if ( subControl == QskTextInput::Panel ) + const auto input = static_cast< const Q* >( skinnable ); + + if ( subControl == Q::Panel ) { - return contentsRect; + auto rect = contentsRect; + + const auto h = input->strutSizeHint( subControl ).height(); + rect.setHeight( h ); + + return rect; } - else if ( subControl == QskTextInput::InputText ) + else if ( subControl == Q::LeadingIcon ) { - return skinnable->subControlContentsRect( contentsRect, QskTextInput::Panel ); + if( input->symbolHint( subControl ).isEmpty() ) + { + return {}; + } + else + { + const auto margins = input->marginHint( subControl ); + const auto panelRect = input->subControlRect( Q::Panel ); + auto rect = panelRect.marginsRemoved( margins ); + + const auto size = input->strutSizeHint( subControl ); + rect.setSize( size ); + rect.moveCenter( { rect.center().x(), panelRect.center().y() } ); + + return rect; + } + } + else if ( subControl == Q::LabelText ) + { + const auto inputRect = input->subControlRect( Q::InputText ); + + if( input->hasSkinState( Q::Focused ) || !input->inputText().isEmpty() ) + { + const auto margins = input->marginHint( subControl ); + auto rect = inputRect; + rect.setY( contentsRect.y() + margins.top() ); + const QFontMetricsF fm ( input->effectiveFont( subControl ) ); + rect.setHeight( fm.height() ); + return rect; + } + else + { + return inputRect; + } + } + else if ( subControl == Q::InputText ) + { + const auto margins = input->marginHint( subControl ); + + const auto leadingIconRect = input->subControlRect( Q::LeadingIcon ); + const auto panelRect = input->subControlRect( Q::Panel ); + auto rect = panelRect; + rect.setLeft( leadingIconRect.right() ); + rect.setRight( contentsRect.right() ); // ### space for trailing icon + rect = rect.marginsRemoved( margins ); + + return rect; + } + else if ( subControl == Q::HintText ) + { + if( input->hasSkinState( Q::Focused ) && input->inputText().isEmpty() ) // ### has TextEmpty state + { + return input->subControlRect( Q::InputText ); + } + else + { + return {}; + } + } + else if ( subControl == Q::SupportingText ) + { + if( input->supportingText().isEmpty() ) + { + return {}; + } + else + { + auto rect = contentsRect; + + const auto margins = input->marginHint( subControl ); + const auto h = margins.top() + input->effectiveFontHeight( subControl ) + margins.bottom(); + rect.setTop( rect.bottom() - h ); + + rect.setLeft( rect.left() + margins.left() ); + + return rect; + } + } + else if ( subControl == Q::CharacterCount ) + { + if( input->maxLength() == 32767 ) // magic number hardcoded in qquicktextinput.cpp + { + return {}; + } + else + { + auto rect = contentsRect; + + const auto margins = input->marginHint( subControl ); + const auto h = margins.top() + input->effectiveFontHeight( subControl ) + margins.bottom(); + rect.setTop( rect.bottom() - h ); + + const QFontMetricsF fm( input->effectiveFont( subControl ) ); + const auto w = qskHorizontalAdvance( fm, maxLengthString( input ) ); + rect.setRight( rect.right() - margins.right() ); + rect.setLeft( rect.right() - ( margins.left() + w + margins.right() ) ); + + return rect; + } + } + else if ( subControl == Q::TrailingIconRipple ) + { + const auto cursorPos = input->effectiveSkinHint( + Q::TrailingIconRipple | Q::Hovered | QskAspect::Metric | QskAspect::Position ).toPointF(); + const auto trailingIconRect = input->subControlRect( Q::TrailingIcon ); + + if( !cursorPos.isNull() && trailingIconRect.contains( cursorPos ) ) + { + const auto size = input->strutSizeHint( subControl ); + QRectF rect( { 0, 0 }, size ); + + rect.moveCenter( trailingIconRect.center() ); + + return rect; + } + else + { + return {}; + } + } + else if ( subControl == Q::TrailingIcon ) + { + if( input->symbolHint( subControl ).isEmpty() ) + { + return {}; + } + else + { + const auto margins = input->marginHint( subControl ); + const auto panelRect = input->subControlRect( Q::Panel ); + auto rect = panelRect.marginsRemoved( margins ); + + const auto size = input->strutSizeHint( subControl ); + rect.setHeight( size.height() ); + rect.moveCenter( { rect.center().x(), panelRect.center().y() } ); + rect.setLeft( rect.right() - size.width() ); + + return rect; + } } return Inherited::subControlRect( skinnable, contentsRect, subControl ); @@ -34,15 +204,51 @@ QRectF QskTextInputSkinlet::subControlRect( const QskSkinnable* skinnable, QSGNode* QskTextInputSkinlet::updateSubNode( const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const { + const auto input = static_cast< const Q* >( skinnable ); + switch ( nodeRole ) { case PanelRole: { - const auto input = static_cast< const QskTextInput* >( skinnable ); if ( !input->hasPanel() ) return nullptr; - return updateBoxNode( skinnable, node, QskTextInput::Panel ); + return updateBoxNode( skinnable, node, Q::Panel ); + } + + case LeadingIconRole: + { + return updateSymbolNode( skinnable, node, Q::LeadingIcon ); + } + + case LabelTextRole: + { + return updateTextNode( skinnable, node, input->labelText(), Q::LabelText ); + } + + case HintTextRole: + { + return updateTextNode( skinnable, node, input->hintText(), Q::HintText ); + } + + case SupportingTextRole: + { + return updateTextNode( skinnable, node, input->supportingText(), Q::SupportingText ); + } + + case CharacterCountRole: + { + return updateTextNode( skinnable, node, maxLengthString( input ), Q::CharacterCount ); + } + + case TrailingIconRippleRole: + { + return updateBoxNode( skinnable, node, Q::TrailingIconRipple ); + } + + case TrailingIconRole: + { + return updateSymbolNode( skinnable, node, Q::TrailingIcon ); } } diff --git a/src/controls/QskTextInputSkinlet.h b/src/controls/QskTextInputSkinlet.h index 946c6d32..904bb05c 100644 --- a/src/controls/QskTextInputSkinlet.h +++ b/src/controls/QskTextInputSkinlet.h @@ -18,6 +18,13 @@ class QSK_EXPORT QskTextInputSkinlet : public QskSkinlet enum NodeRole { PanelRole, + LeadingIconRole, + LabelTextRole, + HintTextRole, + SupportingTextRole, + TrailingIconRippleRole, + TrailingIconRole, + CharacterCountRole, RoleCount };