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 void updateWaveNode();
34
35 qreal m_from = 0;
36 qreal m_to = 0;
37 qreal m_value = 0;
38 qreal m_newValue = 0;
39
40 WavePhase m_phase = WaveEnter;
41 QPointF m_anchor;
42 QRectF m_bounds;
43
44 qreal m_opacity = 1.0;
45 qreal m_newOpacity = 1.0;
46
47 QQuickWindow *m_window = nullptr;
48 QMetaObject::Connection m_animationConnection;
49};
50
53{
54 start(qRound(1000.0 * qSqrt(ripple->diameter() / 2.0 / WAVE_TOUCH_DOWN_ACCELERATION)));
55
56 QSGOpacityNode *opacityNode = new QSGOpacityNode;
57 appendChildNode(opacityNode);
58
59 QQuickItemPrivate *d = QQuickItemPrivate::get(ripple);
60 QSGInternalRectangleNode *rectNode = d->sceneGraphContext()->createInternalRectangleNode();
61 rectNode->setAntialiasing(true);
62 opacityNode->appendChildNode(rectNode);
63
64 m_window = ripple->window();
65
66 connect(this, &QQuickAnimatedNode::started, this, [this]() {
67 this->m_animationConnection = connect(
68 this->m_window,
69 &QQuickWindow::beforeFrameBegin,
70 this,
71 &QQuickMaterialRippleWaveNode::updateWaveNode,
72 Qt::DirectConnection);
73 });
74 connect(this, &QQuickAnimatedNode::stopped, this, [this]() {
75 disconnect(this->m_animationConnection);
76 });
77}
78
80{
81 m_phase = WaveExit;
82 m_from = m_value;
83 setDuration(WAVE_OPACITY_DECAY_DURATION);
84 restart();
85 connect(this, &QQuickAnimatedNode::stopped, this, &QObject::deleteLater);
86}
87
89{
90 qreal p = 1.0;
91 if (duration() > 0)
92 p = time / static_cast<qreal>(duration());
93
94 m_newValue = m_from + (m_to - m_from) * p;
95
96 if (m_phase == WaveExit)
97 m_newOpacity = 1.0 - static_cast<qreal>(time) / WAVE_OPACITY_DECAY_DURATION;
98}
99
100void QQuickMaterialRippleWaveNode::sync(QQuickItem *item)
101{
102 QQuickMaterialRipple *ripple = static_cast<QQuickMaterialRipple *>(item);
103 m_to = ripple->diameter();
104 m_anchor = ripple->anchorPoint();
105 m_bounds = ripple->boundingRect();
106
107 QSGOpacityNode *opacityNode = static_cast<QSGOpacityNode *>(firstChild());
108 Q_ASSERT(opacityNode->type() == QSGNode::OpacityNodeType);
109
110 QSGInternalRectangleNode *rectNode = static_cast<QSGInternalRectangleNode *>(opacityNode->firstChild());
111 Q_ASSERT(rectNode->type() == QSGNode::GeometryNodeType);
112 rectNode->setColor(ripple->color());
113}
114
115void QQuickMaterialRippleWaveNode::updateWaveNode()
116{
117 QSGOpacityNode *opacityNode = static_cast<QSGOpacityNode *>(firstChild());
118 Q_ASSERT(opacityNode->type() == QSGNode::OpacityNodeType);
119 if (!qFuzzyCompare(m_opacity, m_newOpacity)) {
120 m_opacity = m_newOpacity;
121 opacityNode->setOpacity(m_opacity);
122 }
123
124 if (qFuzzyCompare(m_value, m_newValue))
125 return;
126
127 m_value = m_newValue;
128
129 qreal p = m_value / m_to;
130
131 const qreal dx = (1.0 - p) * (m_anchor.x() - m_bounds.width() / 2);
132 const qreal dy = (1.0 - p) * (m_anchor.y() - m_bounds.height() / 2);
133
134 QMatrix4x4 m;
135 m.translate(qRound((m_bounds.width() - m_value) / 2 + dx),
136 qRound((m_bounds.height() - m_value) / 2 + dy));
137 setMatrix(m);
138
139 QSGInternalRectangleNode *rectNode = static_cast<QSGInternalRectangleNode *>(opacityNode->firstChild());
140 Q_ASSERT(rectNode->type() == QSGNode::GeometryNodeType);
141 rectNode->setRect(QRectF(0, 0, m_value, m_value));
142 rectNode->setRadius(m_value / 2);
143 rectNode->update();
144}
145
147{
149
150public:
152
154 void sync(QQuickItem *item) override;
155
156private:
157 void updateBackgroundNode();
158
159 bool m_active = false;
160
161 qreal m_opacity = 0.0;
162 qreal m_newOpacity = 0.0;
163
164 QQuickWindow *m_window = nullptr;
165 QMetaObject::Connection m_animationConnection;
166};
167
168QQuickMaterialRippleBackgroundNode::QQuickMaterialRippleBackgroundNode(QQuickMaterialRipple *ripple)
169 : QQuickAnimatedNode(ripple)
170{
171 setDuration(OPACITY_ENTER_DURATION_FAST);
172
173 QSGOpacityNode *opacityNode = new QSGOpacityNode;
174 opacityNode->setOpacity(0.0);
175 appendChildNode(opacityNode);
176
177 QQuickItemPrivate *d = QQuickItemPrivate::get(ripple);
178 QSGInternalRectangleNode *rectNode = d->sceneGraphContext()->createInternalRectangleNode();
179 rectNode->setAntialiasing(true);
180 opacityNode->appendChildNode(rectNode);
181
182 m_window = ripple->window();
183 connect(this, &QQuickAnimatedNode::started, this, [this]() {
184 this->m_animationConnection = connect(
185 this->m_window,
186 &QQuickWindow::beforeFrameBegin,
187 this,
188 &QQuickMaterialRippleBackgroundNode::updateBackgroundNode,
189 Qt::DirectConnection);
190 });
191 connect(this, &QQuickAnimatedNode::stopped,
192 this, [this]() { disconnect(this->m_animationConnection); });
193}
194
196{
197 qreal opacity = time / static_cast<qreal>(duration());
198 if (!m_active)
199 opacity = 1.0 - opacity;
200
201 QSGOpacityNode *opacityNode = static_cast<QSGOpacityNode *>(firstChild());
202 Q_ASSERT(opacityNode->type() == QSGNode::OpacityNodeType);
203 m_newOpacity = opacity;
204}
205
207{
208 QQuickMaterialRipple *ripple = static_cast<QQuickMaterialRipple *>(item);
209 if (m_active != ripple->isActive()) {
210 m_active = ripple->isActive();
212 restart();
213 }
214
215 QSGOpacityNode *opacityNode = static_cast<QSGOpacityNode *>(firstChild());
216 Q_ASSERT(opacityNode->type() == QSGNode::OpacityNodeType);
217
218 QSGInternalRectangleNode *rectNode = static_cast<QSGInternalRectangleNode *>(opacityNode->firstChild());
219 Q_ASSERT(rectNode->type() == QSGNode::GeometryNodeType);
220
221 const qreal w = ripple->width();
222 const qreal h = ripple->height();
223 const qreal sz = qSqrt(w * w + h * h);
224
225 QMatrix4x4 matrix;
226 if (qFuzzyIsNull(ripple->clipRadius())) {
227 matrix.translate(qRound((w - sz) / 2), qRound((h - sz) / 2));
228 rectNode->setRect(QRectF(0, 0, sz, sz));
229 rectNode->setRadius(sz / 2);
230 } else {
231 rectNode->setRect(QRectF(0, 0, w, h));
232 rectNode->setRadius(ripple->clipRadius());
233 }
234
235 setMatrix(matrix);
236 rectNode->setColor(ripple->color());
237 rectNode->update();
238}
239
240void QQuickMaterialRippleBackgroundNode::updateBackgroundNode()
241{
242 if (m_opacity == m_newOpacity)
243 return;
244
245 m_opacity = m_newOpacity;
246 QSGOpacityNode *opacityNode = static_cast<QSGOpacityNode *>(firstChild());
247 Q_ASSERT(opacityNode->type() == QSGNode::OpacityNodeType);
248 opacityNode->setOpacity(m_opacity);
249}
250
251QQuickMaterialRipple::QQuickMaterialRipple(QQuickItem *parent)
252 : QQuickItem(parent)
253{
254 setFlag(ItemHasContents);
255}
256
257bool QQuickMaterialRipple::isActive() const
258{
259 return m_active;
260}
261
262void QQuickMaterialRipple::setActive(bool active)
263{
264 if (active == m_active)
265 return;
266
267 m_active = active;
268 update();
269}
270
271QColor QQuickMaterialRipple::color() const
272{
273 return m_color;
274}
275
276void QQuickMaterialRipple::setColor(const QColor &color)
277{
278 if (m_color == color)
279 return;
280
281 m_color = color;
282 update();
283}
284
285qreal QQuickMaterialRipple::clipRadius() const
286{
287 return m_clipRadius;
288}
289
290void QQuickMaterialRipple::setClipRadius(qreal radius)
291{
292 if (qFuzzyCompare(m_clipRadius, radius))
293 return;
294
295 m_clipRadius = radius;
296 update();
297}
298
299bool QQuickMaterialRipple::isPressed() const
300{
301 return m_pressed;
302}
303
304void QQuickMaterialRipple::setPressed(bool pressed)
305{
306 if (pressed == m_pressed)
307 return;
308
309 m_pressed = pressed;
310
311 if (!isEnabled()) {
312 exitWave();
313 return;
314 }
315
316 if (pressed) {
317 if (m_trigger == Press)
318 prepareWave();
319 else
320 exitWave();
321 } else {
322 if (m_trigger == Release)
323 enterWave();
324 else
325 exitWave();
326 }
327}
328
329QQuickMaterialRipple::Trigger QQuickMaterialRipple::trigger() const
330{
331 return m_trigger;
332}
333
334void QQuickMaterialRipple::setTrigger(Trigger trigger)
335{
336 m_trigger = trigger;
337}
338
339QPointF QQuickMaterialRipple::anchorPoint() const
340{
341 const QRectF bounds = boundingRect();
342 const QPointF center = bounds.center();
343 if (!m_anchor)
344 return center;
345
346 QPointF anchorPoint = bounds.center();
347 if (QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(m_anchor))
348 anchorPoint = QQuickAbstractButtonPrivate::get(button)->pressPoint;
349 anchorPoint = mapFromItem(m_anchor, anchorPoint);
350
351 // calculate whether the anchor point is within the ripple circle bounds,
352 // that is, whether waves should start expanding from the anchor point
353 const qreal r = qSqrt(bounds.width() * bounds.width() + bounds.height() * bounds.height()) / 2;
354 if (QLineF(center, anchorPoint).length() < r)
355 return anchorPoint;
356
357 // if the anchor point is outside the ripple circle bounds, start expanding
358 // from the intersection point of the ripple circle and a line from its center
359 // to the the anchor point
360 const qreal p = qAtan2(anchorPoint.y() - center.y(), anchorPoint.x() - center.x());
361 return QPointF(center.x() + r * qCos(p), center.y() + r * qSin(p));
362}
363
364QQuickItem *QQuickMaterialRipple::anchor() const
365{
366 return m_anchor;
367}
368
369void QQuickMaterialRipple::setAnchor(QQuickItem *item)
370{
371 m_anchor = item;
372}
373
374qreal QQuickMaterialRipple::diameter() const
375{
376 const qreal w = width();
377 const qreal h = height();
378 return qSqrt(w * w + h * h);
379}
380
381void QQuickMaterialRipple::itemChange(ItemChange change, const ItemChangeData &data)
382{
383 QQuickItem::itemChange(change, data);
384}
385
386QSGNode *QQuickMaterialRipple::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
387{
388 QQuickItemPrivate *d = QQuickItemPrivate::get(this);
389 QQuickDefaultClipNode *clipNode = d->clipNode();
390 if (clipNode) {
391 clipNode->setRadius(m_clipRadius);
392 clipNode->setRect(boundingRect());
393 clipNode->update();
394 }
395
396 QSGNode *container = oldNode;
397 if (!container)
398 container = new QSGNode;
399
400 QQuickMaterialRippleBackgroundNode *backgroundNode = static_cast<QQuickMaterialRippleBackgroundNode *>(container->firstChild());
401 if (!backgroundNode) {
402 backgroundNode = new QQuickMaterialRippleBackgroundNode(this);
403 backgroundNode->setObjectName(objectName());
404 container->appendChildNode(backgroundNode);
405 }
406 backgroundNode->sync(this);
407
408 // enter new waves
409 int i = m_waves;
410 QQuickMaterialRippleWaveNode *enterNode = static_cast<QQuickMaterialRippleWaveNode *>(backgroundNode->nextSibling());
411 while (i-- > 0) {
412 if (!enterNode) {
413 enterNode = new QQuickMaterialRippleWaveNode(this);
414 container->appendChildNode(enterNode);
415 }
416 enterNode->sync(this);
417 enterNode = static_cast<QQuickMaterialRippleWaveNode *>(enterNode->nextSibling());
418 }
419
420 // exit old waves
421 int j = container->childCount() - 1 - m_waves;
422 while (j-- > 0) {
423 QQuickMaterialRippleWaveNode *exitNode = static_cast<QQuickMaterialRippleWaveNode *>(backgroundNode->nextSibling());
424 if (exitNode) {
425 exitNode->exit();
426 exitNode->sync(this);
427 }
428 }
429
430 return container;
431}
432
433void QQuickMaterialRipple::timerEvent(QTimerEvent *event)
434{
435 QQuickItem::timerEvent(event);
436
437 if (event->timerId() == m_enterDelay)
438 enterWave();
439}
440
441void QQuickMaterialRipple::prepareWave()
442{
443 if (m_enterDelay <= 0)
444 m_enterDelay = startTimer(RIPPLE_ENTER_DELAY);
445}
446
447void QQuickMaterialRipple::enterWave()
448{
449 if (m_enterDelay > 0) {
450 killTimer(m_enterDelay);
451 m_enterDelay = 0;
452 }
453
454 ++m_waves;
455 update();
456}
457
458void QQuickMaterialRipple::exitWave()
459{
460 if (m_enterDelay > 0) {
461 killTimer(m_enterDelay);
462 m_enterDelay = 0;
463 }
464
465 if (m_waves > 0) {
466 --m_waves;
467 update();
468 }
469}
470
471QT_END_NAMESPACE
472
473#include "moc_qquickmaterialripple_p.cpp"
474
475#include "qquickmaterialripple.moc"
void sync(QQuickItem *item) override
void updateCurrentTime(int time) override
void sync(QQuickItem *item) override
QQuickMaterialRippleWaveNode(QQuickMaterialRipple *ripple)
Combined button and popup list for selecting options.
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