QskTextField wip

This commit is contained in:
Uwe Rathmann 2025-03-10 15:01:29 +01:00
parent 04fbb2a2a1
commit edad453505
15 changed files with 348 additions and 406 deletions

View File

@ -7,15 +7,10 @@ list(APPEND HEADERS
QskFluent2Global.h QskFluent2Theme.h QskFluent2SkinFactory.h
)
list(APPEND PRIVATE_HEADERS
QskFluent2TextFieldSkinlet.h
)
list(APPEND SOURCES
QskFluent2Theme.cpp
QskFluent2Skin.cpp
QskFluent2SkinFactory.cpp
QskFluent2TextFieldSkinlet.cpp
)
qt_add_resources(SOURCES QskFluent2Icons.qrc)

View File

@ -47,7 +47,6 @@
*/
#include "QskFluent2Skin.h"
#include "QskFluent2Theme.h"
#include "QskFluent2TextFieldSkinlet.h"
#include <QskTextAreaSkinlet.h>
#include <QskSkinHintTableEditor.h>
@ -1854,11 +1853,13 @@ void Editor::setupTextAreaColors(
setBoxBorderGradient( panel, borderColor1, borderColor2, panelColor );
}
}
void Editor::setupTextFieldMetrics()
{
using Q = QskTextField;
setFontRole( Q::Text, Fluent2::Body );
setFontRole( Q::Header, Fluent2::Body );
setFontRole( Q::Footer, Fluent2::Caption );
setStrutSize( Q::TextPanel, { -1, 30_px } );
setPadding( Q::TextPanel, { 11_px, 0, 11_px, 0 } );
@ -1869,15 +1870,14 @@ void Editor::setupTextFieldMetrics()
setBoxShape( Q::TextPanel, 3_px );
setAlignment( Q::Placeholder, Qt::AlignLeft | Qt::AlignVCenter );
setFontRole( Q::Placeholder, fontRole( Q::Text ) );
setStrutSize( Q::Header, { -1, 30_px } );
setFontRole( Q::Header, Fluent2::Body );
setAlignment( Q::Text, Qt::AlignLeft | Qt::AlignVCenter );
setFontRole( Q::Text, Fluent2::Body );
for ( const auto subControl : { Q::Text, Q::Placeholder } )
{
setFontRole( subControl, Fluent2::Body );
setAlignment( subControl, Qt::AlignLeft | Qt::AlignVCenter );
}
//setStrutSize( Q::Header, { -1, 30_px } );
//setStrutSize( Q::Footer, { -1, 30_px } );
setSymbol( Q::Icon, symbol( "search" ) );
setSymbol( Q::Button, symbol( "dismiss" ) );
@ -2141,8 +2141,6 @@ void Editor::setupVirtualKeyboardColors(
QskFluent2Skin::QskFluent2Skin( QObject* parent )
: Inherited( parent )
{
declareSkinlet< QskTextField, QskFluent2TextFieldSkinlet >();
setupFonts();
Editor editor( &hintTable() );

View File

@ -1,60 +0,0 @@
/******************************************************************************
* QSkinny - Copyright (C) The authors
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#include "QskFluent2TextFieldSkinlet.h"
#include "QskTextField.h"
using Q = QskTextField;
QskFluent2TextFieldSkinlet::QskFluent2TextFieldSkinlet( QskSkin* skin )
: Inherited( skin )
{
}
QskFluent2TextFieldSkinlet::~QskFluent2TextFieldSkinlet()
{
}
QRectF QskFluent2TextFieldSkinlet::subControlRect( const QskSkinnable* skinnable,
const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const
{
if ( subControl == Q::TextPanel )
{
auto rect = subControlRect( skinnable, contentsRect, Q::Panel );
rect.setY( rect.bottom() - skinnable->strutSizeHint( subControl ).height() );
return rect;
}
if ( subControl == Q::Header )
{
const auto rect = subControlRect( skinnable, contentsRect, Q::TextPanel );
const auto h = skinnable->effectiveFontHeight( Q::Header );
return QRectF( rect.x(), rect.y() - h, rect.width(), h );
}
return Inherited::subControlRect( skinnable, contentsRect, subControl );
}
QSizeF QskFluent2TextFieldSkinlet::sizeHint( const QskSkinnable* skinnable,
Qt::SizeHint which, const QSizeF& constraint ) const
{
if ( which != Qt::PreferredSize )
return QSizeF();
auto hint = Inherited::sizeHint( skinnable, which, constraint );
const auto textField = static_cast< const QskTextField* >( skinnable );
if ( !textField->headerText().isEmpty() )
{
// spacing ???
hint.rheight() += textField->strutSizeHint( Q::Header ).height();
}
return hint;
}
#include "moc_QskFluent2TextFieldSkinlet.cpp"

View File

@ -1,29 +0,0 @@
/******************************************************************************
* QSkinny - Copyright (C) The authors
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#ifndef QSK_FLUENT2_TEXTFIELD_SKINLET_H
#define QSK_FLUENT2_TEXTFIELD_SKINLET_H
#include "QskFluent2Global.h"
#include "QskTextFieldSkinlet.h"
class QSK_FLUENT2_EXPORT QskFluent2TextFieldSkinlet : public QskTextFieldSkinlet
{
Q_GADGET
using Inherited = QskTextFieldSkinlet;
public:
Q_INVOKABLE QskFluent2TextFieldSkinlet( QskSkin* = nullptr );
~QskFluent2TextFieldSkinlet() override;
QRectF subControlRect( const QskSkinnable*,
const QRectF& rect, QskAspect::Subcontrol ) const override;
QSizeF sizeHint( const QskSkinnable*,
Qt::SizeHint, const QSizeF& ) const override;
};
#endif

View File

@ -425,6 +425,9 @@ void Editor::setupTextField()
setBoxShape( Q::TextPanel, 2_px );
setPadding( Q::TextPanel, 4_px );
setFontRole( Q::Header, QskFontRole::Body );
setFontRole( Q::Footer, QskFontRole::Caption );
}
void Editor::setupTextArea()

View File

@ -489,66 +489,55 @@ void Editor::setupTextField()
const auto activeStates = Q::Focused | Q::Editing;
{
// Text
// TextPanel
setAnimation( Q::TextPanel | A::Color, qskDuration );
setAnimation( Q::TextPanel | A::Metric, qskDuration );
}
for ( const auto variation : { A::NoVariation, Filled, Outlined } )
{
const auto Panel = Q::Panel | variation;
const auto aspect = Q::TextPanel | Filled;
QskBoxBorderMetrics borderMetrics[2];
setBoxBorderColors( aspect, m_pal.onSurfaceVariant );
setGradient( aspect, m_pal.surfaceVariant );
if ( variation == Filled )
{
setBoxShape( Panel, m_pal.shapeExtraSmallTop );
setGradient( aspect | Q::Hovered,
m_pal.hoverColor( m_pal.onSurfaceVariant, m_pal.surfaceVariant ),
{ QskStateCombination::CombinationNoState, activeStates | Q::Error } );
borderMetrics[0].setBottom( 1 );
borderMetrics[1].setBottom( 2 );
setGradient( aspect | Q::Disabled,
QskRgb::toTransparentF( m_pal.onSurface, 0.04 ) );
setBoxBorderColors( Panel, m_pal.onSurfaceVariant );
setGradient( Panel, m_pal.surfaceVariant );
setGradient( Panel | Q::Hovered,
m_pal.hoverColor( m_pal.onSurfaceVariant, m_pal.surfaceVariant ),
{ QskStateCombination::CombinationNoState, activeStates | Q::Error } );
setGradient( Panel | Q::Disabled,
QskRgb::toTransparentF( m_pal.onSurface, 0.04 ) );
}
else
{
setBoxShape( Panel, m_pal.shapeExtraSmall );
borderMetrics[0].setWidths( 1 );
borderMetrics[1].setWidths( 2 );
setBoxBorderColors( Panel, m_pal.outline );
}
if ( variation != A::NoVariation )
{
setStrutSize( Panel, -1.0, 56_px );
setPadding( Panel, 16_px, 8_px, 16_px, 8_px );
}
setBoxBorderMetrics( Panel, borderMetrics[0] );
setBoxBorderMetrics( Panel, borderMetrics[1], activeStates | Q::Hovered );
setBoxBorderMetrics( Panel | Q::Error, borderMetrics[1], activeStates | Q::Hovered );
setBoxBorderColors( Panel, m_pal.primary, activeStates );
setBoxBorderColors( Panel | Q::Hovered, m_pal.primary, activeStates );
setBoxBorderColors( Panel | Q::Hovered, m_pal.onSurface );
setBoxBorderColors( Panel | Q::Disabled, m_pal.onSurface38 );
setBoxBorderColors( Panel | Q::Error, m_pal.error,
{ QskStateCombination::CombinationNoState, activeStates | Q::Hovered } );
setColor( Q::TextPanel | variation | Q::Selected, m_pal.primary12 );
setBoxShape( aspect, m_pal.shapeExtraSmallTop );
setBoxBorderMetrics( aspect, { 0, 0, 0, 1 } );
setBoxBorderMetrics( aspect, { 0, 0, 0, 2 }, activeStates | Q::Hovered );
setBoxBorderMetrics( aspect | Q::Error, { 0, 0, 0, 2 }, activeStates | Q::Hovered );
}
{
const auto aspect = Q::TextPanel | Outlined;
setBoxBorderColors( aspect, m_pal.outline );
setBoxShape( aspect, m_pal.shapeExtraSmall );
setBoxBorderMetrics( aspect, 1 );
setBoxBorderMetrics( aspect, 2, activeStates | Q::Hovered );
setBoxBorderMetrics( aspect | Q::Error, 2, activeStates | Q::Hovered );
setGradient( aspect, QColor() );
}
setStrutSize( Q::TextPanel, -1.0, 56_px );
setPadding( Q::TextPanel, 16_px, 8_px, 16_px, 8_px );
setBoxBorderColors( Q::TextPanel, m_pal.primary, activeStates );
setBoxBorderColors( Q::TextPanel | Q::Hovered, m_pal.primary, activeStates );
setBoxBorderColors( Q::TextPanel | Q::Hovered, m_pal.onSurface );
setBoxBorderColors( Q::TextPanel | Q::Disabled, m_pal.onSurface38 );
setBoxBorderColors( Q::TextPanel | Q::Error, m_pal.error,
{ QskStateCombination::CombinationNoState, activeStates | Q::Hovered } );
setColor( Q::TextPanel | Q::Selected, m_pal.primary12 );
// Icon
@ -561,39 +550,6 @@ void Editor::setupTextField()
setGraphicRole( Q::Icon | Q::Disabled, M3::GraphicRoleOnSurface38 );
{
setAlignment( Q::Header, Qt::AlignLeft | Qt::AlignVCenter );
setFontRole( Q::Header, BodySmall );
setColor( Q::Header, m_pal.onSurfaceVariant );
setColor( Q::Header, m_pal.primary, activeStates );
setColor( Q::Header | Q::Error, m_pal.error );
setColor( Q::Header | Q::Disabled, m_pal.onSurface38 );
}
#if 0
setMargin( Q::Header | Outlined, 4_px, 0, 4_px, 0 );
#endif
for ( const auto subControl : { Q::Text, Q::Placeholder } )
{
setAlignment( subControl, Qt::AlignLeft | Qt::AlignVCenter );
setFontRole( subControl, BodyLarge );
setColor( subControl | Q::Disabled, m_pal.onSurface38 );
if ( subControl == Q::Text )
{
setColor( subControl, m_pal.onSurface );
}
else
{
setColor( subControl | Q::Error, m_pal.error );
setColor( subControl | Q::Error | Q::Hovered, m_pal.onSurface );
}
}
// Button
setStrutSize( Q::Button, { 24_px, 24_px } );
@ -613,24 +569,48 @@ void Editor::setupTextField()
setGradient( Q::ButtonPanel | Q::Hovered, m_pal.onSurface8 );
setBoxShape( Q::ButtonPanel, 100, Qt::RelativeSize );
// Header
// SupportingText
setAlignment( Q::Header, Qt::AlignLeft | Qt::AlignVCenter );
setFontRole( Q::Header, BodySmall );
setColor( Q::Header, m_pal.onSurfaceVariant );
setColor( Q::Header, m_pal.primary, activeStates );
setColor( Q::Header | Q::Error, m_pal.error );
setColor( Q::Header | Q::Disabled, m_pal.onSurface38 );
for ( const auto subControl : { Q::Text, Q::Placeholder } )
{
setAlignment( subControl, Qt::AlignLeft | Qt::AlignVCenter );
setFontRole( subControl, BodyLarge );
setColor( subControl | Q::Disabled, m_pal.onSurface38 );
if ( subControl == Q::Text )
{
setColor( subControl, m_pal.onSurface );
}
else
{
setColor( subControl | Q::Error, m_pal.error );
setColor( subControl | Q::Error | Q::Hovered, m_pal.onSurface );
}
}
// Footer, CharacterCount
for ( const auto subControl : { Q::Footer, Q::CharacterCount } )
{
setMargin( subControl, { 16_px, 4_px, 16_px, 4_px } );
setFontRole( subControl, BodySmall );
setColor( subControl, m_pal.onSurfaceVariant );
setColor( subControl | Q::Error, m_pal.error );
setColor( subControl | Q::Disabled, m_pal.onSurface38 );
}
setMargin( Q::Footer, { 16_px, 4_px, 16_px, 4_px } );
setColor( Q::Footer, m_pal.onSurfaceVariant );
setColor( Q::Footer | Q::Error, m_pal.error );
setFontRole( Q::Footer, BodySmall );
setAlignment( Q::Footer, Qt::AlignLeft | Qt::AlignVCenter );
setColor( Q::Footer | Q::Disabled, m_pal.onSurface38 );
// CharacterCount
setMargin( Q::CharacterCount, margin( Q::Footer ) );
setColor( Q::CharacterCount, color( Q::Footer ) );
setFontRole( Q::CharacterCount, fontRole( Q::Footer ) );
setAlignment( Q::CharacterCount, Qt::AlignRight | Qt::AlignVCenter );
setColor( Q::CharacterCount | Q::Disabled, color( Q::Footer | Q::Disabled ) );
}
void Editor::setupProgressBar()

View File

@ -8,7 +8,7 @@
#include <QskTextField.h>
#include <QskBoxBorderColors.h>
#include <QskBoxBorderMetrics.h>
#include <QskBoxHints.h>
#include <QskFunctions.h>
#include <QFontMetricsF>
@ -19,25 +19,12 @@ namespace
{
const int spacingV = 0; // skin hint !
QString effectiveHeaderText( const QskTextField* textField )
{
if ( !textField->isEditing() && textField->text().isEmpty() )
return QString();
return textField->headerText();
}
inline bool hasCharacterCount( const QskTextField* textField )
{
// magic number hardcoded in qquicktextinput.cpp
return textField->maxLength() < 32767;
}
inline bool hasBottomText( const QskTextField* textField )
{
return !textField->footerText().isEmpty() || hasCharacterCount( textField );
}
QString maxLengthString( const QskTextField* textField )
{
QString s = QString::number( textField->text().length() )
@ -79,7 +66,7 @@ namespace
QskMaterial3TextFieldSkinlet::QskMaterial3TextFieldSkinlet( QskSkin* skin )
: Inherited( skin )
{
appendNodeRoles( { SupportingTextRole, CharacterCountRole } );
appendNodeRoles( { CharacterCountRole } );
}
QskMaterial3TextFieldSkinlet::~QskMaterial3TextFieldSkinlet()
@ -91,92 +78,47 @@ QRectF QskMaterial3TextFieldSkinlet::subControlRect( const QskSkinnable* skinnab
{
const auto textField = static_cast< const Q* >( skinnable );
if ( subControl == Q::Panel )
{
auto rect = contentsRect;
if( textField->style() == QskTextField::OutlinedStyle )
{
const auto h = textField->effectiveFontHeight( Q::Header );
rect.setTop( rect.top() + 0.5 * h );
}
if( hasBottomText( textField ) )
{
const auto margins = textField->marginHint( Q::Footer );
const auto h = textField->effectiveFontHeight( Q::Footer )
+ margins.top() + margins.bottom();
rect.setHeight( rect.height() - h );
}
return rect;
}
if ( subControl == Q::Text )
{
auto rect = Inherited::subControlRect( skinnable, contentsRect, Q::Text );
if ( !rect.isEmpty() && ( textField->style() == QskTextField::FilledStyle ) )
{
const auto text = effectiveHeaderText( textField );
if ( !text.isEmpty() )
{
const auto h = skinnable->effectiveFontHeight( Q::Header );
rect.translate( 0.0, 0.5 * ( h + spacingV ) );
}
}
return rect;
}
if ( subControl == Q::Header )
{
const auto text = effectiveHeaderText( textField );
const auto text = effectiveText( textField, Q::Header );
if( text.isEmpty() )
return QRectF();
const QFontMetrics fm( textField->effectiveFont( Q::Header ) );
const auto textSize = fm.size( Qt::TextSingleLine | Qt::TextExpandTabs, text );
qreal x, y;
if ( textField->style() == QskTextField::OutlinedStyle )
{
const auto r = subControlRect( skinnable, contentsRect, Q::TextPanel );
if ( textField->style() == QskTextField::FilledStyle )
const auto x = r.left() + skinnable->paddingHint( Q::TextPanel ).left();
const auto y = r.top() - 0.5 * textSize.height();
return QRectF( x, y, textSize.width(), textSize.height() );
}
else
{
const auto r = subControlRect( skinnable, contentsRect, Q::Text );
x = r.left();
y = r.top() - spacingV - textSize.height();
return QRectF( r.x(), r.top() - textSize.height(),
textSize.width(), textSize.height() );
}
else if ( textField->style() == QskTextField::OutlinedStyle )
{
const auto r = subControlRect( skinnable, contentsRect, Q::Panel );
x = r.left() + skinnable->paddingHint( Q::Panel ).left();
y = r.top() - 0.5 * textSize.height();
}
return QRectF( x, y, textSize.width(), textSize.height() );
}
if ( subControl == Q::Footer )
if ( subControl == Q::TextPanel )
{
if( !textField->footerText().isEmpty() )
auto rect = skinnable->subControlRect( contentsRect, Q::Panel );
if ( textField->style() == QskTextField::OutlinedStyle )
{
auto rect = contentsRect;
const auto margins = textField->marginHint( subControl );
const auto h = textField->effectiveFontHeight( subControl )
+ margins.top() + margins.bottom();
rect.setTop( rect.bottom() - h );
rect.setLeft( rect.left() + margins.left() );
return rect;
const QFontMetrics fm( textField->effectiveFont( Q::Header ) );
rect.setTop( rect.top() + 0.5 * fm.height() );
}
return QRectF();
const auto h = skinnable->strutSizeHint( Q::TextPanel ).height();
rect.setHeight( h );
return rect;
}
if ( subControl == Q::CharacterCount )
@ -212,31 +154,23 @@ QSGNode* QskMaterial3TextFieldSkinlet::updateSubNode(
switch ( nodeRole )
{
case PanelRole:
case TextPanelRole:
{
if( ( textField->style() == QskTextField::OutlinedStyle ) &&
!effectiveHeaderText( textField ).isEmpty() )
const auto rect = textField->subControlRect( Q::TextPanel );
auto hints = textField->boxHints( Q::TextPanel );
if( textField->style() == QskTextField::OutlinedStyle )
{
auto clipRect = textField->subControlRect( Q::Header );
const auto clipRect = textField->subControlRect( Q::Header );
if ( !clipRect.isEmpty() )
{
const auto subControl = Q::Panel;
const auto panelRect = textField->subControlRect( subControl );
auto borderColors = textField->boxBorderColorsHint( subControl );
borderColors = outlineColors( borderColors, panelRect, clipRect );
return updateBoxNode( skinnable, node,
panelRect,
skinnable->boxShapeHint( subControl ),
skinnable->boxBorderMetricsHint( subControl ),
borderColors,
skinnable->gradientHint( subControl ) );
hints.borderColors = outlineColors(
hints.borderColors, rect, clipRect );
}
}
return updateBoxNode( skinnable, node, Q::Panel );
return updateBoxNode( skinnable, node, rect, hints );
}
case CharacterCountRole:
@ -244,12 +178,6 @@ QSGNode* QskMaterial3TextFieldSkinlet::updateSubNode(
return updateTextNode( skinnable, node,
maxLengthString( textField ), Q::CharacterCount );
}
case HeaderRole:
{
return updateTextNode( skinnable, node,
effectiveHeaderText( textField ), Q::Header );
}
}
return Inherited::updateSubNode( skinnable, nodeRole, node );
@ -258,40 +186,68 @@ QSGNode* QskMaterial3TextFieldSkinlet::updateSubNode(
QSizeF QskMaterial3TextFieldSkinlet::sizeHint( const QskSkinnable* skinnable,
Qt::SizeHint which, const QSizeF& constraint ) const
{
Q_UNUSED( constraint ); // TODO ...
if ( which != Qt::PreferredSize )
return QSizeF();
auto hint = Inherited::sizeHint( skinnable, which, constraint );
const auto textField = static_cast< const QskTextField* >( skinnable );
if( textField->style() != QskTextField::PlainStyle )
hint.rheight() += textField->effectiveFontHeight( Q::Header ) + spacingV;
auto hint = textField->unwrappedTextSize();
hint = hint.expandedTo( skinnable->strutSizeHint( Q::TextPanel ) );
if( hasBottomText( textField ) )
if ( textField->style() == QskTextField::OutlinedStyle )
{
const auto margins = textField->marginHint( Q::Footer );
hint.rheight() += textField->effectiveFontHeight( Q::Footer )
+ margins.top() + margins.bottom();
const QFontMetrics fm( textField->effectiveFont( Q::Header ) );
hint.rheight() += 0.5 * fm.height();
}
hint.rheight() += effectiveFooterHeight( textField );
hint = hint.expandedTo( skinnable->strutSizeHint( Q::Panel ) );
return hint;
}
QString QskMaterial3TextFieldSkinlet::effectivePlaceholderText(
const QskTextField* textField ) const
QString QskMaterial3TextFieldSkinlet::effectiveText(
const QskTextField* textField, QskAspect::Subcontrol subControl ) const
{
if ( textField->text().isEmpty() &&
!( textField->isReadOnly() || textField->isEditing() ) )
{
auto text = textField->placeholderText();
if ( text.isEmpty() )
text = textField->headerText();
const bool showHeaderAsPlaceholder =
textField->text().isEmpty() && !textField->isEditing();
return text;
if ( subControl == Q::Header )
{
if ( showHeaderAsPlaceholder )
return QString();
}
else if ( subControl == Q::Placeholder )
{
if ( showHeaderAsPlaceholder )
{
auto text = Inherited::effectiveText( textField, Q::Placeholder );
if ( text.isEmpty() )
text = Inherited::effectiveText( textField, Q::Header );
return text;
}
return QString();
}
return QString();
return Inherited::effectiveText( textField, subControl );
}
qreal QskMaterial3TextFieldSkinlet::effectiveFooterHeight(
const QskTextField* textField ) const
{
if ( hasCharacterCount( textField ) )
{
const auto h = textField->effectiveFontHeight( Q::Footer );
const auto margins = textField->marginHint( Q::Footer );
return h + margins.top() + margins.bottom();
}
return Inherited::effectiveFooterHeight( textField );
}
#include "moc_QskMaterial3TextFieldSkinlet.cpp"

View File

@ -18,8 +18,7 @@ class QSK_MATERIAL3_EXPORT QskMaterial3TextFieldSkinlet : public QskTextFieldSki
public:
enum NodeRole : quint8
{
SupportingTextRole = Inherited::RoleCount,
CharacterCountRole
CharacterCountRole = Inherited::RoleCount
};
Q_INVOKABLE QskMaterial3TextFieldSkinlet( QskSkin* = nullptr );
@ -35,7 +34,10 @@ class QSK_MATERIAL3_EXPORT QskMaterial3TextFieldSkinlet : public QskTextFieldSki
QSGNode* updateSubNode( const QskSkinnable*,
quint8 nodeRole, QSGNode* ) const override;
QString effectivePlaceholderText( const QskTextField* ) const override;
QString effectiveText( const QskTextField*,
QskAspect::Subcontrol ) const override;
qreal effectiveFooterHeight( const QskTextField* ) const override;
};
#endif

View File

@ -158,9 +158,8 @@ namespace
StyleComboBox( QQuickItem* parent = nullptr )
: QskComboBox( parent )
{
addOption( QString(), "Plain" );
addOption( QString(), "Outlined" );
addOption( QString(), "Filled" );
addOption( QString(), "Outlined" );
}
};
}

View File

@ -51,12 +51,14 @@ namespace
new QskTextLabel( label, this );
m_textField = new QskTextField( this );
m_textField->setText( "-0.000" );
m_textField->setPreferredWidth( m_textField->sizeHint().width() );
m_textField->setSizePolicy( Qt::Horizontal, QskSizePolicy::Fixed );
m_textField->setValidator( new InputValidator( m_textField ) );
m_textField->setText( QString::number( value ) );
const QFontMetricsF fm( m_textField->font() );
m_textField->setFixedWidth( fm.horizontalAdvance( "-0.000" ) );
connect( m_textField, &QskTextField::editingChanged,
this, [ this ]( bool on ) { if ( !on ) Q_EMIT valueChanged(); } );

View File

@ -29,7 +29,7 @@ class QskTextField::PrivateData
QString footerText;
QString placeholderText;
Style style = PlainStyle;
Style style = FilledStyle;
QskAspect::States buttonStates;
};
@ -60,6 +60,7 @@ void QskTextField::setStyle( Style style )
m_data->style = style;
resetImplicitSize();
polish();
update();
Q_EMIT styleChanged( style );

View File

@ -35,10 +35,8 @@ class QSK_EXPORT QskTextField : public QskTextInput
enum Style : quint8
{
PlainStyle,
OutlinedStyle,
FilledStyle
FilledStyle,
OutlinedStyle
};
Q_ENUM( Style )

View File

@ -5,16 +5,27 @@
#include "QskTextFieldSkinlet.h"
#include "QskTextField.h"
#include <qfontmetrics.h>
#include "QskFunctions.h"
using Q = QskTextField;
static qreal qskEffectiveTextHeight( const QskTextField* textField,
QskAspect::Subcontrol subControl )
{
auto h = textField->effectiveFontHeight( subControl );
const auto margins = textField->marginHint( subControl );
h += margins.top() + margins.bottom();
const auto sz = textField->strutSizeHint( subControl );
return qMax( h, sz.height() );
}
QskTextFieldSkinlet::QskTextFieldSkinlet( QskSkin* skin )
: Inherited( skin )
{
setNodeRoles( { PanelRole, TextPanelRole,
IconRole, ButtonPanelRole, ButtonRole,
setNodeRoles( { TextPanelRole, IconRole, ButtonPanelRole, ButtonRole,
PlaceholderRole, HeaderRole, FooterRole } );
}
@ -25,41 +36,68 @@ QskTextFieldSkinlet::~QskTextFieldSkinlet()
QRectF QskTextFieldSkinlet::subControlRect( const QskSkinnable* skinnable,
const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const
{
const auto textField = static_cast< const QskTextField* >( skinnable );
if ( subControl == Q::Panel )
{
return contentsRect;
}
if ( subControl == Q::Header )
{
if ( auto h = effectiveHeaderHeight( textField ) )
{
const auto m = textField->marginHint( Q::Header );
const auto r = subControlRect( skinnable, contentsRect, Q::TextPanel );
return QRectF( r.left() + m.left(),
r.top() - m.bottom() - h, r.width() - m.left() - m.right(), h );
}
return QRectF();
}
if ( subControl == Q::Footer )
{
if ( const auto h = effectiveFooterHeight( textField ) )
{
const auto m = textField->marginHint( Q::Footer );
const auto r = subControlRect( skinnable, contentsRect, Q::TextPanel );
return QRectF( r.left() + m.left(),
r.bottom() + m.top(), r.width() - m.left() - m.right(), h );
}
return QRectF();
}
if ( subControl == Q::TextPanel )
return skinnable->subControlContentsRect( contentsRect, Q::Panel );
{
const auto rect = textField->subControlContentsRect( contentsRect, Q::Panel );
return inputPanelRect( textField, rect );
}
if ( subControl == Q::Text )
{
auto rect = skinnable->subControlContentsRect( contentsRect, Q::TextPanel );
auto rect = textField->subControlContentsRect( contentsRect, Q::TextPanel );
if( !skinnable->symbolHint( Q::Icon ).isEmpty() )
{
const auto r = subControlRect( skinnable, contentsRect, Q::Icon );
if ( !r.isEmpty() )
rect.setLeft( r.right() );
}
const auto iconRect = subControlRect( skinnable, contentsRect, Q::Icon );
if ( !iconRect.isEmpty() )
rect.setLeft( iconRect.right() );
if( !skinnable->symbolHint( Q::Button ).isEmpty() )
{
const auto r = subControlRect( skinnable, contentsRect, Q::Button );
if( !r.isEmpty() )
rect.setRight( r.left() );
}
const auto buttonRect = subControlRect( skinnable, contentsRect, Q::Button );
if( !buttonRect.isEmpty() )
rect.setRight( buttonRect.left() );
const auto h = skinnable->effectiveFontHeight( Q::Text );
rect.setTop( rect.center().y() - 0.5 * h );
rect.setHeight( h );
rect = rect.marginsAdded( skinnable->marginHint( Q::Text ) );
return rect;
}
if ( subControl == Q::Placeholder )
{
const auto textField = static_cast< const QskTextField* >( skinnable );
if( textField->text().isEmpty() )
return subControlRect( skinnable, contentsRect, Q::Text );
@ -70,15 +108,10 @@ QRectF QskTextFieldSkinlet::subControlRect( const QskSkinnable* skinnable,
{
if( !skinnable->symbolHint( subControl ).isEmpty() )
{
const auto panelRect = skinnable->subControlContentsRect(
contentsRect, Q::TextPanel );
const auto rect = textField->subControlContentsRect( contentsRect, Q::TextPanel );
auto rect = panelRect;
rect.setSize( skinnable->strutSizeHint( subControl ) );
rect.moveCenter( { rect.center().x(), panelRect.center().y() } );
return rect;
return qskAlignedRectF( rect,
skinnable->strutSizeHint( Q::Icon ), Qt::AlignLeft | Qt::AlignVCenter );
}
return QRectF();
@ -86,15 +119,18 @@ QRectF QskTextFieldSkinlet::subControlRect( const QskSkinnable* skinnable,
if ( subControl == Q::ButtonPanel )
{
const auto textField = static_cast< const QskTextField* >( skinnable );
if ( textField->buttonStates() & Q::Hovered )
{
const auto r = subControlRect( skinnable, contentsRect, Q::Button );
const auto sz = skinnable->strutSizeHint( Q::ButtonPanel );
if ( !sz.isEmpty() )
{
const auto r = subControlRect( skinnable, contentsRect, Q::Button );
QRectF rect( QPointF(), skinnable->strutSizeHint( subControl ) );
rect.moveCenter( r.center() );
QRectF rect( QPointF(), sz );
rect.moveCenter( r.center() );
return rect;
return rect;
}
}
return QRectF();
@ -102,19 +138,12 @@ QRectF QskTextFieldSkinlet::subControlRect( const QskSkinnable* skinnable,
if ( subControl == Q::Button )
{
if( !skinnable->symbolHint( subControl ).isEmpty() )
if( !skinnable->symbolHint( Q::Button ).isEmpty() )
{
const auto panelRect = skinnable->subControlContentsRect(
contentsRect, Q::TextPanel );
const auto rect = textField->subControlContentsRect( contentsRect, Q::TextPanel );
auto rect = panelRect;
const auto size = skinnable->strutSizeHint( subControl );
rect.setHeight( size.height() );
rect.moveCenter( { rect.center().x(), panelRect.center().y() } );
rect.setLeft( rect.right() - size.width() );
return rect;
return qskAlignedRectF( rect,
skinnable->strutSizeHint( Q::Icon ), Qt::AlignRight | Qt::AlignVCenter );
}
return QRectF();
@ -130,22 +159,18 @@ QSGNode* QskTextFieldSkinlet::updateSubNode(
switch ( nodeRole )
{
case PanelRole:
{
return updateBoxNode( skinnable, node, Q::Panel );
}
case TextPanelRole:
{
return updateBoxNode( skinnable, node, Q::TextPanel );
}
case PlaceholderRole:
{
const auto text = effectivePlaceholderText( textField );
const auto subControl = Q::Placeholder;
const auto text = effectiveText( textField, subControl );
if ( text.isEmpty() )
return nullptr;
const auto subControl = Q::Placeholder;
QskSkinHintStatus status;
auto options = skinnable->textOptionsHint( subControl, &status );
@ -161,13 +186,13 @@ QSGNode* QskTextFieldSkinlet::updateSubNode(
case HeaderRole:
{
return updateTextNode( skinnable, node,
textField->headerText(), Q::Header );
effectiveText( textField, Q::Header ), Q::Header );
}
case FooterRole:
{
return updateTextNode( skinnable, node,
textField->footerText(), Q::Footer );
return updateTextNode( skinnable, node,
effectiveText( textField, Q::Footer ), Q::Footer );
}
case IconRole:
@ -183,6 +208,37 @@ QSGNode* QskTextFieldSkinlet::updateSubNode(
return Inherited::updateSubNode( skinnable, nodeRole, node );
}
QRectF QskTextFieldSkinlet::inputPanelRect(
const QskTextField* textField, const QRectF& rect ) const
{
qreal h = textField->effectiveFontHeight( Q::Text );
h = textField->outerBoxSize( Q::TextPanel, QSizeF( rect.width(), h ) ).height();
h = qMax( h, textField->strutSizeHint( Q::TextPanel ).height() );
/*
when having textfields in horizontal layouts you usually want
the text panels being vertically aligned - regardless of having
Q::Header/Q::Footer being available.
*/
auto top = qskEffectiveTextHeight( textField, Q::Header );
auto bottom = qskEffectiveTextHeight( textField, Q::Footer );
if ( rect.height() < top + h + bottom )
{
if ( effectiveText( textField, Q::Footer ).isEmpty() )
bottom = 0.0;
}
if ( rect.height() < top + h + bottom )
{
if ( effectiveText( textField, Q::Header ).isEmpty() )
top = 0.0;
}
return QRectF( rect.left(), rect.top() + top, rect.width(), h );
}
QSizeF QskTextFieldSkinlet::sizeHint( const QskSkinnable* skinnable,
Qt::SizeHint which, const QSizeF& constraint ) const
{
@ -191,13 +247,22 @@ QSizeF QskTextFieldSkinlet::sizeHint( const QskSkinnable* skinnable,
if ( which != Qt::PreferredSize )
return QSizeF();
const auto input = static_cast< const QskAbstractTextInput* >( skinnable );
const auto textField = static_cast< const QskTextField* >( skinnable );
auto hint = input->unwrappedTextSize();
hint = hint.grownBy( skinnable->marginHint( Q::Text ) );
auto hint = textField->unwrappedTextSize();
hint = input->outerBoxSize( Q::TextPanel, hint );
hint = hint.expandedTo( input->strutSizeHint( Q::TextPanel ) );
if( !skinnable->symbolHint( Q::Button ).isEmpty() )
{
const auto sz = skinnable->strutSizeHint( Q::Button );
if ( sz.width() > 0.0 )
hint.rwidth() += sz.width();
}
hint = skinnable->outerBoxSize( Q::TextPanel, hint );
hint = hint.expandedTo( skinnable->strutSizeHint( Q::TextPanel ) );
hint.rheight() += effectiveHeaderHeight( textField );
hint.rheight() += effectiveFooterHeight( textField );
hint = skinnable->outerBoxSize( Q::Panel, hint );
hint = hint.expandedTo( skinnable->strutSizeHint( Q::Panel ) );
@ -205,16 +270,42 @@ QSizeF QskTextFieldSkinlet::sizeHint( const QskSkinnable* skinnable,
return hint;
}
QString QskTextFieldSkinlet::effectivePlaceholderText(
const QskTextField* textField ) const
QString QskTextFieldSkinlet::effectiveText(
const QskTextField* textField, QskAspect::Subcontrol subcontrol ) const
{
if ( textField->text().isEmpty() &&
!( textField->isReadOnly() || textField->isEditing() ) )
if ( subcontrol == Q::Text )
return textField->text();
if ( subcontrol == Q::Placeholder )
{
return textField->placeholderText();
if ( textField->text().isEmpty() &&
!( textField->isReadOnly() || textField->isEditing() ) )
{
return textField->placeholderText();
}
return QString();
}
if ( subcontrol == Q::Header )
return textField->headerText();
if ( subcontrol == Q::Footer )
return textField->footerText();
return QString();
}
qreal QskTextFieldSkinlet::effectiveHeaderHeight( const QskTextField* textField ) const
{
const auto text = effectiveText( textField, Q::Header );
return text.isEmpty() ? 0.0 : qskEffectiveTextHeight( textField, Q::Header );
}
qreal QskTextFieldSkinlet::effectiveFooterHeight( const QskTextField* textField ) const
{
const auto text = effectiveText( textField, Q::Footer );
return text.isEmpty() ? 0.0 : qskEffectiveTextHeight( textField, Q::Footer );
}
#include "moc_QskTextFieldSkinlet.cpp"

View File

@ -19,7 +19,6 @@ class QSK_EXPORT QskTextFieldSkinlet : public QskSkinlet
public:
enum NodeRole : quint8
{
PanelRole,
TextPanelRole,
HeaderRole,
@ -45,7 +44,14 @@ class QSK_EXPORT QskTextFieldSkinlet : public QskSkinlet
QSGNode* updateSubNode( const QskSkinnable*,
quint8 nodeRole, QSGNode* ) const override;
virtual QString effectivePlaceholderText( const QskTextField* ) const;
virtual QString effectiveText( const QskTextField*,
QskAspect::Subcontrol ) const;
qreal effectiveHeaderHeight( const QskTextField* ) const;
virtual qreal effectiveFooterHeight( const QskTextField* ) const;
private:
QRectF inputPanelRect( const QskTextField*, const QRectF& ) const;
};
#endif

View File

@ -161,7 +161,7 @@ QskTextInput::QskTextInput( QQuickItem* parent )
setAcceptedMouseButtons( wrappedInput->acceptedMouseButtons() );
wrappedInput->setAcceptedMouseButtons( Qt::NoButton );
initSizePolicy( QskSizePolicy::Expanding, QskSizePolicy::Fixed );
initSizePolicy( QskSizePolicy::Expanding, QskSizePolicy::Minimum );
setup( wrappedInput );