Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qquickmaterialplaceholdertext.cpp
Go to the documentation of this file.
1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
6#include <QtCore/qpropertyanimation.h>
7#include <QtCore/qparallelanimationgroup.h>
8#include <QtGui/qpainter.h>
9#include <QtGui/qpainterpath.h>
10#include <QtQml/qqmlinfo.h>
11#include <QtQuickTemplates2/private/qquicktheme_p.h>
12#include <QtQuickTemplates2/private/qquicktextarea_p.h>
13#include <QtQuickTemplates2/private/qquicktextfield_p.h>
14
16
17static const qreal floatingScale = 0.8;
19
20/*
21 This class makes it easier to animate the various placeholder text changes
22 for each type of text container (filled, outlined).
23
24 By doing animations in C++, we avoid having a bunch of states, transitions,
25 and animations (which are all QObjects) declared in QML, even if that text
26 control never gets focus and hence never needs them.
27*/
28
35
37{
38 return m_filled;
39}
40
42{
43 if (filled == m_filled)
44 return;
45
46 m_filled = filled;
47 update();
48 void filledChanged();
49}
50
52{
53 return m_controlHasActiveFocus;
54}
55
57{
58 if (m_controlHasActiveFocus == controlHasActiveFocus)
59 return;
60
61 m_controlHasActiveFocus = controlHasActiveFocus;
62 if (m_controlHasActiveFocus)
63 controlGotActiveFocus();
64 else
65 controlLostActiveFocus();
67}
68
70{
71 return m_controlHasText;
72}
73
75{
76 if (m_controlHasText == controlHasText)
77 return;
78
79 m_controlHasText = controlHasText;
80 maybeSetFocusAnimationProgress();
82}
83
84/*
85 Placeholder text of outlined text fields should float when:
86 - There is placeholder text, and
87 - The control has active focus, or
88 - The control has text
89*/
90bool QQuickMaterialPlaceholderText::shouldFloat() const
91{
92 const bool controlHasActiveFocusOrText = m_controlHasActiveFocus || m_controlHasText;
93 return m_filled
94 ? controlHasActiveFocusOrText
95 : !text().isEmpty() && controlHasActiveFocusOrText;
96}
97
98bool QQuickMaterialPlaceholderText::shouldAnimate() const
99{
100 return m_filled
101 ? !m_controlHasText
102 : !m_controlHasText && !text().isEmpty();
103}
104
105void QQuickMaterialPlaceholderText::updateY()
106{
107 setY(shouldFloat() ? floatingTargetY() : normalTargetY());
108}
109
111{
112 if (const auto textArea = qobject_cast<QQuickTextArea *>(textControl))
113 return textArea->topInset();
114
115 if (const auto textField = qobject_cast<QQuickTextField *>(textControl))
116 return textField->topInset();
117
118 return 0;
119}
120
121qreal QQuickMaterialPlaceholderText::normalTargetY() const
122{
123 auto *textArea = qobject_cast<QQuickTextArea *>(textControl());
124 if (textArea && m_controlHeight >= textArea->implicitHeight()) {
125 // TextArea can be multiple lines in height, and we want the
126 // placeholder text to sit in the middle of its default-height
127 // (one-line) if its explicit height is greater than or equal to its
128 // implicit height - i.e. if it has room for it. If it doesn't have
129 // room, just do what TextField does.
130 // We should also account for any topInset the user might have specified,
131 // which is useful to ensure that the text doesn't get clipped.
132 return ((m_controlImplicitBackgroundHeight - m_largestHeight) / 2.0)
134 }
135
136 // When the placeholder text shouldn't float, it should sit in the middle of the TextField.
137 return (m_controlHeight - height()) / 2.0;
138}
139
140qreal QQuickMaterialPlaceholderText::floatingTargetY() const
141{
142 // For filled text fields, the placeholder text sits just above
143 // the text when floating.
144 if (m_filled)
145 return m_verticalPadding;
146
147 // Outlined text fields have the placeaholder vertically centered
148 // along the outline at the top.
149 return (-m_largestHeight / 2.0) + controlTopInset(textControl());
150}
151
158{
159 return m_largestHeight;
160}
161
163{
164 return m_controlImplicitBackgroundHeight;
165}
166
168{
169 if (qFuzzyCompare(m_controlImplicitBackgroundHeight, controlImplicitBackgroundHeight))
170 return;
171
172 m_controlImplicitBackgroundHeight = controlImplicitBackgroundHeight;
173 updateY();
175}
176
188{
189 return m_controlHeight;
190}
191
193{
194 if (qFuzzyCompare(m_controlHeight, controlHeight))
195 return;
196
197 m_controlHeight = controlHeight;
198 updateY();
199}
200
202{
203 return m_verticalPadding;
204}
205
207{
208 if (qFuzzyCompare(m_verticalPadding, verticalPadding))
209 return;
210
211 m_verticalPadding = verticalPadding;
213}
214
215void QQuickMaterialPlaceholderText::adjustTransformOrigin()
216{
217 switch (effectiveHAlign()) {
222 break;
225 break;
228 break;
229 }
230}
231
232void QQuickMaterialPlaceholderText::controlGotActiveFocus()
233{
234 if (m_focusOutAnimation) {
235 // Focus changes can happen before the animations finish.
236 // In that case, stop the animation, which will eventually delete it.
237 // Until it's deleted, we clear the pointer so that our asserts don't fail
238 // for the wrong reason.
239 m_focusOutAnimation->stop();
240 m_focusOutAnimation.clear();
241 }
242
243 Q_ASSERT(!m_focusInAnimation);
244 if (shouldAnimate()) {
245 m_focusInAnimation = new QParallelAnimationGroup(this);
246
247 QPropertyAnimation *yAnimation = new QPropertyAnimation(this, "y", this);
248 yAnimation->setDuration(300);
249 yAnimation->setStartValue(y());
250 yAnimation->setEndValue(floatingTargetY());
251 yAnimation->setEasingCurve(*animationEasingCurve);
252 m_focusInAnimation->addAnimation(yAnimation);
253
254 auto *scaleAnimation = new QPropertyAnimation(this, "scale", this);
255 scaleAnimation->setDuration(300);
256 scaleAnimation->setStartValue(1);
257 scaleAnimation->setEndValue(floatingScale);
258 yAnimation->setEasingCurve(*animationEasingCurve);
259 m_focusInAnimation->addAnimation(scaleAnimation);
260
261 m_focusInAnimation->start(QAbstractAnimation::DeleteWhenStopped);
262 } else {
263 updateY();
264 }
265}
266
267void QQuickMaterialPlaceholderText::controlLostActiveFocus()
268{
269 if (m_focusInAnimation) {
270 m_focusInAnimation->stop();
271 m_focusInAnimation.clear();
272 }
273
274 Q_ASSERT(!m_focusOutAnimation);
275 if (shouldAnimate()) {
276 m_focusOutAnimation = new QParallelAnimationGroup(this);
277
278 auto *yAnimation = new QPropertyAnimation(this, "y", this);
279 yAnimation->setDuration(300);
280 yAnimation->setStartValue(y());
281 yAnimation->setEndValue(normalTargetY());
282 yAnimation->setEasingCurve(*animationEasingCurve);
283 m_focusOutAnimation->addAnimation(yAnimation);
284
285 auto *scaleAnimation = new QPropertyAnimation(this, "scale", this);
286 scaleAnimation->setDuration(300);
287 scaleAnimation->setStartValue(floatingScale);
288 scaleAnimation->setEndValue(1);
289 yAnimation->setEasingCurve(*animationEasingCurve);
290 m_focusOutAnimation->addAnimation(scaleAnimation);
291
292 m_focusOutAnimation->start(QAbstractAnimation::DeleteWhenStopped);
293 } else {
294 updateY();
295 }
296}
297
298void QQuickMaterialPlaceholderText::maybeSetFocusAnimationProgress()
299{
300 updateY();
301 setScale(shouldFloat() ? floatingScale : 1.0);
302}
303
305{
307
308 adjustTransformOrigin();
309
310 m_largestHeight = implicitHeight();
311 if (m_largestHeight > 0) {
313 } else {
314 qmlWarning(this) << "Expected implicitHeight of placeholder text" << text()
315 << "to be greater than 0 by component completion!";
316 }
317
318 maybeSetFocusAnimationProgress();
319}
320
void stop()
Stops the animation.
void start(QAbstractAnimation::DeletionPolicy policy=KeepWhenStopped)
Starts the animation.
void addAnimation(QAbstractAnimation *animation)
Adds animation to this group.
\inmodule QtCore
void clear() noexcept
Definition qpointer.h:87
The QQuickItem class provides the most basic of all visual items in \l {Qt Quick}.
Definition qquickitem.h:63
void setScale(qreal)
void setTransformOrigin(TransformOrigin)
void setY(qreal)
void update()
Schedules a call to updatePaintNode() for this item.
void setControlImplicitBackgroundHeight(qreal controlImplicitBackgroundHeight)
void componentComplete() override
\reimp Derived classes should call the base class method before adding their own actions to perform a...
QQuickMaterialPlaceholderText(QQuickItem *parent=nullptr)
void setControlHasActiveFocus(bool controlHasActiveFocus)
QQuickItem * textControl() const
void componentComplete() override
\reimp Derived classes should call the base class method before adding their own actions to perform a...
HAlignment effectiveHAlign() const
QString text
void effectiveHorizontalAlignmentChanged()
bool isEmpty() const noexcept
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:192
Combined button and popup list for selecting options.
#define Q_FALLTHROUGH()
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
Definition qfloat16.h:333
#define Q_GLOBAL_STATIC(TYPE, NAME,...)
GLint GLsizei GLsizei height
GLint y
Q_QML_EXPORT QQmlInfo qmlWarning(const QObject *me)
static QT_BEGIN_NAMESPACE const qreal floatingScale
qreal controlTopInset(QQuickItem *textControl)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define emit
double qreal
Definition qtypes.h:187
connect(quitButton, &QPushButton::clicked, &app, &QCoreApplication::quit, Qt::QueuedConnection)