341 lines
10 KiB
C++
341 lines
10 KiB
C++
/******************************************************************************
|
|
* QSkinny - Copyright (C) The authors
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*****************************************************************************/
|
|
|
|
#include "QskMaterial3TextInputSkinlet.h"
|
|
#include "QskTextInput.h"
|
|
|
|
#include "QskBoxBorderColors.h"
|
|
#include "QskBoxBorderMetrics.h"
|
|
#include "QskBoxShapeMetrics.h"
|
|
#include "QskFunctions.h"
|
|
|
|
#include <QFontMetricsF>
|
|
|
|
using Q = QskTextInput;
|
|
|
|
namespace
|
|
{
|
|
QString maxLengthString( const QskTextInput* input )
|
|
{
|
|
QString s = QString::number( input->inputText().length() )
|
|
+ " / " + 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 );
|
|
|
|
if( input->labelText().isEmpty() )
|
|
{
|
|
return borderColors;
|
|
}
|
|
|
|
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();
|
|
auto r1 = x1 / panelRect.width();
|
|
r1 = qBound( 0.0, r1, 1.0 );
|
|
|
|
const auto w = qskHorizontalAdvance( input->effectiveFont( Q::LabelText ), input->labelText() );
|
|
|
|
const auto x2 = x1 + w + margins.right();
|
|
auto r2 = x2 / panelRect.width();
|
|
r2 = qBound( 0.0, r2, 1.0 );
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
QskMaterial3TextInputSkinlet::QskMaterial3TextInputSkinlet( QskSkin* skin )
|
|
: Inherited( skin )
|
|
{
|
|
setNodeRoles( {
|
|
PanelRole,
|
|
LeadingIconRole,
|
|
LabelTextRole,
|
|
HintTextRole,
|
|
SupportingTextRole,
|
|
CharacterCountRole,
|
|
TrailingIconRippleRole,
|
|
TrailingIconRole,
|
|
} );
|
|
}
|
|
|
|
QskMaterial3TextInputSkinlet::~QskMaterial3TextInputSkinlet()
|
|
{
|
|
}
|
|
|
|
QRectF QskMaterial3TextInputSkinlet::subControlRect( const QskSkinnable* skinnable,
|
|
const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const
|
|
{
|
|
const auto input = static_cast< const Q* >( skinnable );
|
|
|
|
if ( subControl == Q::Panel )
|
|
{
|
|
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 );
|
|
|
|
return rect;
|
|
}
|
|
else if ( subControl == Q::LabelText )
|
|
{
|
|
const auto inputRect = input->subControlRect( Q::InputText );
|
|
|
|
if( !input->inputText().isEmpty()
|
|
|| input->hasSkinState( Q::Focused )
|
|
|| input->hasSkinState( Q::Editing ) )
|
|
{
|
|
const auto margins = input->marginHint( subControl );
|
|
auto rect = inputRect;
|
|
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
|
|
{
|
|
return inputRect;
|
|
}
|
|
}
|
|
else if ( subControl == Q::LeadingIcon )
|
|
{
|
|
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::HintText )
|
|
{
|
|
if( !input->hasSkinState( Q::TextPopulated )
|
|
&& ( input->hasSkinState( Q::Focused ) || input->hasSkinState( Q::Editing ) ) )
|
|
{
|
|
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 );
|
|
}
|
|
|
|
QSizeF QskMaterial3TextInputSkinlet::adjustSizeHint( const QskSkinnable* skinnable, Qt::SizeHint which, const QSizeF& oldHint ) const
|
|
{
|
|
if ( which != Qt::PreferredSize )
|
|
return QSizeF();
|
|
|
|
auto hint = oldHint;
|
|
|
|
const auto input = static_cast< const Q* >( skinnable );
|
|
|
|
if( input->emphasis() == Q::LowEmphasis )
|
|
{
|
|
const auto fontHeight = input->effectiveFontHeight( Q::LabelText | Q::Focused );
|
|
hint.rheight() += fontHeight / 2;
|
|
}
|
|
|
|
if( !input->supportingText().isEmpty() || input->maxLength() != 32767 ) // magic number hardcoded in qquicktextinput.cpp
|
|
{
|
|
const auto margins = input->marginHint( Q::SupportingText );
|
|
hint.rheight() += margins.top() + input->effectiveFontHeight( Q::SupportingText ) + margins.bottom();
|
|
}
|
|
|
|
return hint;
|
|
}
|
|
|
|
QSGNode* QskMaterial3TextInputSkinlet::updateSubNode(
|
|
const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const
|
|
{
|
|
const auto input = static_cast< const Q* >( skinnable );
|
|
|
|
switch ( nodeRole )
|
|
{
|
|
case PanelRole:
|
|
{
|
|
if ( !input->hasPanel() )
|
|
return nullptr;
|
|
|
|
if( input->emphasis() == Q::LowEmphasis
|
|
&& ( input->hasSkinState( Q::TextPopulated )
|
|
|| 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:
|
|
{
|
|
return updateSymbolNode( skinnable, node, Q::LeadingIcon );
|
|
}
|
|
|
|
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 );
|
|
}
|
|
}
|
|
|
|
return Inherited::updateSubNode( skinnable, nodeRole, node );
|
|
}
|
|
|
|
#include "moc_QskMaterial3TextInputSkinlet.cpp"
|