diff options
Diffstat (limited to 'src/imports/controls/material/qquickmaterialripple.cpp')
-rw-r--r-- | src/imports/controls/material/qquickmaterialripple.cpp | 451 |
1 files changed, 451 insertions, 0 deletions
diff --git a/src/imports/controls/material/qquickmaterialripple.cpp b/src/imports/controls/material/qquickmaterialripple.cpp new file mode 100644 index 00000000..645b0289 --- /dev/null +++ b/src/imports/controls/material/qquickmaterialripple.cpp @@ -0,0 +1,451 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickmaterialripple_p.h" + +#include <QtCore/qmath.h> +#include <QtQuick/private/qquickitem_p.h> +#include <QtQuick/private/qquickanimator_p.h> +#include <QtQuick/private/qquickrectangle_p.h> +#include <QtQuick/private/qquickanimatorjob_p.h> +#include <QtQuick/private/qsgadaptationlayer_p.h> +#include <QtQuickTemplates2/private/qquickabstractbutton_p.h> +#include <QtQuickTemplates2/private/qquickabstractbutton_p_p.h> + +QT_BEGIN_NAMESPACE + +namespace { + enum WavePhase { WaveEnter, WaveExit }; +} + +static const int RIPPLE_ENTER_DELAY = 80; +static const int OPACITY_ENTER_DURATION_FAST = 120; +static const int WAVE_OPACITY_DECAY_DURATION = 333; +static const qreal WAVE_TOUCH_UP_ACCELERATION = 3400.0; +static const qreal WAVE_TOUCH_DOWN_ACCELERATION = 1024.0; + +class QQuickMaterialRippleAnimatorJob : public QQuickAnimatorJob +{ +public: + QQuickMaterialRippleAnimatorJob(WavePhase phase, const QPointF &anchor, const QRectF &bounds); + + void initialize(QQuickAnimatorController *controller) override; + void updateCurrentTime(int time) override; + void writeBack() override; + void nodeWasDestroyed() override; + void afterNodeSync() override; + +private: + qreal m_diameter; + WavePhase m_phase; + QRectF m_bounds; + QPointF m_anchor; + QSGTransformNode *m_itemNode; + QSGOpacityNode *m_opacityNode; + QSGInternalRectangleNode *m_rectNode; +}; + +QQuickMaterialRippleAnimatorJob::QQuickMaterialRippleAnimatorJob(WavePhase phase, const QPointF &anchor, const QRectF &bounds) + : m_diameter(qSqrt(bounds.width() * bounds.width() + bounds.height() * bounds.height())), + m_phase(phase), + m_bounds(bounds), + m_anchor(anchor), + m_itemNode(nullptr), + m_opacityNode(nullptr), + m_rectNode(nullptr) +{ +} + +void QQuickMaterialRippleAnimatorJob::initialize(QQuickAnimatorController *controller) +{ + QQuickAnimatorJob::initialize(controller); + afterNodeSync(); +} + +void QQuickMaterialRippleAnimatorJob::updateCurrentTime(int time) +{ + if (!m_itemNode || !m_rectNode) + return; + + qreal duration = 0; + if (m_phase == WaveEnter) + duration = QQuickAnimatorJob::duration(); + else + duration = 1000.0 * qSqrt((m_diameter - m_from) / 2.0 / (WAVE_TOUCH_UP_ACCELERATION + WAVE_TOUCH_DOWN_ACCELERATION)); + + qreal p = 1.0; + if (!qFuzzyIsNull(duration) && time < duration) + p = time / duration; + + m_value = m_from + (m_to - m_from) * p; + p = m_value / m_diameter; + + const qreal dx = (1.0 - p) * (m_anchor.x() - m_bounds.width() / 2); + const qreal dy = (1.0 - p) * (m_anchor.y() - m_bounds.height() / 2); + + m_rectNode->setRect(QRectF(0, 0, m_value, m_value)); + m_rectNode->setRadius(m_value / 2); + m_rectNode->update(); + + QMatrix4x4 m; + m.translate((m_bounds.width() - m_value) / 2 + dx, + (m_bounds.height() - m_value) / 2 + dy); + m_itemNode->setMatrix(m); + + if (m_opacityNode) { + qreal opacity = 1.0; + if (m_phase == WaveExit) + opacity -= static_cast<qreal>(time) / WAVE_OPACITY_DECAY_DURATION; + m_opacityNode->setOpacity(opacity); + } +} + +void QQuickMaterialRippleAnimatorJob::writeBack() +{ + if (m_target) + m_target->setSize(QSizeF(m_value, m_value)); + if (m_phase == WaveExit) + m_target->deleteLater(); +} + +void QQuickMaterialRippleAnimatorJob::nodeWasDestroyed() +{ + m_itemNode = nullptr; + m_opacityNode = nullptr; + m_rectNode = nullptr; +} + +void QQuickMaterialRippleAnimatorJob::afterNodeSync() +{ + m_itemNode = QQuickItemPrivate::get(m_target)->itemNode(); + m_opacityNode = QQuickItemPrivate::get(m_target)->opacityNode(); + m_rectNode = static_cast<QSGInternalRectangleNode *>(QQuickItemPrivate::get(m_target)->childContainerNode()->firstChild()); +} + +class QQuickMaterialRippleAnimator : public QQuickAnimator +{ +public: + QQuickMaterialRippleAnimator(const QPointF &anchor, const QRectF &bounds, QObject *parent = nullptr); + + WavePhase phase() const; + void setPhase(WavePhase phase); + +protected: + QString propertyName() const override; + QQuickAnimatorJob *createJob() const override; + +private: + QPointF m_anchor; + QRectF m_bounds; + WavePhase m_phase; +}; + +QQuickMaterialRippleAnimator::QQuickMaterialRippleAnimator(const QPointF &anchor, const QRectF &bounds, QObject *parent) + : QQuickAnimator(parent), m_anchor(anchor), m_bounds(bounds), m_phase(WaveEnter) +{ +} + +WavePhase QQuickMaterialRippleAnimator::phase() const +{ + return m_phase; +} + +void QQuickMaterialRippleAnimator::setPhase(WavePhase phase) +{ + if (m_phase == phase) + return; + + m_phase = phase; +} + +QString QQuickMaterialRippleAnimator::propertyName() const +{ + return QString(); +} + +QQuickAnimatorJob *QQuickMaterialRippleAnimator::createJob() const +{ + return new QQuickMaterialRippleAnimatorJob(m_phase, m_anchor, m_bounds); +} + +QQuickMaterialRipple::QQuickMaterialRipple(QQuickItem *parent) + : QQuickItem(parent), m_active(false), m_pressed(false), m_enterDelay(0), m_trigger(Press), m_clipRadius(0.0), m_anchor(nullptr), m_opacityAnimator(nullptr) +{ + setOpacity(0.0); + setFlag(ItemHasContents); +} + +bool QQuickMaterialRipple::isActive() const +{ + return m_active; +} + +void QQuickMaterialRipple::setActive(bool active) +{ + if (active == m_active) + return; + + m_active = active; + + if (!m_opacityAnimator) { + m_opacityAnimator = new QQuickOpacityAnimator(this); + m_opacityAnimator->setTargetItem(this); + } + m_opacityAnimator->setDuration(active ? OPACITY_ENTER_DURATION_FAST : WAVE_OPACITY_DECAY_DURATION); + + const int time = m_opacityAnimator->currentTime(); + m_opacityAnimator->stop(); + m_opacityAnimator->setFrom(opacity()); + m_opacityAnimator->setTo(active ? 1.0 : 0.0); + m_opacityAnimator->setCurrentTime(time); + m_opacityAnimator->start(); +} + +QColor QQuickMaterialRipple::color() const +{ + return m_color; +} + +void QQuickMaterialRipple::setColor(const QColor &color) +{ + if (m_color == color) + return; + + m_color = color; + update(); +} + +qreal QQuickMaterialRipple::clipRadius() const +{ + return m_clipRadius; +} + +void QQuickMaterialRipple::setClipRadius(qreal radius) +{ + if (qFuzzyCompare(m_clipRadius, radius)) + return; + + m_clipRadius = radius; + setClip(!qFuzzyIsNull(radius)); + update(); +} + +bool QQuickMaterialRipple::isPressed() const +{ + return m_pressed; +} + +void QQuickMaterialRipple::setPressed(bool pressed) +{ + if (pressed == m_pressed) + return; + + m_pressed = pressed; + + if (!isEnabled()) + return; + + if (pressed) { + if (m_trigger == Press) + prepareWave(); + else + exitWave(); + } else { + if (m_trigger == Release) + enterWave(); + else + exitWave(); + } +} + +QQuickMaterialRipple::Trigger QQuickMaterialRipple::trigger() const +{ + return m_trigger; +} + +void QQuickMaterialRipple::setTrigger(Trigger trigger) +{ + m_trigger = trigger; +} + +QPointF QQuickMaterialRipple::anchorPoint() const +{ + const QRectF bounds = boundingRect(); + const QPointF center = bounds.center(); + if (!m_anchor) + return center; + + QPointF anchorPoint = bounds.center(); + if (QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(m_anchor)) + anchorPoint = QQuickAbstractButtonPrivate::get(button)->pressPoint; + anchorPoint = mapFromItem(m_anchor, anchorPoint); + + // calculate whether the anchor point is within the ripple circle bounds, + // that is, whether waves should start expanding from the anchor point + const qreal r = qSqrt(bounds.width() * bounds.width() + bounds.height() * bounds.height()) / 2; + if (QLineF(center, anchorPoint).length() < r) + return anchorPoint; + + // if the anchor point is outside the ripple circle bounds, start expanding + // from the intersection point of the ripple circle and a line from its center + // to the the anchor point + const qreal p = qAtan2(anchorPoint.y() - center.y(), anchorPoint.x() - center.x()); + return QPointF(center.x() + r * qCos(p), center.y() + r * qSin(p)); +} + +QQuickItem *QQuickMaterialRipple::anchor() const +{ + return m_anchor; +} + +void QQuickMaterialRipple::setAnchor(QQuickItem *item) +{ + m_anchor = item; +} + +void QQuickMaterialRipple::itemChange(ItemChange change, const ItemChangeData &data) +{ + QQuickItem::itemChange(change, data); + + if (change == ItemChildRemovedChange) { + QQuickMaterialRippleAnimator *animator = data.item->findChild<QQuickMaterialRippleAnimator *>(); + if (animator) + m_rippleAnimators.removeOne(animator); + } +} + +QSGNode *QQuickMaterialRipple::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) +{ + QQuickItemPrivate *d = QQuickItemPrivate::get(this); + QQuickDefaultClipNode *clipNode = d->clipNode(); + if (clipNode) { + // TODO: QTBUG-51894 + // clipNode->setRadius(m_clipRadius); + clipNode->setRect(boundingRect()); + clipNode->update(); + } + + const qreal w = width(); + const qreal h = height(); + const qreal sz = qSqrt(w * w + h * h); + + QSGTransformNode *transformNode = static_cast<QSGTransformNode *>(oldNode); + if (!transformNode) + transformNode = new QSGTransformNode; + + QSGInternalRectangleNode *rectNode = static_cast<QSGInternalRectangleNode *>(transformNode->firstChild()); + if (!rectNode) { + rectNode = d->sceneGraphContext()->createInternalRectangleNode(); + rectNode->setAntialiasing(true); + transformNode->appendChildNode(rectNode); + } + + QMatrix4x4 matrix; + if (qFuzzyIsNull(m_clipRadius)) { + matrix.translate((w - sz) / 2, (h - sz) / 2); + rectNode->setRect(QRectF(0, 0, sz, sz)); + rectNode->setRadius(sz / 2); + } else { + rectNode->setRect(QRectF(0, 0, w, h)); + rectNode->setRadius(m_clipRadius); + } + transformNode->setMatrix(matrix); + rectNode->setColor(m_color); + rectNode->update(); + + return transformNode; +} + +void QQuickMaterialRipple::timerEvent(QTimerEvent *event) +{ + QQuickItem::timerEvent(event); + + if (event->timerId() == m_enterDelay) + enterWave(); +} + +void QQuickMaterialRipple::prepareWave() +{ + if (m_enterDelay <= 0) + m_enterDelay = startTimer(RIPPLE_ENTER_DELAY); +} + +void QQuickMaterialRipple::enterWave() +{ + if (m_enterDelay > 0) { + killTimer(m_enterDelay); + m_enterDelay = 0; + } + + const qreal w = width(); + const qreal h = height(); + const qreal sz = qSqrt(w * w + h * h); + + QQuickRectangle *wave = new QQuickRectangle(this); + wave->setPosition(QPointF((w - sz) / 2, (h - sz) / 2)); + wave->setSize(QSizeF(sz, sz)); + wave->setRadius(sz / 2); + wave->setColor(color()); + wave->setOpacity(0.0); + + QQuickMaterialRippleAnimator *animator = new QQuickMaterialRippleAnimator(anchorPoint(), boundingRect(), wave); + animator->setDuration(qRound(1000.0 * qSqrt(sz / 2.0 / WAVE_TOUCH_DOWN_ACCELERATION))); + animator->setTargetItem(wave); + animator->setTo(sz); + animator->start(); + m_rippleAnimators += animator; +} + +void QQuickMaterialRipple::exitWave() +{ + if (m_enterDelay > 0) { + killTimer(m_enterDelay); + m_enterDelay = 0; + } + + for (QQuickMaterialRippleAnimator *animator : m_rippleAnimators) { + if (animator->phase() == WaveEnter) { + animator->stop(); // -> writeBack() -> setSize() + if (QQuickItem *wave = animator->targetItem()) + animator->setFrom(wave->width()); + animator->setDuration(WAVE_OPACITY_DECAY_DURATION); + animator->setPhase(WaveExit); + animator->restart(); + } + } +} + +QT_END_NAMESPACE |