| 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 |  | 
| 13 | QT_BEGIN_NAMESPACE | 
| 14 |  | 
| 15 | enum WavePhase { WaveEnter, WaveExit }; | 
| 16 |  | 
| 17 | static const int RIPPLE_ENTER_DELAY = 80; | 
| 18 | static const int OPACITY_ENTER_DURATION_FAST = 120; | 
| 19 | static const int WAVE_OPACITY_DECAY_DURATION = 333; | 
| 20 | static const qreal WAVE_TOUCH_DOWN_ACCELERATION = 1024.0; | 
| 21 |  | 
| 22 | class QQuickMaterialRippleWaveNode : public QQuickAnimatedNode | 
| 23 | { | 
| 24 | public: | 
| 25 |     QQuickMaterialRippleWaveNode(QQuickMaterialRipple *ripple); | 
| 26 |  | 
| 27 |     void exit(); | 
| 28 |     void updateCurrentTime(int time) override; | 
| 29 |     void sync(QQuickItem *item) override; | 
| 30 |  | 
| 31 | private: | 
| 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 |  | 
| 40 | QQuickMaterialRippleWaveNode::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 |  | 
| 54 | void 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 |  | 
| 63 | void 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 |  | 
| 98 | void 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 |  | 
| 113 | class QQuickMaterialRippleBackgroundNode : public QQuickAnimatedNode | 
| 114 | { | 
| 115 |     Q_OBJECT | 
| 116 |  | 
| 117 | public: | 
| 118 |     QQuickMaterialRippleBackgroundNode(QQuickMaterialRipple *ripple); | 
| 119 |  | 
| 120 |     void updateCurrentTime(int time) override; | 
| 121 |     void sync(QQuickItem *item) override; | 
| 122 |  | 
| 123 | private: | 
| 124 |     bool m_active = false; | 
| 125 | }; | 
| 126 |  | 
| 127 | QQuickMaterialRippleBackgroundNode::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 |  | 
| 142 | void 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 |  | 
| 153 | void 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 |  | 
| 191 | QQuickMaterialRipple::QQuickMaterialRipple(QQuickItem *parent) | 
| 192 |     : QQuickItem(parent) | 
| 193 | { | 
| 194 |     setFlag(flag: ItemHasContents); | 
| 195 | } | 
| 196 |  | 
| 197 | bool QQuickMaterialRipple::isActive() const | 
| 198 | { | 
| 199 |     return m_active; | 
| 200 | } | 
| 201 |  | 
| 202 | void QQuickMaterialRipple::setActive(bool active) | 
| 203 | { | 
| 204 |     if (active == m_active) | 
| 205 |         return; | 
| 206 |  | 
| 207 |     m_active = active; | 
| 208 |     update(); | 
| 209 | } | 
| 210 |  | 
| 211 | QColor QQuickMaterialRipple::color() const | 
| 212 | { | 
| 213 |     return m_color; | 
| 214 | } | 
| 215 |  | 
| 216 | void QQuickMaterialRipple::setColor(const QColor &color) | 
| 217 | { | 
| 218 |     if (m_color == color) | 
| 219 |         return; | 
| 220 |  | 
| 221 |     m_color = color; | 
| 222 |     update(); | 
| 223 | } | 
| 224 |  | 
| 225 | qreal QQuickMaterialRipple::clipRadius() const | 
| 226 | { | 
| 227 |     return m_clipRadius; | 
| 228 | } | 
| 229 |  | 
| 230 | void QQuickMaterialRipple::setClipRadius(qreal radius) | 
| 231 | { | 
| 232 |     if (qFuzzyCompare(p1: m_clipRadius, p2: radius)) | 
| 233 |         return; | 
| 234 |  | 
| 235 |     m_clipRadius = radius; | 
| 236 |     update(); | 
| 237 | } | 
| 238 |  | 
| 239 | bool QQuickMaterialRipple::isPressed() const | 
| 240 | { | 
| 241 |     return m_pressed; | 
| 242 | } | 
| 243 |  | 
| 244 | void 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 |  | 
| 269 | QQuickMaterialRipple::Trigger QQuickMaterialRipple::trigger() const | 
| 270 | { | 
| 271 |     return m_trigger; | 
| 272 | } | 
| 273 |  | 
| 274 | void QQuickMaterialRipple::setTrigger(Trigger trigger) | 
| 275 | { | 
| 276 |     m_trigger = trigger; | 
| 277 | } | 
| 278 |  | 
| 279 | QPointF 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 |  | 
| 304 | QQuickItem *QQuickMaterialRipple::anchor() const | 
| 305 | { | 
| 306 |     return m_anchor; | 
| 307 | } | 
| 308 |  | 
| 309 | void QQuickMaterialRipple::setAnchor(QQuickItem *item) | 
| 310 | { | 
| 311 |     m_anchor = item; | 
| 312 | } | 
| 313 |  | 
| 314 | qreal QQuickMaterialRipple::diameter() const | 
| 315 | { | 
| 316 |     const qreal w = width(); | 
| 317 |     const qreal h = height(); | 
| 318 |     return qSqrt(v: w * w + h * h); | 
| 319 | } | 
| 320 |  | 
| 321 | void QQuickMaterialRipple::itemChange(ItemChange change, const ItemChangeData &data) | 
| 322 | { | 
| 323 |     QQuickItem::itemChange(change, data); | 
| 324 | } | 
| 325 |  | 
| 326 | QSGNode *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 |  | 
| 373 | void QQuickMaterialRipple::timerEvent(QTimerEvent *event) | 
| 374 | { | 
| 375 |     QQuickItem::timerEvent(event); | 
| 376 |  | 
| 377 |     if (event->timerId() == m_enterDelay) | 
| 378 |         enterWave(); | 
| 379 | } | 
| 380 |  | 
| 381 | void QQuickMaterialRipple::prepareWave() | 
| 382 | { | 
| 383 |     if (m_enterDelay <= 0) | 
| 384 |         m_enterDelay = startTimer(interval: RIPPLE_ENTER_DELAY); | 
| 385 | } | 
| 386 |  | 
| 387 | void 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 |  | 
| 398 | void 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 |  | 
| 411 | QT_END_NAMESPACE | 
| 412 |  | 
| 413 | #include "moc_qquickmaterialripple_p.cpp" | 
| 414 |  | 
| 415 | #include "qquickmaterialripple.moc" | 
| 416 |  |