/* * Copyright (C) 2021 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #import "config.h" #import "ScrollAnimationRubberBand.h" #if HAVE(RUBBER_BANDING) #import "FloatPoint.h" #import "GeometryUtilities.h" #import static float elasticDeltaForTimeDelta(float initialPosition, float initialVelocity, Seconds elapsedTime) { return _NSElasticDeltaForTimeDelta(initialPosition, initialVelocity, elapsedTime.seconds()); } namespace WebCore { static inline float roundTowardZero(float num) { return num > 0 ? ceilf(num - 0.5f) : floorf(num + 0.5f); } static inline float roundToDevicePixelTowardZero(float num) { float roundedNum = roundf(num); if (fabs(num - roundedNum) < 0.125) num = roundedNum; return roundTowardZero(num); } ScrollAnimationRubberBand::ScrollAnimationRubberBand(ScrollAnimationClient& client) : ScrollAnimation(Type::RubberBand, client) { } ScrollAnimationRubberBand::~ScrollAnimationRubberBand() = default; bool ScrollAnimationRubberBand::startRubberBandAnimation(const FloatSize& initialVelocity, const FloatSize& initialOverscroll) { m_initialVelocity = initialVelocity; m_initialOverscroll = initialOverscroll; didStart(MonotonicTime::now()); return true; } bool ScrollAnimationRubberBand::retargetActiveAnimation(const FloatPoint&) { return false; } void ScrollAnimationRubberBand::updateScrollExtents() { // FIXME: If we're rubberbanding at the bottom and the content size changes we should fix up m_targetOffset. } void ScrollAnimationRubberBand::serviceAnimation(MonotonicTime currentTime) { auto elapsedTime = timeSinceStart(currentTime); // This is very similar to ScrollingMomentumCalculator logic, but I wasn't able to get to ScrollingMomentumCalculator to // give the correct behavior when starting a rubberband with initial velocity (i.e. bouncing). auto rubberBandOffset = FloatSize { roundToDevicePixelTowardZero(elasticDeltaForTimeDelta(m_initialOverscroll.width(), -m_initialVelocity.width(), elapsedTime)), roundToDevicePixelTowardZero(elasticDeltaForTimeDelta(m_initialOverscroll.height(), -m_initialVelocity.height(), elapsedTime)) }; // We might be rubberbanding away from an edge and back, so wait a frame or two before checking for completion. bool animationComplete = rubberBandOffset.isZero() && elapsedTime > 24_ms; auto scrollDelta = rubberBandOffset - m_client.overscrollAmount(*this); m_currentOffset = m_client.scrollOffset(*this) + scrollDelta; m_client.scrollAnimationDidUpdate(*this, m_currentOffset); if (animationComplete) didEnd(); } String ScrollAnimationRubberBand::debugDescription() const { TextStream textStream; textStream << "ScrollAnimationRubberBand " << this << " active " << isActive() << " initial velocity " << m_initialVelocity << " initial overscroll " << m_initialOverscroll; return textStream.release(); } } // namespace WebCore #endif // HAVE(RUBBER_BANDING)