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
4#include "qquickmaterialplaceholdertext_p.h"
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
15QT_BEGIN_NAMESPACE
16
17static const qreal floatingScale = 0.8;
18Q_GLOBAL_STATIC(QEasingCurve, animationEasingCurve, QEasingCurve::OutSine);
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
29QQuickMaterialPlaceholderText::QQuickMaterialPlaceholderText(QQuickItem *parent)
30 : QQuickPlaceholderText(parent)
31{
32 connect(sender: this, signal: &QQuickMaterialPlaceholderText::effectiveHorizontalAlignmentChanged,
33 context: this, slot: &QQuickMaterialPlaceholderText::adjustTransformOrigin);
34}
35
36bool QQuickMaterialPlaceholderText::isFilled() const
37{
38 return m_filled;
39}
40
41void QQuickMaterialPlaceholderText::setFilled(bool filled)
42{
43 if (filled == m_filled)
44 return;
45
46 m_filled = filled;
47 update();
48 void filledChanged();
49}
50
51bool QQuickMaterialPlaceholderText::controlHasActiveFocus() const
52{
53 return m_controlHasActiveFocus;
54}
55
56void QQuickMaterialPlaceholderText::setControlHasActiveFocus(bool controlHasActiveFocus)
57{
58 if (m_controlHasActiveFocus == controlHasActiveFocus)
59 return;
60
61 m_controlHasActiveFocus = controlHasActiveFocus;
62 if (m_controlHasActiveFocus)
63 controlGotActiveFocus();
64 else
65 controlLostActiveFocus();
66 emit controlHasActiveFocusChanged();
67}
68
69bool QQuickMaterialPlaceholderText::controlHasText() const
70{
71 return m_controlHasText;
72}
73
74void QQuickMaterialPlaceholderText::setControlHasText(bool controlHasText)
75{
76 if (m_controlHasText == controlHasText)
77 return;
78
79 m_controlHasText = controlHasText;
80 maybeSetFocusAnimationProgress();
81 emit controlHasTextChanged();
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
110qreal controlTopInset(QQuickItem *textControl)
111{
112 if (const auto textArea = qobject_cast<QQuickTextArea *>(object: textControl))
113 return textArea->topInset();
114
115 if (const auto textField = qobject_cast<QQuickTextField *>(object: textControl))
116 return textField->topInset();
117
118 return 0;
119}
120
121qreal QQuickMaterialPlaceholderText::normalTargetY() const
122{
123 auto *textArea = qobject_cast<QQuickTextArea *>(object: 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)
133 + controlTopInset(textControl: textControl());
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: textControl());
150}
151
152/*!
153 \internal
154
155 The height of the text at its largest size that we set.
156*/
157int QQuickMaterialPlaceholderText::largestHeight() const
158{
159 return m_largestHeight;
160}
161
162qreal QQuickMaterialPlaceholderText::controlImplicitBackgroundHeight() const
163{
164 return m_controlImplicitBackgroundHeight;
165}
166
167void QQuickMaterialPlaceholderText::setControlImplicitBackgroundHeight(qreal controlImplicitBackgroundHeight)
168{
169 if (qFuzzyCompare(p1: m_controlImplicitBackgroundHeight, p2: controlImplicitBackgroundHeight))
170 return;
171
172 m_controlImplicitBackgroundHeight = controlImplicitBackgroundHeight;
173 updateY();
174 emit controlImplicitBackgroundHeightChanged();
175}
176
177/*!
178 \internal
179
180 Exists so that we can call updateY when the control's height changes,
181 which is necessary for some y position calculations.
182
183 We don't really need it for the actual calculations, since we already
184 have access to the control, from which the property comes, but
185 it's simpler just to use it.
186*/
187qreal QQuickMaterialPlaceholderText::controlHeight() const
188{
189 return m_controlHeight;
190}
191
192void QQuickMaterialPlaceholderText::setControlHeight(qreal controlHeight)
193{
194 if (qFuzzyCompare(p1: m_controlHeight, p2: controlHeight))
195 return;
196
197 m_controlHeight = controlHeight;
198 updateY();
199}
200
201qreal QQuickMaterialPlaceholderText::verticalPadding() const
202{
203 return m_verticalPadding;
204}
205
206void QQuickMaterialPlaceholderText::setVerticalPadding(qreal verticalPadding)
207{
208 if (qFuzzyCompare(p1: m_verticalPadding, p2: verticalPadding))
209 return;
210
211 m_verticalPadding = verticalPadding;
212 emit verticalPaddingChanged();
213}
214
215void QQuickMaterialPlaceholderText::adjustTransformOrigin()
216{
217 switch (effectiveHAlign()) {
218 case QQuickText::AlignLeft:
219 Q_FALLTHROUGH();
220 case QQuickText::AlignJustify:
221 setTransformOrigin(QQuickItem::Left);
222 break;
223 case QQuickText::AlignRight:
224 setTransformOrigin(QQuickItem::Right);
225 break;
226 case QQuickText::AlignHCenter:
227 setTransformOrigin(QQuickItem::Center);
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(animation: 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(animation: scaleAnimation);
260
261 m_focusInAnimation->start(policy: 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(animation: 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(animation: scaleAnimation);
291
292 m_focusOutAnimation->start(policy: QAbstractAnimation::DeleteWhenStopped);
293 } else {
294 updateY();
295 }
296}
297
298void QQuickMaterialPlaceholderText::maybeSetFocusAnimationProgress()
299{
300 updateY();
301 setScale(shouldFloat() ? floatingScale : 1.0);
302}
303
304void QQuickMaterialPlaceholderText::componentComplete()
305{
306 QQuickPlaceholderText::componentComplete();
307
308 adjustTransformOrigin();
309
310 m_largestHeight = implicitHeight();
311 if (m_largestHeight > 0) {
312 emit largestHeightChanged();
313 } else {
314 qmlWarning(me: this) << "Expected implicitHeight of placeholder text" << text()
315 << "to be greater than 0 by component completion!";
316 }
317
318 maybeSetFocusAnimationProgress();
319}
320
321QT_END_NAMESPACE
322

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of qtdeclarative/src/quickcontrols/material/impl/qquickmaterialplaceholdertext.cpp