ScrollAnimationKinetic.cpp [plain text]
#include "config.h"
#include "ScrollAnimationKinetic.h"
#include "ScrollableArea.h"
static const double decelFriction = 4;
static const double frameRate = 60;
static const Seconds tickTime = 1_s / frameRate;
static const Seconds minimumTimerInterval { 1_ms };
namespace WebCore {
ScrollAnimationKinetic::PerAxisData::PerAxisData(double lower, double upper, double initialPosition, double initialVelocity)
: m_lower(lower)
, m_upper(upper)
, m_coef1(initialVelocity / decelFriction + initialPosition)
, m_coef2(-initialVelocity / decelFriction)
, m_position(clampTo(initialPosition, lower, upper))
, m_velocity(initialPosition < lower || initialPosition > upper ? 0 : initialVelocity)
{
}
bool ScrollAnimationKinetic::PerAxisData::animateScroll(Seconds timeDelta)
{
auto lastPosition = m_position;
auto lastTime = m_elapsedTime;
m_elapsedTime += timeDelta;
double exponentialPart = exp(-decelFriction * m_elapsedTime.value());
m_position = m_coef1 + m_coef2 * exponentialPart;
m_velocity = -decelFriction * m_coef2 * exponentialPart;
if (m_position < m_lower) {
m_position = m_lower;
m_velocity = 0;
} else if (m_position > m_upper) {
m_position = m_upper;
m_velocity = 0;
} else if (fabs(m_velocity) < 1 || (lastTime && fabs(m_position - lastPosition) < 1)) {
m_position = round(m_position);
m_velocity = 0;
}
return m_velocity;
}
ScrollAnimationKinetic::ScrollAnimationKinetic(ScrollableArea& scrollableArea, std::function<void(FloatPoint&&)>&& notifyPositionChangedFunction)
: ScrollAnimation(scrollableArea)
, m_notifyPositionChangedFunction(WTFMove(notifyPositionChangedFunction))
, m_animationTimer(*this, &ScrollAnimationKinetic::animationTimerFired)
{
}
ScrollAnimationKinetic::~ScrollAnimationKinetic() = default;
void ScrollAnimationKinetic::stop()
{
m_animationTimer.stop();
m_horizontalData = std::nullopt;
m_verticalData = std::nullopt;
}
void ScrollAnimationKinetic::start(const FloatPoint& initialPosition, const FloatPoint& velocity, bool mayHScroll, bool mayVScroll)
{
stop();
m_position = initialPosition;
if (!velocity.x() && !velocity.y())
return;
if (mayHScroll) {
m_horizontalData = PerAxisData(m_scrollableArea.minimumScrollPosition().x(),
m_scrollableArea.maximumScrollPosition().x(),
initialPosition.x(), velocity.x());
}
if (mayVScroll) {
m_verticalData = PerAxisData(m_scrollableArea.minimumScrollPosition().y(),
m_scrollableArea.maximumScrollPosition().y(),
initialPosition.y(), velocity.y());
}
m_startTime = MonotonicTime::now() - tickTime / 2.;
animationTimerFired();
}
void ScrollAnimationKinetic::animationTimerFired()
{
MonotonicTime currentTime = MonotonicTime::now();
Seconds deltaToNextFrame = 1_s * ceil((currentTime - m_startTime).value() * frameRate) / frameRate - (currentTime - m_startTime);
if (m_horizontalData && !m_horizontalData.value().animateScroll(deltaToNextFrame))
m_horizontalData = std::nullopt;
if (m_verticalData && !m_verticalData.value().animateScroll(deltaToNextFrame))
m_verticalData = std::nullopt;
if (m_horizontalData || m_verticalData)
m_animationTimer.startOneShot(std::max(minimumTimerInterval, deltaToNextFrame));
double x = m_horizontalData ? m_horizontalData.value().position() : m_position.x();
double y = m_verticalData ? m_verticalData.value().position() : m_position.y();
m_position = FloatPoint(x, y);
m_notifyPositionChangedFunction(FloatPoint(m_position));
}
}