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