text input: Add M3 outlined mode

This commit is contained in:
Peter Hartmann 2024-10-07 14:46:55 +02:00
parent 0208573325
commit 59a42395b2
6 changed files with 231 additions and 54 deletions

View File

@ -451,12 +451,16 @@ void Editor::setupTextLabel()
void Editor::setupTextInput()
{
using Q = QskTextInput;
using M3 = QskMaterial3Skin;
const QskStateCombination allStates( QskStateCombination::CombinationNoState, QskAspect::AllStates );
// Panel
setStrutSize( Q::Panel, -1.0, 56_dp );
// Panel - Filled
setGradient( Q::Panel, m_pal.surfaceVariant );
setBoxShape( Q::Panel, m_pal.shapeExtraSmallTop );
@ -484,6 +488,16 @@ void Editor::setupTextInput()
setGradient( Q::Panel | Q::Disabled, disabledPanelColor, allStates );
setBoxBorderColors( Q::Panel | Q::Disabled, m_pal.onSurface38, allStates );
// Panel - Outlined
setGradient( Q::Panel | M3::Outlined, Qt::transparent );
setBoxShape( Q::Panel | M3::Outlined, m_pal.shapeExtraSmall );
setBoxBorderMetrics( Q::Panel | M3::Outlined, 1_dp );
setBoxBorderColors( Q::Panel | M3::Outlined, m_pal.outline );
setBoxBorderMetrics( Q::Panel | M3::Outlined | Q::Focused, 2_dp, allStates );
setBoxBorderColors( Q::Panel | M3::Outlined | Q::Focused, m_pal.primary, allStates );
// LeadingIcon
@ -492,18 +506,19 @@ void Editor::setupTextInput()
const auto leadingIcon = symbol( "text_field_search" );
setSymbol( Q::LeadingIcon, leadingIcon );
setGraphicRole( Q::LeadingIcon, QskMaterial3Skin::GraphicRoleOnSurface );
setGraphicRole( Q::LeadingIcon | Q::Error, QskMaterial3Skin::GraphicRoleOnSurfaceVariant, allStates );
setGraphicRole( Q::LeadingIcon, M3::GraphicRoleOnSurface );
setGraphicRole( Q::LeadingIcon | Q::Error, M3::GraphicRoleOnSurfaceVariant, allStates );
// LabelText
setAlignment( Q::LabelText, Qt::AlignLeft | Qt::AlignVCenter );
setAlignment( Q::LabelText, Qt::AlignLeft | Qt::AlignTop );
const QskStateCombination textEmptyStates( QskStateCombination::CombinationNoState,
Q::Focused | Q::Hovered | Q::ReadOnly | Q::Disabled );
Q::Hovered | Q::ReadOnly | Q::Disabled | Q::Error );
setFontRole( Q::LabelText | Q::TextEmpty, BodyMedium, textEmptyStates );
setAlignment( Q::LabelText | Q::TextEmpty, Qt::AlignLeft | Qt::AlignVCenter, textEmptyStates );
setFontRole( Q::LabelText | Q::TextEmpty, BodyLarge, textEmptyStates );
setColor( Q::LabelText | Q::TextEmpty, m_pal.onSurfaceVariant, textEmptyStates );
const QskStateCombination editingHoveredFocused( QskStateCombination::CombinationNoState,
@ -516,38 +531,47 @@ void Editor::setupTextInput()
setColor( Q::LabelText | Q::Error, m_pal.error, allStates );
setColor( Q::LabelText | Q::Error | Q::Hovered, m_pal.onErrorContainer, allStates );
// LabelText - Outlined
setMargin( Q::LabelText | M3::Outlined, { 4_dp, 0, 4_dp, 0 }, editingHoveredFocused );
// InputText
setMargin( Q::InputText, { 16_dp, 8_dp, 16_dp, 8_dp } );
setColor( Q::InputText, m_pal.onSurface );
setFontRole( Q::InputText, BodyMedium );
setFontRole( Q::InputText, BodyLarge );
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 );
// InputText - Outlined
setAlignment( Q::InputText | M3::Outlined, Qt::AlignLeft | Qt::AlignVCenter );
// HintText
setColor( Q::HintText, color( Q::InputText ) );
setFontRole( Q::HintText, fontRole( Q::InputText ) );
setAlignment( Q::HintText, alignment( Q::InputText ) );
setAlignment( Q::HintText | M3::Outlined, alignment( Q::InputText | M3::Outlined ) );
// TrailingIcon
setStrutSize( Q::TrailingIcon, { 24_dp, 24_dp } );
setMargin( Q::TrailingIcon, { 0, 0, 12_dp, 0 } );
setGraphicRole( Q::TrailingIcon, QskMaterial3Skin::GraphicRoleOnSurface );
setGraphicRole( Q::TrailingIcon, M3::GraphicRoleOnSurfaceVariant );
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 );
setGraphicRole( Q::TrailingIcon | Q::Error, M3::GraphicRoleError, allStates );
setGraphicRole( Q::TrailingIcon | Q::Error | Q::Hovered, M3::GraphicRoleOnErrorContainer, allStates );
// TrailingIconRipple
@ -1654,6 +1678,7 @@ QskMaterial3Theme::QskMaterial3Theme( QskSkin::ColorScheme colorScheme,
elevation2 = QskShadowMetrics( -2, 8, { 0, 2 } );
elevation3 = QskShadowMetrics( -1, 11, { 0, 2 } );
shapeExtraSmall = QskBoxShapeMetrics( 4_dp, 4_dp, 4_dp, 4_dp );
shapeExtraSmallTop = QskBoxShapeMetrics( 4_dp, 4_dp, 0, 0 );
}

