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
qquickmaterialripple.cpp
Go to the documentation of this file.
1// Copyright (C) 2017 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/qmath.h>
8#include <QtQuick/private/qquickitem_p.h>
9#include <QtQuick/private/qsgadaptationlayer_p.h>
10#include <QtQuickControls2Impl/private/qquickanimatednode_p.h>
11#include <QtQuickTemplates2/private/qquickabstractbutton_p.h>
12#include <QtQuickTemplates2/private/qquickabstractbutton_p_p.h>
13
15
17
18static const int RIPPLE_ENTER_DELAY = 80;
19static const int OPACITY_ENTER_DURATION_FAST = 120;
20static const int WAVE_OPACITY_DECAY_DURATION = 333;
21static const qreal WAVE_TOUCH_DOWN_ACCELERATION = 1024.0;
22
24{
25public:
26 QQuickMaterialRippleWaveNode(QQuickMaterialRipple *ripple);
27
28 void exit();
30 void sync(QQuickItem *item) override;
31
32private:
33 qreal m_from = 0;
34 qreal m_to = 0;
35 qreal m_value = 0;
36 WavePhase m_phase = WaveEnter;
37 QPointF m_anchor;
38 QRectF m_bounds;
39};
40
43{
44 start(qRound(1000.0 * qSqrt(ripple->diameter() / 2.0 / WAVE_TOUCH_DOWN_ACCELERATION)));
45
46 QSGOpacityNode *opacityNode = new QSGOpacityNode;
47 appendChildNode(opacityNode);
48
49 QQuickItemPrivate *d = QQuickItemPrivate::get(ripple);
50 QSGInternalRectangleNode *rectNode = d->sceneGraphContext()->createInternalRectangleNode();
51 rectNode->setAntialiasing(true);
52 opacityNode->appendChildNode(rectNode);
53}
54
56{
57 m_phase = WaveExit;
58 m_from = m_value;
59 setDuration(WAVE_OPACITY_DECAY_DURATION);
60 restart();
61 connect(this, &QQuickAnimatedNode::stopped, this, &QObject::deleteLater);
62}
63
65{
66 qreal p = 1.0;
67 if (duration() > 0)
68 p = time / static_cast<qreal>(duration());
69
70 m_value = m_from + (m_to - m_from) * p;
71 p = m_value / m_to;
72
73 const qreal dx = (1.0 - p) * (m_anchor.x() - m_bounds.width() / 2);
74 const qreal dy = (1.0 - p) * (m_anchor.y() - m_bounds.height() / 2);
75
76 QMatrix4x4 m;
77 m.translate(qRound((m_bounds.width() - m_value) / 2 + dx),
78 qRound((m_bounds.height() - m_value) / 2 + dy));
79 setMatrix(m);
80
81 QSGOpacityNode *opacityNode = static_cast<QSGOpacityNode *>(firstChild());
82 Q_ASSERT(opacityNode->type() == QSGNode::OpacityNodeType);
83 qreal opacity = 1.0;
84 if (m_phase == WaveExit)
85 opacity -= static_cast<qreal>(time) / WAVE_OPACITY_DECAY_DURATION;
86 opacityNode->setOpacity(opacity);
87
88 QSGInternalRectangleNode *rectNode = static_cast<QSGInternalRectangleNode *>(opacityNode->firstChild());
89 Q_ASSERT(rectNode->type() == QSGNode::GeometryNodeType);
90 rectNode->setRect(QRectF(0, 0, m_value, m_value));
91 rectNode->setRadius(m_value / 2);
92 rectNode->update();
93}
94
95void QQuickMaterialRippleWaveNode::sync(QQuickItem *item)
96{
97 QQuickMaterialRipple *ripple = static_cast<QQuickMaterialRipple *>(item);
98 m_to = ripple->diameter();
99 m_anchor = ripple->anchorPoint();
100 m_bounds = ripple->boundingRect();
101
102 QSGOpacityNode *opacityNode = static_cast<QSGOpacityNode *>(firstChild());
103 Q_ASSERT(opacityNode->type() == QSGNode::OpacityNodeType);
104
105 QSGInternalRectangleNode *rectNode = static_cast<QSGInternalRectangleNode *>(opacityNode->firstChild());
106 Q_ASSERT(rectNode->type() == QSGNode::GeometryNodeType);
107 rectNode->setColor(ripple->color());
108}
109
111{
113
114public:
116
118 void sync(QQuickItem *item) override;
119
120private:
121 bool m_active = false;
122};
123
124QQuickMaterialRippleBackgroundNode::QQuickMaterialRippleBackgroundNode(QQuickMaterialRipple *ripple)
125 : QQuickAnimatedNode(ripple)
126{
127 setDuration(OPACITY_ENTER_DURATION_FAST);
128
129 QSGOpacityNode *opacityNode = new QSGOpacityNode;
130 opacityNode->setOpacity(0.0);
131 appendChildNode(opacityNode);
132
133 QQuickItemPrivate *d = QQuickItemPrivate::get(ripple);
134 QSGInternalRectangleNode *rectNode = d->sceneGraphContext()->createInternalRectangleNode();
135 rectNode->setAntialiasing(true);
136 opacityNode->appendChildNode(rectNode);
137}
138
140{
141 qreal opacity = time / static_cast<qreal>(duration());
142 if (!m_active)
143 opacity = 1.0 - opacity;
144
145 QSGOpacityNode *opacityNode = static_cast<QSGOpacityNode *>(firstChild());
146 Q_ASSERT(opacityNode->type() == QSGNode::OpacityNodeType);
147 opacityNode->setOpacity(opacity);
148}
149
151{
152 QQuickMaterialRipple *ripple = static_cast<QQuickMaterialRipple *>(item);
153 if (m_active != ripple->isActive()) {
154 m_active = ripple->isActive();
156 restart();
157 }
158
159 QSGOpacityNode *opacityNode = static_cast<QSGOpacityNode *>(firstChild());
160 Q_ASSERT(opacityNode->type() == QSGNode::OpacityNodeType);
161
162 QSGInternalRectangleNode *rectNode = static_cast<QSGInternalRectangleNode *>(opacityNode->firstChild());
163 Q_ASSERT(rectNode->type() == QSGNode::GeometryNodeType);
164
165 const qreal w = ripple->width();
166 const qreal h = ripple->height();
167 const qreal sz = qSqrt(w * w + h * h);
168
169 QMatrix4x4 matrix;
170 if (qFuzzyIsNull(ripple->clipRadius())) {
171 matrix.translate(qRound((w - sz) / 2), qRound((h - sz) / 2));
172 rectNode->setRect(QRectF(0, 0, sz, sz));
173 rectNode->setRadius(sz / 2);
174 } else {
175 rectNode->setRect(QRectF(0, 0, w, h));
176 rectNode->setRadius(ripple->clipRadius());
177 }
178
179 setMatrix(matrix);
180 rectNode->setColor(ripple->color());
181 rectNode->update();
182}
183
184QQuickMaterialRipple::QQuickMaterialRipple(QQuickItem *parent)
185 : QQuickItem(parent)
186{
187 setFlag(ItemHasContents);
188}
189
190bool QQuickMaterialRipple::isActive() const
191{
192 return m_active;
193}
194
195void QQuickMaterialRipple::setActive(bool active)
196{
197 if (active == m_active)
198 return;
199
200 m_active = active;
201 update();
202}
203
204QColor QQuickMaterialRipple::color() const
205{
206 return m_color;
207}
208
209void QQuickMaterialRipple::setColor(const QColor &color)
210{
211 if (m_color == color)
212 return;
213
214 m_color = color;
215 update();
216}
217
218qreal QQuickMaterialRipple::clipRadius() const
219{
220 return m_clipRadius;
221}
222
223void QQuickMaterialRipple::setClipRadius(qreal radius)
224{
225 if (qFuzzyCompare(m_clipRadius, radius))
226 return;
227
228 m_clipRadius = radius;
229 update();
230}
231
232bool QQuickMaterialRipple::isPressed() const
233{
234 return m_pressed;
235}
236
237void QQuickMaterialRipple::setPressed(bool pressed)
238{
239 if (pressed == m_pressed)
240 return;
241
242 m_pressed = pressed;
243
244 if (!isEnabled()) {
245 exitWave();
246 return;
247 }
248
249 if (pressed) {
250 if (m_trigger == Press)
251 prepareWave();
252 else
253 exitWave();
254 } else {
255 if (m_trigger == Release)
256 enterWave();
257 else
258 exitWave();
259 }
260}
261
262QQuickMaterialRipple::Trigger QQuickMaterialRipple::trigger() const
263{
264 return m_trigger;
265}
266
267void QQuickMaterialRipple::setTrigger(Trigger trigger)
268{
269 m_trigger = trigger;
270}
271
272QPointF QQuickMaterialRipple::anchorPoint() const
273{
274 const QRectF bounds = boundingRect();
275 const QPointF center = bounds.center();
276 if (!m_anchor)
277 return center;
278
279 QPointF anchorPoint = bounds.center();
280 if (QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(m_anchor))
281 anchorPoint = QQuickAbstractButtonPrivate::get(button)->pressPoint;
282 anchorPoint = mapFromItem(m_anchor, anchorPoint);
283
284 // calculate whether the anchor point is within the ripple circle bounds,
285 // that is, whether waves should start expanding from the anchor point
286 const qreal r = qSqrt(bounds.width() * bounds.width() + bounds.height() * bounds.height()) / 2;
287 if (QLineF(center, anchorPoint).length() < r)
288 return anchorPoint;
289
290 // if the anchor point is outside the ripple circle bounds, start expanding
291 // from the intersection point of the ripple circle and a line from its center
292 // to the the anchor point
293 const qreal p = qAtan2(anchorPoint.y() - center.y(), anchorPoint.x() - center.x());
294 return QPointF(center.x() + r * qCos(p), center.y() + r * qSin(p));
295}
296
297QQuickItem *QQuickMaterialRipple::anchor() const
298{
299 return m_anchor;
300}
301
302void QQuickMaterialRipple::setAnchor(QQuickItem *item)
303{
304 m_anchor = item;
305}
306
307qreal QQuickMaterialRipple::diameter() const
308{
309 const qreal w = width();
310 const qreal h = height();
311 return qSqrt(w * w + h * h);
312}
313
314void QQuickMaterialRipple::itemChange(ItemChange change, const ItemChangeData &data)
315{
316 QQuickItem::itemChange(change, data);
317}
318
319QSGNode *QQuickMaterialRipple::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
320{
321 QQuickItemPrivate *d = QQuickItemPrivate::get(this);
322 QQuickDefaultClipNode *clipNode = d->clipNode();
323 if (clipNode) {
324 clipNode->setRadius(m_clipRadius);
325 clipNode->setRect(boundingRect());
326 clipNode->update();
327 }
328
329 QSGNode *container = oldNode;
330 if (!container)
331 container = new QSGNode;
332
333 QQuickMaterialRippleBackgroundNode *backgroundNode = static_cast<QQuickMaterialRippleBackgroundNode *>(container->firstChild());
334 if (!backgroundNode) {
335 backgroundNode = new QQuickMaterialRippleBackgroundNode(this);
336 backgroundNode->setObjectName(objectName());
337 container->appendChildNode(backgroundNode);
338 }
339 backgroundNode->sync(this);
340
341 // enter new waves
342 int i = m_waves;
343 QQuickMaterialRippleWaveNode *enterNode = static_cast<QQuickMaterialRippleWaveNode *>(backgroundNode->nextSibling());
344 while (i-- > 0) {
345 if (!enterNode) {
346 enterNode = new QQuickMaterialRippleWaveNode(this);
347 container->appendChildNode(enterNode);
348 }
349 enterNode->sync(this);
350 enterNode = static_cast<QQuickMaterialRippleWaveNode *>(enterNode->nextSibling());
351 }
352
353 // exit old waves
354 int j = container->childCount() - 1 - m_waves;
355 while (j-- > 0) {
356 QQuickMaterialRippleWaveNode *exitNode = static_cast<QQuickMaterialRippleWaveNode *>(backgroundNode->nextSibling());
357 if (exitNode) {
358 exitNode->exit();
359 exitNode->sync(this);
360 }
361 }
362
363 return container;
364}
365
366void QQuickMaterialRipple::timerEvent(QTimerEvent *event)
367{
368 QQuickItem::timerEvent(event);
369
370 if (event->timerId() == m_enterDelay)
371 enterWave();
372}
373
374void QQuickMaterialRipple::prepareWave()
375{
376 if (m_enterDelay <= 0)
377 m_enterDelay = startTimer(RIPPLE_ENTER_DELAY);
378}
379
380void QQuickMaterialRipple::enterWave()
381{
382 if (m_enterDelay > 0) {
383 killTimer(m_enterDelay);
384 m_enterDelay = 0;
385 }
386
387 ++m_waves;
388 update();
389}
390
391void QQuickMaterialRipple::exitWave()
392{
393 if (m_enterDelay > 0) {
394 killTimer(m_enterDelay);
395 m_enterDelay = 0;
396 }
397
398 if (m_waves > 0) {
399 --m_waves;
400 update();
401 }
402}
403
404QT_END_NAMESPACE
405
406#include "moc_qquickmaterialripple_p.cpp"
407
408#include "qquickmaterialripple.moc"
void sync(QQuickItem *item) override
void updateCurrentTime(int time) override
void sync(QQuickItem *item) override
QQuickMaterialRippleWaveNode(QQuickMaterialRipple *ripple)
static const int WAVE_OPACITY_DECAY_DURATION
static const int RIPPLE_ENTER_DELAY
static const int OPACITY_ENTER_DURATION_FAST
static const qreal WAVE_TOUCH_DOWN_ACCELERATION