public void pull(float overscroll, float extent, float crossAxisOffset, float crossExtent)
        {
            _pullRecedeTimer?.cancel();
            _pullDistance +=
                overscroll / 200.0f; // This factor is magic. Not clear why we need it to match Android.
            _glowOpacityTween.begin = _glowOpacity.value;
            _glowOpacityTween.end   =
                Mathf.Min(_glowOpacity.value + overscroll / extent * _pullOpacityGlowFactor, _maxOpacity);
            float height = Mathf.Min(extent, crossExtent * _widthToHeightFactor);

            _glowSizeTween.begin = _glowSize.value;
            _glowSizeTween.end   = Mathf.Max(1.0f - 1.0f / (0.7f * Mathf.Sqrt(_pullDistance * height)),
                                             _glowSize.value);
            _displacementTarget = crossAxisOffset / crossExtent;
            if (_displacementTarget != _displacement)
            {
                if (!_displacementTicker.isTicking)
                {
                    D.assert(_displacementTickerLastElapsed == null);
                    _displacementTicker.start();
                }
            }
            else
            {
                _displacementTicker.stop();
                _displacementTickerLastElapsed = null;
            }

            _glowController.duration = _pullTime;
            if (_state != _GlowState.pull)
            {
                _glowController.forward(from: 0.0f);
                _state = _GlowState.pull;
            }
            else
            {
                if (!_glowController.isAnimating)
                {
                    D.assert(_glowController.value == 1.0f);
                    notifyListeners();
                }
            }

            _pullRecedeTimer =
                Timer.create(_pullHoldTime, () => {
                _recede(_pullDecayTime);
                return(null);
            });
        }
 public void absorbImpact(float velocity)
 {
     D.assert(velocity >= 0.0f);
     _pullRecedeTimer?.cancel();
     _pullRecedeTimer        = null;
     velocity                = velocity.clamp(_minVelocity, _maxVelocity);
     _glowOpacityTween.begin = _state == _GlowState.idle ? 0.3f : _glowOpacity.value;
     _glowOpacityTween.end   =
         (velocity * _velocityGlowFactor).clamp(_glowOpacityTween.begin, _maxOpacity);
     _glowSizeTween.begin     = _glowSize.value;
     _glowSizeTween.end       = Mathf.Min(0.025f + 7.5e-7f * velocity * velocity, 1.0f);
     _glowController.duration = new TimeSpan(0, 0, 0, 0, (0.15f + velocity * 0.02f).round());
     _glowController.forward(from: 0.0f);
     _displacement = 0.5f;
     _state        = _GlowState.absorb;
 }
        void _recede(TimeSpan duration)
        {
            if (_state == _GlowState.recede || _state == _GlowState.idle)
            {
                return;
            }

            _pullRecedeTimer?.cancel();
            _pullRecedeTimer         = null;
            _glowOpacityTween.begin  = _glowOpacity.value;
            _glowOpacityTween.end    = 0.0f;
            _glowSizeTween.begin     = _glowSize.value;
            _glowSizeTween.end       = 0.0f;
            _glowController.duration = duration;
            _glowController.forward(from: 0.0f);
            _state = _GlowState.recede;
        }
        void _changePhase(AnimationStatus status)
        {
            if (status != AnimationStatus.completed)
            {
                return;
            }

            switch (_state)
            {
            case _GlowState.absorb:
                _recede(_recedeTime);
                break;

            case _GlowState.recede:
                _state        = _GlowState.idle;
                _pullDistance = 0.0f;
                break;

            case _GlowState.pull:
            case _GlowState.idle:
                break;
            }
        }