Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
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// Qt-Security score:significant reason:default
4
6
7#include <QtCore/qpropertyanimation.h>
8#include <QtCore/qparallelanimationgroup.h>
9#include <QtGui/qpainter.h>
10#include <QtGui/qpainterpath.h>
11#include <QtQml/qqmlinfo.h>
12#include <QtQuickTemplates2/private/qquicktheme_p.h>
13#include <QtQuickTemplates2/private/qquicktextarea_p.h>
14#include <QtQuickTemplates2/private/qquicktextfield_p.h>
15
17
18static const qreal floatingScale = 0.8;
20
21/*
22 This class makes it easier to animate the various placeholder text changes
23 for each type of text container (filled, outlined).
24
25 By doing animations in C++, we avoid having a bunch of states, transitions,
26 and animations (which are all QObjects) declared in QML, even if that text
27 control never gets focus and hence never needs them.
28*/
29
30QQuickMaterialPlaceholderText::QQuickMaterialPlaceholderText(QQuickItem *parent)
31 : QQuickPlaceholderText(parent)
32{
33 connect(this, &QQuickMaterialPlaceholderText::effectiveHorizontalAlignmentChanged,
34 this, &QQuickMaterialPlaceholderText::adjustTransformOrigin);
35}
36
38{
39 return m_filled;
40}
41
43{
44 if (filled == m_filled)
45 return;
46
47 m_filled = filled;
48 update();
49 void filledChanged();
50}
51
53{
54 return m_controlHasActiveFocus;
55}
56
58{
59 if (m_controlHasActiveFocus == controlHasActiveFocus)
60 return;
61
62 m_controlHasActiveFocus = controlHasActiveFocus;
63 controlActiveFocusChanged();
64 emit controlHasActiveFocusChanged();
65}
66
68{
69 return m_controlHasText;
70}
71
73{
74 if (m_controlHasText == controlHasText)
75 return;
76
77 m_controlHasText = controlHasText;
78 updateFocusAnimation();
79 emit controlHasTextChanged();
80}
81
82/*
83 Placeholder text of outlined text fields should float when:
84 - There is placeholder text, and
85 - The control has active focus, or
86 - The control has text
87*/
88bool QQuickMaterialPlaceholderText::shouldFloat() const
89{
90 const bool controlHasActiveFocusOrText = m_controlHasActiveFocus || m_controlHasText;
91 return m_filled
92 ? controlHasActiveFocusOrText
93 : !text().isEmpty() && controlHasActiveFocusOrText;
94}
95
96bool QQuickMaterialPlaceholderText::shouldAnimate() const
97{
98 return m_filled
99 ? !m_controlHasText
100 : !m_controlHasText && !text().isEmpty();
101}
102
103void QQuickMaterialPlaceholderText::updateY()
104{
105 setY(shouldFloat() ? floatingTargetY() : normalTargetY());
106}
107
108void QQuickMaterialPlaceholderText::updateX()
109{
110 setX(shouldFloat() ? floatingTargetX() : normalTargetX());
111}
112
113qreal controlTopInset(QQuickItem *textControl)
114{
115 if (const auto textArea = qobject_cast<QQuickTextArea *>(textControl))
116 return textArea->topInset();
117
118 if (const auto textField = qobject_cast<QQuickTextField *>(textControl))
119 return textField->topInset();
120
121 return 0;
122}
123
124qreal QQuickMaterialPlaceholderText::normalTargetY() const
125{
126 auto *textArea = qobject_cast<QQuickTextArea *>(textControl());
127 if (textArea && m_controlHeight >= textArea->implicitHeight()) {
128 // TextArea can be multiple lines in height, and we want the
129 // placeholder text to sit in the middle of its default-height
130 // (one-line) if its explicit height is greater than or equal to its
131 // implicit height - i.e. if it has room for it. If it doesn't have
132 // room, just do what TextField does.
133 // We should also account for any topInset the user might have specified,
134 // which is useful to ensure that the text doesn't get clipped.
135 return ((m_controlImplicitBackgroundHeight - m_largestHeight) / 2.0)
136 + controlTopInset(textControl());
137 }
138
139 // When the placeholder text shouldn't float, it should sit in the middle of the TextField.
140 return (m_controlHeight - height()) / 2.0;
141}
142
143qreal QQuickMaterialPlaceholderText::floatingTargetY() const
144{
145 // For filled text fields, the placeholder text sits just above
146 // the text when floating.
147 if (m_filled)
148 return m_verticalPadding;
149
150 // Outlined text fields have the placeaholder vertically centered
151 // along the outline at the top.
152 return (-m_largestHeight / 2.0) + controlTopInset(textControl());
153}
154
155qreal QQuickMaterialPlaceholderText::normalTargetX() const
156{
157 return m_leftPadding;
158}
159
160qreal QQuickMaterialPlaceholderText::floatingTargetX() const
161{
162 return m_floatingLeftPadding;
163}
164
165/*!
166 \internal
167
168 The height of the text at its largest size that we set.
169*/
171{
172 return m_largestHeight;
173}
174
176{
177 return m_controlImplicitBackgroundHeight;
178}
179
180void QQuickMaterialPlaceholderText::setControlImplicitBackgroundHeight(qreal controlImplicitBackgroundHeight)
181{
182 if (qFuzzyCompare(m_controlImplicitBackgroundHeight, controlImplicitBackgroundHeight))
183 return;
184
185 m_controlImplicitBackgroundHeight = controlImplicitBackgroundHeight;
186 updateFocusAnimation();
187 emit controlImplicitBackgroundHeightChanged();
188}
189
190/*!
191 \internal
192
193 Exists so that we can call updateY when the control's height changes,
194 which is necessary for some y position calculations.
195
196 We don't really need it for the actual calculations, since we already
197 have access to the control, from which the property comes, but
198 it's simpler just to use it.
199*/
201{
202 return m_controlHeight;
203}
204
206{
207 if (qFuzzyCompare(m_controlHeight, controlHeight))
208 return;
209
210 m_controlHeight = controlHeight;
211 updateFocusAnimation();
212}
213
215{
216 return m_verticalPadding;
217}
218
220{
221 if (qFuzzyCompare(m_verticalPadding, verticalPadding))
222 return;
223
224 m_verticalPadding = verticalPadding;
225 updateFocusAnimation();
226 emit verticalPaddingChanged();
227}
228
230{
231 if (leftPadding == m_leftPadding)
232 return;
233
234 m_leftPadding = leftPadding;
235 updateFocusAnimation();
236}
237
239{
240 if (floatingLeftPadding == m_floatingLeftPadding)
241 return;
242
243 m_floatingLeftPadding = floatingLeftPadding;
244 updateFocusAnimation();
245}
246
247void QQuickMaterialPlaceholderText::adjustTransformOrigin()
248{
249 switch (effectiveHAlign()) {
250 case QQuickText::AlignLeft:
251 Q_FALLTHROUGH();
252 case QQuickText::AlignJustify:
253 setTransformOrigin(QQuickItem::Left);
254 break;
255 case QQuickText::AlignRight:
256 setTransformOrigin(QQuickItem::Right);
257 break;
258 case QQuickText::AlignHCenter:
259 setTransformOrigin(QQuickItem::Center);
260 break;
261 }
262}
263
264void QQuickMaterialPlaceholderText::controlActiveFocusChanged()
265{
266 if (m_focusAnimation) {
267 // Focus changes can happen before the animations finish.
268 // In that case, stop the animation, which will eventually delete it.
269 // Until it's deleted, we clear the pointer so that our asserts don't fail
270 // for the wrong reason.
271 m_focusAnimation->stop();
272 m_focusAnimation.clear();
273 }
274 updateFocusAnimation(true);
275}
276
277void QQuickMaterialPlaceholderText::updateFocusAnimation(bool createIfNeeded)
278{
279 if (shouldAnimate() && (m_focusAnimation || createIfNeeded)) {
280 int duration = 300;
281 if (m_focusAnimation) {
282 duration = m_focusAnimation->totalDuration() - m_focusAnimation->currentTime();
283 m_focusAnimation->stop();
284 m_focusAnimation.clear();
285 }
286
287 m_focusAnimation = new QParallelAnimationGroup(this);
288
289 auto *yAnimation = new QPropertyAnimation(this, "y", this);
290 yAnimation->setDuration(duration);
291 yAnimation->setStartValue(y());
292 yAnimation->setEndValue(shouldFloat() ? floatingTargetY() : normalTargetY());
293 yAnimation->setEasingCurve(*animationEasingCurve);
294 m_focusAnimation->addAnimation(yAnimation);
295
296 QPropertyAnimation *xAnimation = new QPropertyAnimation(this, "x", this);
297 xAnimation->setDuration(duration);
298 xAnimation->setStartValue(x());
299 xAnimation->setEndValue(shouldFloat() ? floatingTargetX() : normalTargetX());
300 xAnimation->setEasingCurve(*animationEasingCurve);
301 m_focusAnimation->addAnimation(xAnimation);
302
303 auto *scaleAnimation = new QPropertyAnimation(this, "scale", this);
304 scaleAnimation->setDuration(duration);
305 scaleAnimation->setStartValue(scale());
306 scaleAnimation->setEndValue(shouldFloat() ? floatingScale : 1.0);
307 yAnimation->setEasingCurve(*animationEasingCurve);
308 m_focusAnimation->addAnimation(scaleAnimation);
309
310 m_focusAnimation->start(QAbstractAnimation::DeleteWhenStopped);
311 } else {
312 if (m_focusAnimation) {
313 m_focusAnimation->stop();
314 m_focusAnimation.clear();
315 }
316 updateY();
317 updateX();
318 setScale(shouldFloat() ? floatingScale : 1.0);
319 }
320}
321
323{
324 QQuickPlaceholderText::componentComplete();
325
326 adjustTransformOrigin();
327
328 m_largestHeight = implicitHeight();
329 if (m_largestHeight > 0) {
330 emit largestHeightChanged();
331 } else {
332 qmlWarning(this) << "Expected implicitHeight of placeholder text" << text()
333 << "to be greater than 0 by component completion!";
334 }
335
336 updateFocusAnimation();
337}
338
339QT_END_NAMESPACE
void setControlImplicitBackgroundHeight(qreal controlImplicitBackgroundHeight)
void setFloatingLeftPadding(int floatingLeftPadding)
void componentComplete() override
\reimp Derived classes should call the base class method before adding their own actions to perform a...
void setControlHasActiveFocus(bool controlHasActiveFocus)
static QT_BEGIN_NAMESPACE const qreal floatingScale
Q_GLOBAL_STATIC(QEasingCurve, animationEasingCurve, QEasingCurve::OutSine)
qreal controlTopInset(QQuickItem *textControl)