Material: fix placeholder text y position when control is too small

Ensure that it's sensibly positioned despite its size.

Fixes: QTBUG-111515
Pick-to: 6.5 6.5.0
Change-Id: I71816c461ff1d2f85e010bf871ab1b7ef2ccaf6e
Reviewed-by: Oliver Eftevaag <oliver.eftevaag@qt.io>
This commit is contained in:
Mitch Curtis 2023-03-07 16:06:29 +08:00
parent ee3d11f70b
commit 4f5b4f81b0
5 changed files with 91 additions and 12 deletions

View File

@ -50,6 +50,7 @@ T.TextArea {
controlHasActiveFocus: control.activeFocus
controlHasText: control.length > 0
controlImplicitBackgroundHeight: control.implicitBackgroundHeight
controlHeight: control.height
}
background: MaterialTextContainer {

View File

@ -50,6 +50,7 @@ T.TextField {
controlHasActiveFocus: control.activeFocus
controlHasText: control.length > 0
controlImplicitBackgroundHeight: control.implicitBackgroundHeight
controlHeight: control.height
}
background: MaterialTextContainer {

View File

@ -9,6 +9,7 @@
#include <QtGui/qpainterpath.h>
#include <QtQml/qqmlinfo.h>
#include <QtQuickTemplates2/private/qquicktheme_p.h>
#include <QtQuickTemplates2/private/qquicktextarea_p.h>
QT_BEGIN_NAMESPACE
@ -100,13 +101,25 @@ bool QQuickMaterialPlaceholderText::shouldAnimate() const
: !m_controlHasText && !text().isEmpty();
}
void QQuickMaterialPlaceholderText::updateY()
{
setY(shouldFloat() ? floatingTargetY() : normalTargetY());
}
qreal QQuickMaterialPlaceholderText::normalTargetY() const
{
auto *textArea = qobject_cast<QQuickTextArea *>(parentItem());
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
// (one-line) if its explicit height is greater than or equal to its
// implicit height - i.e. if it has room for it. If it doesn't have
// room, just do what TextField does.
return (m_controlImplicitBackgroundHeight - m_largestHeight) / 2.0;
}
// When the placeholder text shouldn't float, it should sit in the middle of the TextField.
// We could just use the control's height minus our height instead of the members, but
// that doesn't work for TextArea, which can be multiple lines in height and hence taller.
// In that case, we want the placeholder text to sit in the middle of its default-height (one-line).
return (m_controlImplicitBackgroundHeight - m_largestHeight) / 2.0;
return (m_controlHeight - height()) / 2.0;
}
qreal QQuickMaterialPlaceholderText::floatingTargetY() const
@ -142,10 +155,34 @@ void QQuickMaterialPlaceholderText::setControlImplicitBackgroundHeight(qreal con
return;
m_controlImplicitBackgroundHeight = controlImplicitBackgroundHeight;
setY(shouldFloat() ? floatingTargetY() : normalTargetY());
updateY();
emit controlImplicitBackgroundHeightChanged();
}
/*!
\internal
Exists so that we can call updateY when the control's height changes,
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
it's simpler just to use it.
*/
qreal QQuickMaterialPlaceholderText::controlHeight() const
{
return m_controlHeight;
}
void QQuickMaterialPlaceholderText::setControlHeight(qreal controlHeight)
{
if (qFuzzyCompare(m_controlHeight, controlHeight))
return;
m_controlHeight = controlHeight;
updateY();
}
qreal QQuickMaterialPlaceholderText::verticalPadding() const
{
return m_verticalPadding;
@ -185,8 +222,7 @@ void QQuickMaterialPlaceholderText::controlGotActiveFocus()
m_focusInAnimation->start(QAbstractAnimation::DeleteWhenStopped);
} else {
const int newY = shouldFloat() ? floatingTargetY() : normalTargetY();
setY(newY);
updateY();
}
}
@ -212,16 +248,14 @@ void QQuickMaterialPlaceholderText::controlLostActiveFocus()
m_focusOutAnimation->start(QAbstractAnimation::DeleteWhenStopped);
} else {
const int newY = shouldFloat() ? floatingTargetY() : normalTargetY();
setY(newY);
updateY();
}
}
void QQuickMaterialPlaceholderText::maybeSetFocusAnimationProgress()
{
const bool shouldWeFloat = shouldFloat();
setY(shouldWeFloat ? floatingTargetY() : normalTargetY());
setScale(shouldWeFloat ? floatingScale : 1.0);
updateY();
setScale(shouldFloat() ? floatingScale : 1.0);
}
void QQuickMaterialPlaceholderText::componentComplete()

View File

@ -34,6 +34,7 @@ class QQuickMaterialPlaceholderText : public QQuickPlaceholderText
Q_PROPERTY(qreal verticalPadding READ verticalPadding WRITE setVerticalPadding NOTIFY verticalPaddingChanged FINAL)
Q_PROPERTY(qreal controlImplicitBackgroundHeight READ controlImplicitBackgroundHeight
WRITE setControlImplicitBackgroundHeight NOTIFY controlImplicitBackgroundHeightChanged FINAL)
Q_PROPERTY(qreal controlHeight READ controlHeight WRITE setControlHeight FINAL)
QML_NAMED_ELEMENT(FloatingPlaceholderText)
QML_ADDED_IN_VERSION(6, 5)
@ -54,6 +55,9 @@ public:
qreal controlImplicitBackgroundHeight() const;
void setControlImplicitBackgroundHeight(qreal controlImplicitBackgroundHeight);
qreal controlHeight() const;
void setControlHeight(qreal controlHeight);
qreal verticalPadding() const;
void setVerticalPadding(qreal verticalPadding);
@ -69,6 +73,7 @@ private:
bool shouldFloat() const;
bool shouldAnimate() const;
void updateY();
qreal normalTargetY() const;
qreal floatingTargetY() const;
@ -85,6 +90,7 @@ private:
int m_largestHeight = 0;
qreal m_verticalPadding = 0;
qreal m_controlImplicitBackgroundHeight = 0;
qreal m_controlHeight = 0;
QPointer<QParallelAnimationGroup> m_focusInAnimation;
QPointer<QParallelAnimationGroup> m_focusOutAnimation;
};

View File

@ -7,6 +7,7 @@ import QtTest
import QtQuick.Templates as T
import QtQuick.Controls
import QtQuick.Controls.Material
import QtQuick.Controls.Material.impl as MaterialImpl
TestCase {
id: testCase
@ -953,4 +954,40 @@ TestCase {
verify(item)
compare(item["__isDiscrete"], data.expectTickmarks)
}
Component {
id: textFieldComponent
TextField {}
}
Component {
id: textAreaComponent
TextArea {}
}
function test_placeholderText() {
{
// The non-floating placeholder text should be in the middle of TextField regardless of its height.
let textField = createTemporaryObject(textFieldComponent, testCase, { placeholderText: "TextField" })
verify(textField)
let placeholderTextItem = textField.children[0]
verify(placeholderTextItem as MaterialImpl.FloatingPlaceholderText)
compare(placeholderTextItem.y, (textField.height - placeholderTextItem.height) / 2)
textField.height = 10
compare(placeholderTextItem.y, (textField.height - placeholderTextItem.height) / 2)
textField.destroy()
}
{
// 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.
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)
textArea.height = 10
compare(placeholderTextItem.y, (textArea.height - placeholderTextItem.height) / 2)
}
}
}