Material: fix TextArea decorations when in a Flickable
This patch fixes the following issues when the Material TextArea is attached to a Flickable: - Floating placeholder text scrolls with the Flickable. - When text is cleared without the control having focus: - Floating placeholder text is positioned incorrectly. - The floating text background outline gap is still open. - The background outline color is incorrect when the control has focus (used primaryTextColor instead of accentColor). Pick-to: 6.5 Task-number: QTBUG-112650 Change-Id: Icfa3517e4abcb1209ea2291dabdec225011f19ef Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io>
This commit is contained in:
parent
fd489252a7
commit
2d99c70f98
|
@ -44,6 +44,9 @@ T.TextArea {
|
|||
color: control.placeholderTextColor
|
||||
elide: Text.ElideRight
|
||||
renderType: control.renderType
|
||||
// When the TextArea is in a Flickable, the background is reparented to it
|
||||
// so that decorations don't move with the content. We need to do the same.
|
||||
parent: control.background.parent
|
||||
|
||||
filled: control.Material.containerStyle === Material.Filled
|
||||
verticalPadding: control.Material.textFieldVerticalPadding
|
||||
|
|
|
@ -108,7 +108,7 @@ void QQuickMaterialPlaceholderText::updateY()
|
|||
|
||||
qreal QQuickMaterialPlaceholderText::normalTargetY() const
|
||||
{
|
||||
auto *textArea = qobject_cast<QQuickTextArea *>(parentItem());
|
||||
auto *textArea = qobject_cast<QQuickTextArea *>(textControl());
|
||||
if (textArea && m_controlHeight >= textArea->implicitHeight()) {
|
||||
// TextArea can be multiple lines in height, and we want the
|
||||
// placeholder text to sit in the middle of its default-height
|
||||
|
@ -166,7 +166,7 @@ void QQuickMaterialPlaceholderText::setControlImplicitBackgroundHeight(qreal con
|
|||
which is necessary for some y position calculations.
|
||||
|
||||
We don't really need it for the actual calculations, since we already
|
||||
have access to the parent item, from which the property comes, but
|
||||
have access to the control, from which the property comes, but
|
||||
it's simpler just to use it.
|
||||
*/
|
||||
qreal QQuickMaterialPlaceholderText::controlHeight() const
|
||||
|
|
|
@ -272,7 +272,8 @@ void QQuickMaterialTextContainer::paint(QPainter *painter)
|
|||
|
||||
painter->setRenderHint(QPainter::Antialiasing, true);
|
||||
|
||||
const bool focused = parentItem() && parentItem()->hasActiveFocus();
|
||||
auto control = textControl();
|
||||
const bool focused = control && control->hasActiveFocus();
|
||||
// We still want to draw the stroke when it's filled, otherwise it will be a pixel
|
||||
// (the pen width) too narrow on either side.
|
||||
QPen pen;
|
||||
|
@ -317,6 +318,16 @@ bool QQuickMaterialTextContainer::shouldAnimateOutline() const
|
|||
return !m_controlHasText && m_placeholderHasText;
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal
|
||||
|
||||
\sa QQuickPlaceholderText::textControl().
|
||||
*/
|
||||
QQuickItem *QQuickMaterialTextContainer::textControl() const
|
||||
{
|
||||
return qobject_cast<QQuickItem *>(parent());
|
||||
}
|
||||
|
||||
void QQuickMaterialTextContainer::controlGotActiveFocus()
|
||||
{
|
||||
const bool shouldAnimate = m_filled ? !m_controlHasText : shouldAnimateOutline();
|
||||
|
@ -367,9 +378,16 @@ void QQuickMaterialTextContainer::startFocusAnimation()
|
|||
|
||||
void QQuickMaterialTextContainer::maybeSetFocusAnimationProgress()
|
||||
{
|
||||
// Show the interrupted outline when there is text.
|
||||
if (!m_filled && m_controlHasText && m_placeholderHasText)
|
||||
if (m_filled)
|
||||
return;
|
||||
|
||||
if (m_controlHasText && m_placeholderHasText) {
|
||||
// Show the interrupted outline when there is text.
|
||||
setFocusAnimationProgress(1);
|
||||
} else if (!m_controlHasText && !m_controlHasActiveFocus) {
|
||||
// If the text was cleared while it didn't have focus, don't animate, just close the gap.
|
||||
setFocusAnimationProgress(0);
|
||||
}
|
||||
}
|
||||
|
||||
void QQuickMaterialTextContainer::componentComplete()
|
||||
|
|
|
@ -83,6 +83,7 @@ signals:
|
|||
private:
|
||||
bool shouldAnimateOutline() const;
|
||||
|
||||
QQuickItem *textControl() const;
|
||||
void controlGotActiveFocus();
|
||||
void controlLostActiveFocus();
|
||||
void startFocusAnimation();
|
||||
|
|
|
@ -16,10 +16,25 @@ QQuickPlaceholderText::QQuickPlaceholderText(QQuickItem *parent) : QQuickText(pa
|
|||
void QQuickPlaceholderText::componentComplete()
|
||||
{
|
||||
QQuickText::componentComplete();
|
||||
connect(parentItem(), SIGNAL(effectiveHorizontalAlignmentChanged()), this, SLOT(updateAlignment()));
|
||||
|
||||
auto control = textControl();
|
||||
if (control)
|
||||
connect(control, SIGNAL(effectiveHorizontalAlignmentChanged()), this, SLOT(updateAlignment()));
|
||||
updateAlignment();
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal
|
||||
|
||||
The control that we're representing. This exists because
|
||||
parentItem() is not always the control - it may be a Flickable
|
||||
in the case of TextArea.
|
||||
*/
|
||||
QQuickItem *QQuickPlaceholderText::textControl() const
|
||||
{
|
||||
return qobject_cast<QQuickItem *>(parent());
|
||||
}
|
||||
|
||||
void QQuickPlaceholderText::updateAlignment()
|
||||
{
|
||||
if (QQuickTextInput *input = qobject_cast<QQuickTextInput *>(parentItem())) {
|
||||
|
|
|
@ -32,6 +32,8 @@ public:
|
|||
protected:
|
||||
void componentComplete() override;
|
||||
|
||||
QQuickItem *textControl() const;
|
||||
|
||||
private Q_SLOTS:
|
||||
void updateAlignment();
|
||||
};
|
||||
|
|
|
@ -978,15 +978,61 @@ TestCase {
|
|||
}
|
||||
|
||||
{
|
||||
// The non-floating placeholder text should be near the top of TextArea while it has room, but when it
|
||||
// doesn't have room, it should start behaving like TextField's.
|
||||
// The non-floating placeholder text should be near the top of TextArea while it has room...
|
||||
let textArea = createTemporaryObject(textAreaComponent, testCase, { placeholderText: "TextArea" })
|
||||
verify(textArea)
|
||||
let placeholderTextItem = textArea.children[0]
|
||||
verify(placeholderTextItem as MaterialImpl.FloatingPlaceholderText)
|
||||
compare(placeholderTextItem.y, (placeholderTextItem.controlImplicitBackgroundHeight - placeholderTextItem.largestHeight) / 2)
|
||||
|
||||
// ... also when it has a lot of room...
|
||||
textArea.height = 200
|
||||
compare(placeholderTextItem.y, (placeholderTextItem.controlImplicitBackgroundHeight - placeholderTextItem.largestHeight) / 2)
|
||||
|
||||
// ... but when it doesn't have room, it should start behaving like TextField's.
|
||||
textArea.height = 10
|
||||
compare(placeholderTextItem.y, (textArea.height - placeholderTextItem.height) / 2)
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: flickableTextAreaComponent
|
||||
|
||||
Flickable {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
y: 20
|
||||
width: 180
|
||||
height: 100
|
||||
|
||||
TextArea.flickable: TextArea {
|
||||
placeholderText: "Type something..."
|
||||
text: "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function test_placeholderTextInFlickable() {
|
||||
let flickable = createTemporaryObject(flickableTextAreaComponent, testCase)
|
||||
verify(flickable)
|
||||
|
||||
let textArea = flickable.TextArea.flickable
|
||||
verify(textArea)
|
||||
let placeholderTextItem = flickable.children[2]
|
||||
verify(placeholderTextItem as MaterialImpl.FloatingPlaceholderText)
|
||||
|
||||
// The placeholder text should always float at a fixed position at the top
|
||||
// when text has been set, even when it's in a Flickable.
|
||||
flickable.contentY = -50
|
||||
compare(placeholderTextItem.y, -Math.floor(placeholderTextItem.largestHeight / 2))
|
||||
flickable.contentY = 0
|
||||
|
||||
// When the text is cleared, it shouldn't float.
|
||||
flickable.height = 160
|
||||
textArea.text = ""
|
||||
compare(placeholderTextItem.y, (placeholderTextItem.controlImplicitBackgroundHeight - placeholderTextItem.largestHeight) / 2)
|
||||
// The background outline gap should be closed.
|
||||
let textContainer = flickable.children[1]
|
||||
verify(textContainer as MaterialImpl.MaterialTextContainer)
|
||||
compare(textContainer.focusAnimationProgress, 0)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,6 +89,18 @@ Page {
|
|||
|
||||
Material.containerStyle: layout.containerStyle
|
||||
}
|
||||
|
||||
Flickable {
|
||||
width: 200
|
||||
height: 100
|
||||
|
||||
TextArea.flickable: TextArea {
|
||||
placeholderText: "placeholderText"
|
||||
text: "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn"
|
||||
|
||||
Material.containerStyle: layout.containerStyle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
|
|
Loading…
Reference in New Issue