Qt 6.x
The Qt SDK
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
31{
32 // Ensure that scaling happens on the left side, at the vertical center.
34}
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::controlGotActiveFocus()
216{
217 if (m_focusOutAnimation)
218 m_focusOutAnimation->stop();
219
220 Q_ASSERT(!m_focusInAnimation);
221 if (shouldAnimate()) {
222 m_focusInAnimation = new QParallelAnimationGroup(this);
223
224 QPropertyAnimation *yAnimation = new QPropertyAnimation(this, "y", this);
225 yAnimation->setDuration(300);
226 yAnimation->setStartValue(y());
227 yAnimation->setEndValue(floatingTargetY());
228 yAnimation->setEasingCurve(*animationEasingCurve);
229 m_focusInAnimation->addAnimation(yAnimation);
230
231 auto *scaleAnimation = new QPropertyAnimation(this, "scale", this);
232 scaleAnimation->setDuration(300);
233 scaleAnimation->setStartValue(1);
234 scaleAnimation->setEndValue(floatingScale);
235 yAnimation->setEasingCurve(*animationEasingCurve);
236 m_focusInAnimation->addAnimation(scaleAnimation);
237
238 m_focusInAnimation->start(QAbstractAnimation::DeleteWhenStopped);
239 } else {
240 updateY();
241 }
242}
243
244void QQuickMaterialPlaceholderText::controlLostActiveFocus()
245{
246 Q_ASSERT(!m_focusOutAnimation);
247 if (shouldAnimate()) {
248 m_focusOutAnimation = new QParallelAnimationGroup(this);
249
250 auto *yAnimation = new QPropertyAnimation(this, "y", this);
251 yAnimation->setDuration(300);
252 yAnimation->setStartValue(y());
253 yAnimation->setEndValue(normalTargetY());
254 yAnimation->setEasingCurve(*animationEasingCurve);
255 m_focusOutAnimation->addAnimation(yAnimation);
256
257 auto *scaleAnimation = new QPropertyAnimation(this, "scale", this);
258 scaleAnimation->setDuration(300);
259 scaleAnimation->setStartValue(floatingScale);
260 scaleAnimation->setEndValue(1);
261 yAnimation->setEasingCurve(*animationEasingCurve);
262 m_focusOutAnimation->addAnimation(scaleAnimation);
263
264 m_focusOutAnimation->start(QAbstractAnimation::DeleteWhenStopped);
265 } else {
266 updateY();
267 }
268}
269
270void QQuickMaterialPlaceholderText::maybeSetFocusAnimationProgress()
271{
272 updateY();
273 setScale(shouldFloat() ? floatingScale : 1.0);
274}
275
277{
278 // We deliberately do not call QQuickPlaceholderText's implementation here,
279 // as Material 3 placeholder text should always be left-aligned.
281
282 if (!parentItem())
283 qmlWarning(this) << "Expected parent item by component completion!";
284
285 m_largestHeight = implicitHeight();
286 if (m_largestHeight > 0) {
288 } else {
289 qmlWarning(this) << "Expected implicitHeight of placeholder text" << text()
290 << "to be greater than 0 by component completion!";
291 }
292
293 maybeSetFocusAnimationProgress();
294}
295
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
The QQuickItem class provides the most basic of all visual items in \l {Qt Quick}.
Definition qquickitem.h:64
void setScale(qreal)
QQuickItem * parentItem() const
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
QString text
bool isEmpty() const
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:1083
void setStartValue(const QVariant &value)
void setEasingCurve(const QEasingCurve &easing)
void setDuration(int msecs)
void setEndValue(const QVariant &value)
Combined button and popup list for selecting options.
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
Definition qfloat16.h:287
#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:92
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent