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 QskFluent2Global.h QskFluent2Theme.h QskFluent2SkinFactory.h
) )
list(APPEND PRIVATE_HEADERS
QskFluent2TextFieldSkinlet.h
)
list(APPEND SOURCES list(APPEND SOURCES
QskFluent2Theme.cpp QskFluent2Theme.cpp
QskFluent2Skin.cpp QskFluent2Skin.cpp
QskFluent2SkinFactory.cpp QskFluent2SkinFactory.cpp
QskFluent2TextFieldSkinlet.cpp
) )
qt_add_resources(SOURCES QskFluent2Icons.qrc) qt_add_resources(SOURCES QskFluent2Icons.qrc)

View File

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

View File

@ -489,66 +489,55 @@ void Editor::setupTextField()
const auto activeStates = Q::Focused | Q::Editing; const auto activeStates = Q::Focused | Q::Editing;
{ {
// Text // TextPanel
setAnimation( Q::TextPanel | A::Color, qskDuration ); setAnimation( Q::TextPanel | A::Color, qskDuration );
setAnimation( Q::TextPanel | A::Metric, 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 ) setGradient( aspect | Q::Hovered,
{ m_pal.hoverColor( m_pal.onSurfaceVariant, m_pal.surfaceVariant ),
setBoxShape( Panel, m_pal.shapeExtraSmallTop ); { QskStateCombination::CombinationNoState, activeStates | Q::Error } );
borderMetrics[0].setBottom( 1 ); setGradient( aspect | Q::Disabled,
borderMetrics[1].setBottom( 2 ); QskRgb::toTransparentF( m_pal.onSurface, 0.04 ) );
setBoxBorderColors( Panel, m_pal.onSurfaceVariant ); setBoxShape( aspect, m_pal.shapeExtraSmallTop );
setBoxBorderMetrics( aspect, { 0, 0, 0, 1 } );
setGradient( Panel, m_pal.surfaceVariant ); setBoxBorderMetrics( aspect, { 0, 0, 0, 2 }, activeStates | Q::Hovered );
setBoxBorderMetrics( aspect | Q::Error, { 0, 0, 0, 2 }, activeStates | Q::Hovered );
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 );
} }
{
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 // Icon
@ -561,39 +550,6 @@ void Editor::setupTextField()
setGraphicRole( Q::Icon | Q::Disabled, M3::GraphicRoleOnSurface38 ); 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 // Button
setStrutSize( Q::Button, { 24_px, 24_px } ); setStrutSize( Q::Button, { 24_px, 24_px } );
@ -613,24 +569,48 @@ void Editor::setupTextField()
setGradient( Q::ButtonPanel | Q::Hovered, m_pal.onSurface8 ); setGradient( Q::ButtonPanel | Q::Hovered, m_pal.onSurface8 );
setBoxShape( Q::ButtonPanel, 100, Qt::RelativeSize ); 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 ); 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 ); setAlignment( Q::CharacterCount, Qt::AlignRight | Qt::AlignVCenter );
setColor( Q::CharacterCount | Q::Disabled, color( Q::Footer | Q::Disabled ) );
} }
void Editor::setupProgressBar() void Editor::setupProgressBar()

View File

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

View File

@ -18,8 +18,7 @@ class QSK_MATERIAL3_EXPORT QskMaterial3TextFieldSkinlet : public QskTextFieldSki
public: public:
enum NodeRole : quint8 enum NodeRole : quint8
{ {
SupportingTextRole = Inherited::RoleCount, CharacterCountRole = Inherited::RoleCount
CharacterCountRole
}; };
Q_INVOKABLE QskMaterial3TextFieldSkinlet( QskSkin* = nullptr ); Q_INVOKABLE QskMaterial3TextFieldSkinlet( QskSkin* = nullptr );
@ -35,7 +34,10 @@ class QSK_MATERIAL3_EXPORT QskMaterial3TextFieldSkinlet : public QskTextFieldSki
QSGNode* updateSubNode( const QskSkinnable*, QSGNode* updateSubNode( const QskSkinnable*,
quint8 nodeRole, QSGNode* ) const override; 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 #endif

View File

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

View File

@ -51,12 +51,14 @@ namespace
new QskTextLabel( label, this ); new QskTextLabel( label, this );
m_textField = new QskTextField( 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->setValidator( new InputValidator( m_textField ) );
m_textField->setText( QString::number( value ) ); m_textField->setText( QString::number( value ) );
const QFontMetricsF fm( m_textField->font() );
m_textField->setFixedWidth( fm.horizontalAdvance( "-0.000" ) );
connect( m_textField, &QskTextField::editingChanged, connect( m_textField, &QskTextField::editingChanged,
this, [ this ]( bool on ) { if ( !on ) Q_EMIT valueChanged(); } ); this, [ this ]( bool on ) { if ( !on ) Q_EMIT valueChanged(); } );

View File

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

View File

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

View File

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

View File

@ -19,7 +19,6 @@ class QSK_EXPORT QskTextFieldSkinlet : public QskSkinlet
public: public:
enum NodeRole : quint8 enum NodeRole : quint8
{ {
PanelRole,
TextPanelRole, TextPanelRole,
HeaderRole, HeaderRole,
@ -45,7 +44,14 @@ class QSK_EXPORT QskTextFieldSkinlet : public QskSkinlet
QSGNode* updateSubNode( const QskSkinnable*, QSGNode* updateSubNode( const QskSkinnable*,
quint8 nodeRole, QSGNode* ) const override; 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 #endif

View File

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