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
qquickmaterialtextcontainer.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 <QtGui/qpainter.h>
8#include <QtGui/qpainterpath.h>
9#include <QtQml/qqmlinfo.h>
10
12
13/*
14 This class exists because:
15
16 - Rectangle doesn't support individual radii for each corner (QTBUG-48774).
17 - We need to draw an interrupted (where the placeholder text is) line for outlined containers.
18 - We need to animate the focus line for filled containers, and we can't use "Behavior on"
19 syntax because we only want to animate activeFocus becoming true, not also false. To do this
20 requires imperative code, and we want to keep the QML declarative.
21
22 focusAnimationProgress has to be a property even though it's only used internally,
23 because we have to use QPropertyAnimation on it.
24
25 An advantage of doing the animation in C++ is that we avoid the memory
26 overhead of an animation instance even when we're not using it, and instead
27 create it on demand and delete it when it's done. I tried doing the animation
28 declaratively with states and transitions, but it was more difficult to implement
29 and would have been harder to maintain, as well as having more overhead.
30*/
31
36
38{
39 return m_filled;
40}
41
43{
44 if (filled == m_filled)
45 return;
46
47 m_filled = filled;
48 update();
49}
50
52{
53 return m_fillColor;
54}
55
57{
58 if (fillColor == m_fillColor)
59 return;
60
61 m_fillColor = fillColor;
62 update();
63}
64
66{
67 return m_outlineColor;
68}
69
71{
72 if (outlineColor == m_outlineColor)
73 return;
74
75 m_outlineColor = outlineColor;
76 update();
77}
78
80{
81 return m_outlineColor;
82}
83
85{
86 if (focusedOutlineColor == m_focusedOutlineColor)
87 return;
88
89 m_focusedOutlineColor = focusedOutlineColor;
90 update();
91}
92
94{
95 return m_focusAnimationProgress;
96}
97
99{
100 if (qFuzzyCompare(progress, m_focusAnimationProgress))
101 return;
102
103 m_focusAnimationProgress = progress;
104 update();
105}
106
108{
109 return m_placeholderTextWidth;
110}
111
113{
114 if (qFuzzyCompare(placeholderTextWidth, m_placeholderTextWidth))
115 return;
116
117 m_placeholderTextWidth = placeholderTextWidth;
118 update();
119}
120
125
127{
128 if (m_placeholderTextHAlign == placeholderTextHAlign)
129 return;
130
131 m_placeholderTextHAlign = placeholderTextHAlign;
132 update();
133}
134
136{
137 return m_controlHasActiveFocus;
138}
139
141{
142 if (m_controlHasActiveFocus == controlHasActiveFocus)
143 return;
144
145 m_controlHasActiveFocus = controlHasActiveFocus;
146 if (m_controlHasActiveFocus)
147 controlGotActiveFocus();
148 else
149 controlLostActiveFocus();
151}
152
154{
155 return m_controlHasText;
156}
157
159{
160 if (m_controlHasText == controlHasText)
161 return;
162
163 m_controlHasText = controlHasText;
164 // TextArea's text length is updated after component completion,
165 // so account for that here and in setPlaceholderHasText().
166 maybeSetFocusAnimationProgress();
167 update();
169}
170
172{
173 return m_placeholderHasText;
174}
175
177{
178 if (m_placeholderHasText == placeholderHasText)
179 return;
180
181 m_placeholderHasText = placeholderHasText;
182 maybeSetFocusAnimationProgress();
183 update();
185}
186
188{
189 return m_horizontalPadding;
190}
191
201{
202 if (m_horizontalPadding == horizontalPadding)
203 return;
204 m_horizontalPadding = horizontalPadding;
205 update();
207}
208
210{
211 qreal w = width();
212 qreal h = height();
213 if (w <= 0 || h <= 0)
214 return;
215
216 // Account for pen width.
217 const qreal penWidth = m_filled ? 1 : (m_controlHasActiveFocus ? 2 : 1);
218 w -= penWidth;
219 h -= penWidth;
220
221 const qreal cornerRadius = 4;
222 // This is coincidentally the same as cornerRadius, but use different variable names
223 // to keep the code understandable.
224 const qreal gapPadding = 4;
225 // When animating focus on outlined containers, we need to make a gap
226 // at the top left for the placeholder text.
227 // If the text is too wide for the container, it will be elided, so
228 // we shouldn't need to clamp its width here. TODO: check that this is the case for TextArea.
229 const qreal halfPlaceholderWidth = m_placeholderTextWidth / 2;
230 // Take care of different Alignment cases for the placeholder text.
231 qreal gapCenterX;
232 switch (m_placeholderTextHAlign) {
234 gapCenterX = width() / 2;
235 break;
237 gapCenterX = width() - halfPlaceholderWidth - m_horizontalPadding;
238 break;
239 default:
240 gapCenterX = m_horizontalPadding + halfPlaceholderWidth;
241 break;
242 }
243
245
246 QPointF startPos;
247
248 // Top-left rounded corner.
249 if (m_filled || m_focusAnimationProgress == 0) {
250 startPos = QPointF(cornerRadius, 0);
251 } else {
252 // Start at the center of the gap and animate outwards towards the left-hand side.
253 // Subtract gapPadding to account for the gap between the line and the placeholder text.
254 // Also subtract the pen width because otherwise it extends by that distance too much to the right.
255 // Changing the cap style to Qt::FlatCap would only fix this by half the pen width,
256 // but it has no effect anyway (perhaps it literally only affects end points and not "start" points?).
257 startPos = QPointF(gapCenterX - (m_focusAnimationProgress * halfPlaceholderWidth) - gapPadding - penWidth, 0);
258 }
259 path.moveTo(startPos);
260 path.arcTo(0, 0, cornerRadius * 2, cornerRadius * 2, 90, 90);
261
262 // Bottom-left corner.
263 if (m_filled) {
264 path.lineTo(0, h);
265 } else {
266 path.lineTo(0, h - cornerRadius * 2);
267 path.arcTo(0, h - cornerRadius * 2, cornerRadius * 2, cornerRadius * 2, 180, 90);
268 }
269
270 // Bottom-right corner.
271 if (m_filled) {
272 path.lineTo(w, h);
273 } else {
274 path.lineTo(w - cornerRadius * 2, h);
275 path.arcTo(w - cornerRadius * 2, h - cornerRadius * 2, cornerRadius * 2, cornerRadius * 2, 270, 90);
276 }
277
278 // Top-right rounded corner.
279 path.lineTo(w, cornerRadius);
280 path.arcTo(w - (cornerRadius * 2), 0, cornerRadius * 2, cornerRadius * 2, 0, 90);
281
282 if (m_filled || qFuzzyIsNull(m_focusAnimationProgress)) {
283 // Back to the start.
284 path.lineTo(startPos.x(), startPos.y());
285 } else {
286 path.lineTo(gapCenterX + (m_focusAnimationProgress * halfPlaceholderWidth) + gapPadding, startPos.y());
287 }
288
289 // Account for pen width.
290 painter->translate(penWidth / 2, penWidth / 2);
291
293
294 auto control = textControl();
295 const bool focused = control && control->hasActiveFocus();
296 // We still want to draw the stroke when it's filled, otherwise it will be a pixel
297 // (the pen width) too narrow on either side.
298 QPen pen;
299 pen.setColor(m_filled ? m_fillColor : (focused ? m_focusedOutlineColor : m_outlineColor));
300 pen.setWidthF(penWidth);
301 painter->setPen(pen);
302 if (m_filled)
303 painter->setBrush(QBrush(m_fillColor));
304
305 // Fill or stroke the container's shape.
306 // If not filling, the default brush will be used, which is Qt::NoBrush.
308
309 // Draw the focus line at the bottom for filled containers.
310 if (m_filled) {
311 if (!qFuzzyCompare(m_focusAnimationProgress, 1.0)) {
312 // Draw the enabled active indicator line (#10) that's at the bottom when it's not focused:
313 // https://m3.material.io/components/text-fields/specs#6d654d1d-262e-4697-858c-9a75e8e7c81d
314 // Don't bother drawing it when the animation has finished, as the focused active indicator
315 // line below will obscure it.
316 pen.setColor(m_outlineColor);
317 painter->setPen(pen);
318 painter->drawLine(0, h, w, h);
319 }
320
321 if (!qFuzzyIsNull(m_focusAnimationProgress)) {
322 // Draw the focused active indicator line (#6) that's at the bottom when it's focused.
323 // Start at the center and expand outwards.
324 const int lineLength = m_focusAnimationProgress * w;
325 const int horizontalCenter = w / 2;
326 pen.setColor(m_focusedOutlineColor);
327 pen.setWidth(2);
328 painter->setPen(pen);
329 painter->drawLine(horizontalCenter - (lineLength / 2), h,
330 horizontalCenter + (lineLength / 2) + pen.width() / 2, h);
331 }
332 }
333}
334
335bool QQuickMaterialTextContainer::shouldAnimateOutline() const
336{
337 return !m_controlHasText && m_placeholderHasText;
338}
339
345QQuickItem *QQuickMaterialTextContainer::textControl() const
346{
348}
349
350void QQuickMaterialTextContainer::controlGotActiveFocus()
351{
352 const bool shouldAnimate = m_filled ? !m_controlHasText : shouldAnimateOutline();
353 if (!shouldAnimate) {
354 // It does have focus, but sometimes we don't need to animate anything, just change colors.
355 if (m_filled && m_controlHasText) {
356 // When a filled container has text already entered, we should just immediately change
357 // the color and thickness of the indicator line.
358 m_focusAnimationProgress = 1;
359 }
360 update();
361 return;
362 }
363
364 startFocusAnimation();
365}
366
367void QQuickMaterialTextContainer::controlLostActiveFocus()
368{
369 // We don't want to animate the active indicator line (at the bottom) of filled containers
370 // when the control loses focus, only when it gets it.
371 if (m_filled || !shouldAnimateOutline()) {
372 // Ensure that we set this so that filled containers go back to a non-accent-colored
373 // active indicator line when losing focus.
374 if (m_filled)
375 m_focusAnimationProgress = 0;
376 update();
377 return;
378 }
379
380 QPropertyAnimation *animation = new QPropertyAnimation(this, "focusAnimationProgress", this);
385}
386
387void QQuickMaterialTextContainer::startFocusAnimation()
388{
389 // Each time setFocusAnimationProgress is called by the animation, it'll call update(),
390 // which will cause us to be re-rendered.
391 QPropertyAnimation *animation = new QPropertyAnimation(this, "focusAnimationProgress", this);
396}
397
398void QQuickMaterialTextContainer::maybeSetFocusAnimationProgress()
399{
400 if (m_filled)
401 return;
402
403 if (m_controlHasText && m_placeholderHasText) {
404 // Show the interrupted outline when there is text.
406 } else if (!m_controlHasText && !m_controlHasActiveFocus) {
407 // If the text was cleared while it didn't have focus, don't animate, just close the gap.
409 }
410}
411
413{
415
416 if (!parentItem())
417 qmlWarning(this) << "Expected parent item by component completion!";
418
419 maybeSetFocusAnimationProgress();
420}
421
void start(QAbstractAnimation::DeletionPolicy policy=KeepWhenStopped)
Starts the animation.
\inmodule QtGui
Definition qbrush.h:30
The QColor class provides colors based on RGB, HSV or CMYK values.
Definition qcolor.h:31
\inmodule QtGui
The QPainter class performs low-level painting on widgets and other paint devices.
Definition qpainter.h:46
void drawPath(const QPainterPath &path)
Draws the given painter path using the current pen for outline and the current brush for filling.
void setPen(const QColor &color)
This is an overloaded member function, provided for convenience. It differs from the above function o...
void drawLine(const QLineF &line)
Draws a line defined by line.
Definition qpainter.h:442
void setBrush(const QBrush &brush)
Sets the painter's brush to the given brush.
@ Antialiasing
Definition qpainter.h:52
void translate(const QPointF &offset)
Translates the coordinate system by the given offset; i.e.
void setRenderHint(RenderHint hint, bool on=true)
Sets the given render hint on the painter if on is true; otherwise clears the render hint.
\inmodule QtGui
Definition qpen.h:28
void setWidth(int width)
Sets the pen width to the given width in pixels with integer precision.
Definition qpen.cpp:592
void setWidthF(qreal width)
Sets the pen width to the given width in pixels with floating point precision.
Definition qpen.cpp:618
int width() const
Returns the pen width with integer precision.
Definition qpen.cpp:560
void setColor(const QColor &color)
Sets the color of this pen's brush to the given color.
Definition qpen.cpp:705
\inmodule QtCore\reentrant
Definition qpoint.h:217
constexpr qreal x() const noexcept
Returns the x coordinate of this point.
Definition qpoint.h:343
constexpr qreal y() const noexcept
Returns the y coordinate of this point.
Definition qpoint.h:348
virtual void componentComplete()=0
Invoked after the root component that caused this instantiation has completed construction.
The QQuickItem class provides the most basic of all visual items in \l {Qt Quick}.
Definition qquickitem.h:63
qreal width
This property holds the width of this item.
Definition qquickitem.h:75
QQuickItem * parentItem() const
QQuickItem * parent
\qmlproperty Item QtQuick::Item::parent This property holds the visual parent of the item.
Definition qquickitem.h:67
qreal height
This property holds the height of this item.
Definition qquickitem.h:76
void update()
Schedules a call to updatePaintNode() for this item.
void setFocusedOutlineColor(const QColor &focusedOutlineColor)
void componentComplete() override
\reimp Derived classes should call the base class method before adding their own actions to perform a...
void setOutlineColor(const QColor &outlineColor)
void setControlHasActiveFocus(bool controlHasActiveFocus)
void setControlHasText(bool controlHasText)
void setHorizontalPadding(int horizontalPadding)
void setPlaceholderTextHAlign(PlaceHolderHAlignment placeHolderTextHAlign)
void setPlaceholderTextWidth(qreal placeholderTextWidth)
void setFillColor(const QColor &fillColor)
void setPlaceholderHasText(bool placeholderHasText)
void paint(QPainter *painter) override
This function, which is usually called by the QML Scene Graph, paints the contents of an item in loca...
QQuickMaterialTextContainer(QQuickItem *parent=nullptr)
The QQuickPaintedItem class provides a way to use the QPainter API in the QML Scene Graph.
void setStartValue(const QVariant &value)
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:333
bool qFuzzyIsNull(qfloat16 f) noexcept
Definition qfloat16.h:349
GLfloat GLfloat GLfloat w
[0]
GLfloat GLfloat GLfloat GLfloat h
GLsizei const GLchar *const * path
Q_QML_EXPORT QQmlInfo qmlWarning(const QObject *me)
QQuickItem * qobject_cast< QQuickItem * >(QObject *o)
Definition qquickitem.h:492
#define emit
double qreal
Definition qtypes.h:187
QPropertyAnimation animation
[0]
QPainter painter(this)
[7]