View File

@ -102,6 +102,7 @@ class QSK_MATERIAL3_EXPORT QskMaterial3Theme
qreal stateOpacity( int state ) const;
QskBoxShapeMetrics shapeExtraSmall;
QskBoxShapeMetrics shapeExtraSmallTop;
};

View File

@ -61,57 +61,70 @@ namespace
}
};
class InputBox : public QskLinearBox
class TextInputBox : public QskLinearBox
{
public:
InputBox( QQuickItem* parent = nullptr )
TextInputBox( QQuickItem* parent = nullptr )
: QskLinearBox( Qt::Horizontal, parent )
{
setSpacing( 40 );
setSpacing( 25 );
setDefaultAlignment( Qt::AlignHCenter | Qt::AlignTop );
{
auto spinBox = new QskSpinBox( 0.0, 100.0, 1.0, this );
spinBox->setSizePolicy( Qt::Horizontal, QskSizePolicy::Fixed );
spinBox->setPageSize( 5 );
spinBox->setValue( 35 );
}
for( const auto& emphasis : { QskTextInput::NoEmphasis, QskTextInput::LowEmphasis } )
{
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->setEmphasis( emphasis );
const QString text = ( emphasis == QskTextInput::NoEmphasis ) ? "filled" : "outlined";
input->setLabelText( text );
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->setEmphasis( emphasis );
input->setLeadingIcon( {} );
input->setLabelText( "no leading icon" );
input->setHintText( "hint text" );
input->setSupportingText( "supporting text" );
}
{
auto input = new QskTextInput( this );
input->setEmphasis( emphasis );
input->setLeadingIcon( {} );
input->setLabelText( "no hint text" );
}
{
auto input = new QskTextInput( this );
input->setReadOnly( true );
input->setLabelText( "read only" );
input->setSizePolicy( Qt::Horizontal, QskSizePolicy::MinimumExpanding );
}
{
auto input = new QskTextInput( this );
input->setEmphasis( emphasis );
input->setSkinStateFlag( QskTextInput::Error );
input->setLabelText( "error" );
input->setHintText( "hint text" );
input->setSupportingText( "error text" );
}
{
auto input = new QskTextInput( this );
input->setMaxLength( 15 );
input->setLabelText( "password" );
input->setEchoMode( QskTextInput::Password );
input->setHintText( "better be strong" );
{
auto input = new QskTextInput( this );
input->setEmphasis( emphasis );
input->setReadOnly( true );
input->setLabelText( "read only" );
input->setSizePolicy( Qt::Horizontal, QskSizePolicy::MinimumExpanding );
}
{
auto input = new QskTextInput( this );
input->setEmphasis( emphasis );
input->setMaxLength( 15 );
input->setLabelText( "password" );
input->setEchoMode( QskTextInput::Password );
input->setHintText( "better be strong" );
}
}
}
};
}
@ -136,12 +149,17 @@ InputPage::InputPage( QQuickItem* parent )
auto spinBox = new QskSpinBox( 0.0, 100.0, 1.0 );
spinBox->setSizePolicy( Qt::Horizontal, QskSizePolicy::Fixed );
auto inputBox = new InputBox();
auto textInputBox = new TextInputBox();
inputBox->setSizePolicy( Qt::Vertical, QskSizePolicy::Fixed );
auto vBox = new QskLinearBox( Qt::Vertical );
vBox->setSpacing( 30 );
vBox->setExtraSpacingAt( Qt::RightEdge | Qt::BottomEdge );
auto spinBox = new QskSpinBox( 0.0, 100.0, 1.0 );
spinBox->setSizePolicy( Qt::Horizontal, QskSizePolicy::Fixed );
spinBox->setPageSize( 5 );
spinBox->setValue( 35 );
vBox->addItem( sliders[0].continous );
vBox->addItem( sliders[0].discrete );

