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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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