View File

@ -293,6 +293,11 @@ namespace
class QskTextInput::PrivateData
{
public:
PrivateData()
: emphasis( NoEmphasis )
{
}
TextInput* textInput;
QString labelText;
QString hintText;
@ -300,6 +305,7 @@ class QskTextInput::PrivateData
unsigned int activationModes : 3;
bool hasPanel : 1;
int emphasis : 4;
};
QskTextInput::QskTextInput( QQuickItem* parent )
@ -563,6 +569,12 @@ QSizeF QskTextInput::layoutSizeHint( Qt::SizeHint which, const QSizeF& ) const
hint = hint.expandedTo( strutSizeHint( Panel ) );
}
if( emphasis() == LowEmphasis )
{
const auto fontHeight = effectiveFontHeight( LabelText | TextEmpty );
hint.rheight() += fontHeight / 2;
}
if( !supportingText().isEmpty() || maxLength() != 32767 ) // magic number hardcoded in qquicktextinput.cpp
{
const auto margins = marginHint( SupportingText );
@ -587,6 +599,24 @@ void QskTextInput::updateNode( QSGNode* node )
Inherited::updateNode( node );
}
void QskTextInput::setEmphasis( Emphasis emphasis )
{
if ( emphasis != m_data->emphasis )
{
m_data->emphasis = emphasis;
resetImplicitSize();
update();
Q_EMIT emphasisChanged( emphasis );
}
}
QskTextInput::Emphasis QskTextInput::emphasis() const
{
return static_cast< Emphasis >( m_data->emphasis );
}
QString QskTextInput::inputText() const
{
return m_data->textInput->text();
@ -798,6 +828,18 @@ void QskTextInput::ensureVisible( int position )
m_data->textInput->ensureVisible( position );
}
QskAspect::Variation QskTextInput::effectiveVariation() const
{
switch( m_data->emphasis )
{
case LowEmphasis:
return QskAspect::Small;
default:
return QskAspect::NoVariation;
}
}
int QskTextInput::cursorPosition() const
{
return m_data->textInput->cursorPosition();

View File

@ -58,6 +58,9 @@ class QSK_EXPORT QskTextInput : public QskControl
Q_PROPERTY( bool panel READ hasPanel
WRITE setPanel NOTIFY panelChanged )
Q_PROPERTY( Emphasis emphasis READ emphasis
WRITE setEmphasis NOTIFY emphasisChanged )
using Inherited = QskControl;
public:
@ -67,6 +70,13 @@ class QSK_EXPORT QskTextInput : public QskControl
QSK_STATES( ReadOnly, Editing, Selected, Error, TextEmpty )
enum Emphasis
{
LowEmphasis = -1,
NoEmphasis = 0,
};
Q_ENUM( Emphasis )
enum ActivationMode
{
NoActivation,
@ -99,6 +109,9 @@ class QSK_EXPORT QskTextInput : public QskControl
void setupFrom( const QQuickItem* );
void setEmphasis( Emphasis );
Emphasis emphasis() const;
QString inputText() const;
QString labelText() const;
@ -179,6 +192,8 @@ class QSK_EXPORT QskTextInput : public QskControl
void ensureVisible( int position );
QskAspect::Variation effectiveVariation() const override;
public Q_SLOTS:
void setInputText( const QString& );
void setLabelText( const QString& );
@ -186,6 +201,8 @@ class QSK_EXPORT QskTextInput : public QskControl
void setEditing( bool );
Q_SIGNALS:
void emphasisChanged( Emphasis );
void editingChanged( bool );
void activationModesChanged();

View File

@ -6,6 +6,9 @@
#include "QskTextInputSkinlet.h"
#include "QskTextInput.h"
#include "QskBoxBorderColors.h"
#include "QskBoxBorderMetrics.h"
#include "QskBoxShapeMetrics.h"
#include "QskFunctions.h"
#include <QFontMetricsF>
@ -20,6 +23,39 @@ namespace
+ " / " + QString::number( input->maxLength() );
return s;
}
// We need to "cut a hole" in the upper gradient for the label text:
QskBoxBorderColors outlineColors( const QskTextInput* input )
{
auto borderColors = input->boxBorderColorsHint( Q::Panel );
auto topGradient = borderColors.gradientAt( Qt::TopEdge );
const auto panelRect = input->subControlRect( Q::Panel );
const auto margins = input->marginHint( Q::LabelText );
const auto iconMargins = input->marginHint( Q::LeadingIcon );
const auto x1 = iconMargins.left() - margins.left();
const auto r1 = x1 / panelRect.width();
const auto w = qskHorizontalAdvance( input->effectiveFont( Q::LabelText ), input->labelText() );
const auto x2 = x1 + w + margins.right();
const auto r2 = x2 / panelRect.width();
topGradient.setStops( {
{ 0.0, topGradient.startColor() },
{ r1, topGradient.startColor() },
{ r1, Qt::transparent },
{ r2, Qt::transparent },
{ r2, topGradient.startColor() },
{ 1.0, topGradient.startColor() }
} );
borderColors.setGradientAt( Qt::TopEdge, topGradient );
return borderColors;
}
}
QskTextInputSkinlet::QskTextInputSkinlet( QskSkin* skin )
@ -50,6 +86,13 @@ QRectF QskTextInputSkinlet::subControlRect( const QskSkinnable* skinnable,
{
auto rect = contentsRect;
if( input->emphasis() == Q::LowEmphasis )
{
const auto fontHeight = input->effectiveFontHeight( Q::LabelText | Q::Focused );
rect.setY( fontHeight / 2 );
}
const auto h = input->strutSizeHint( subControl ).height();
rect.setHeight( h );
@ -78,13 +121,27 @@ QRectF QskTextInputSkinlet::subControlRect( const QskSkinnable* skinnable,
{
const auto inputRect = input->subControlRect( Q::InputText );
if( input->hasSkinState( Q::Focused ) || !input->inputText().isEmpty() )
if( !input->inputText().isEmpty()
|| input->hasSkinState( Q::Focused )
|| input->hasSkinState( Q::Editing ) )
{
const auto margins = input->marginHint( subControl );
auto rect = inputRect;
rect.setY( contentsRect.y() + margins.top() );
const QFontMetricsF fm ( input->effectiveFont( subControl ) );
const QFontMetricsF fm( input->effectiveFont( subControl ) );
if( input->emphasis() == Q::LowEmphasis )
{
const auto iconMargins = input->marginHint( Q::LeadingIcon );
rect.setX( iconMargins.left() );
rect.setY( 0 );
}
else
{
rect.setY( contentsRect.y() + margins.top() );
}
rect.setHeight( fm.height() );
return rect;
}
else
@ -107,7 +164,8 @@ QRectF QskTextInputSkinlet::subControlRect( const QskSkinnable* skinnable,
}
else if ( subControl == Q::HintText )
{
if( input->hasSkinState( Q::Focused ) && input->inputText().isEmpty() ) // ### has TextEmpty state
if( input->hasSkinState( Q::TextEmpty )
&& ( input->hasSkinState( Q::Focused ) || input->hasSkinState( Q::Editing ) ) )
{
return input->subControlRect( Q::InputText );
}
@ -213,7 +271,23 @@ QSGNode* QskTextInputSkinlet::updateSubNode(
if ( !input->hasPanel() )
return nullptr;
return updateBoxNode( skinnable, node, Q::Panel );
if( input->emphasis() == Q::LowEmphasis
&& ( !input->hasSkinState( Q::TextEmpty )
|| input->hasSkinState( Q::Focused )
|| input->hasSkinState( Q::Editing ) ) )
{
const auto shape = skinnable->boxShapeHint( Q::Panel );
const auto borderMetrics = skinnable->boxBorderMetricsHint( Q::Panel );
const auto borderColors = outlineColors( input );
const auto gradient = input->gradientHint( Q::Panel );
return updateBoxNode( skinnable, node, input->subControlRect( Q::Panel ),
shape, borderMetrics, borderColors, gradient );
}
else
{
return updateBoxNode( skinnable, node, Q::Panel );
}
}
case LeadingIconRole: