blob: baeb14bdf41713e4d8af1b7795df60e7dd4a9cf6 [file] [log] [blame]
/*
* Copyright (C) 2015 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. ``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
* 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.
*/
#include "config.h"
#include "TimingFunction.h"
#include "CSSTimingFunctionValue.h"
#include "SpringSolver.h"
#include "StyleProperties.h"
#include "UnitBezier.h"
#include <wtf/text/StringConcatenateNumbers.h>
#include <wtf/text/TextStream.h>
namespace WebCore {
TextStream& operator<<(TextStream& ts, const TimingFunction& timingFunction)
{
switch (timingFunction.type()) {
case TimingFunction::LinearFunction:
ts << "linear";
break;
case TimingFunction::CubicBezierFunction: {
auto& function = downcast<CubicBezierTimingFunction>(timingFunction);
ts << "cubic-bezier(" << function.x1() << ", " << function.y1() << ", " << function.x2() << ", " << function.y2() << ")";
break;
}
case TimingFunction::StepsFunction: {
auto& function = downcast<StepsTimingFunction>(timingFunction);
ts << "steps(" << function.numberOfSteps();
if (auto stepPosition = function.stepPosition()) {
ts << ", ";
switch (stepPosition.value()) {
case StepsTimingFunction::StepPosition::JumpStart:
ts << "jump-start";
break;
case StepsTimingFunction::StepPosition::JumpEnd:
ts << "jump-end";
break;
case StepsTimingFunction::StepPosition::JumpNone:
ts << "jump-none";
break;
case StepsTimingFunction::StepPosition::JumpBoth:
ts << "jump-both";
break;
case StepsTimingFunction::StepPosition::Start:
ts << "start";
break;
case StepsTimingFunction::StepPosition::End:
ts << "end";
break;
}
}
ts << ")";
break;
}
case TimingFunction::SpringFunction: {
auto& function = downcast<SpringTimingFunction>(timingFunction);
ts << "spring(" << function.mass() << " " << function.stiffness() << " " << function.damping() << " " << function.initialVelocity() << ")";
break;
}
}
return ts;
}
double TimingFunction::transformTime(double inputTime, double duration, bool before) const
{
switch (m_type) {
case TimingFunction::CubicBezierFunction: {
auto& function = downcast<CubicBezierTimingFunction>(*this);
if (function.isLinear())
return inputTime;
// The epsilon value we pass to UnitBezier::solve given that the animation is going to run over |dur| seconds. The longer the
// animation, the more precision we need in the timing function result to avoid ugly discontinuities.
auto epsilon = 1.0 / (1000.0 * duration);
return UnitBezier(function.x1(), function.y1(), function.x2(), function.y2()).solve(inputTime, epsilon);
}
case TimingFunction::StepsFunction: {
// https://drafts.csswg.org/css-easing-1/#step-timing-functions
auto& function = downcast<StepsTimingFunction>(*this);
auto steps = function.numberOfSteps();
auto stepPosition = function.stepPosition();
// 1. Calculate the current step as floor(input progress value × steps).
auto currentStep = std::floor(inputTime * steps);
// 2. If the step position property is start, increment current step by one.
if (stepPosition == StepsTimingFunction::StepPosition::JumpStart || stepPosition == StepsTimingFunction::StepPosition::Start || stepPosition == StepsTimingFunction::StepPosition::JumpBoth)
++currentStep;
// 3. If both of the following conditions are true:
// - the before flag is set, and
// - input progress value × steps mod 1 equals zero (that is, if input progress value × steps is integral), then
// decrement current step by one.
if (before && !fmod(inputTime * steps, 1))
currentStep--;
// 4. If input progress value ≥ 0 and current step < 0, let current step be zero.
if (inputTime >= 0 && currentStep < 0)
currentStep = 0;
// 5. Calculate jumps based on the step position.
if (stepPosition == StepsTimingFunction::StepPosition::JumpNone)
--steps;
else if (stepPosition == StepsTimingFunction::StepPosition::JumpBoth)
++steps;
// 6. If input progress value ≤ 1 and current step > jumps, let current step be jumps.
if (inputTime <= 1 && currentStep > steps)
currentStep = steps;
// 7. The output progress value is current step / jumps.
return currentStep / steps;
}
case TimingFunction::SpringFunction: {
auto& function = downcast<SpringTimingFunction>(*this);
return SpringSolver(function.mass(), function.stiffness(), function.damping(), function.initialVelocity()).solve(inputTime * duration);
}
case TimingFunction::LinearFunction:
return inputTime;
}
ASSERT_NOT_REACHED();
return 0;
}
ExceptionOr<RefPtr<TimingFunction>> TimingFunction::createFromCSSText(const String& cssText)
{
StringBuilder cssString;
cssString.append(getPropertyNameString(CSSPropertyAnimationTimingFunction));
cssString.appendLiteral(": ");
cssString.append(cssText);
auto styleProperties = MutableStyleProperties::create();
styleProperties->parseDeclaration(cssString.toString(), CSSParserContext(HTMLStandardMode));
if (auto cssValue = styleProperties->getPropertyCSSValue(CSSPropertyAnimationTimingFunction)) {
if (auto timingFunction = createFromCSSValue(*cssValue.get()))
return timingFunction;
}
return Exception { TypeError };
}
RefPtr<TimingFunction> TimingFunction::createFromCSSValue(const CSSValue& value)
{
if (is<CSSPrimitiveValue>(value)) {
switch (downcast<CSSPrimitiveValue>(value).valueID()) {
case CSSValueLinear:
return LinearTimingFunction::create();
case CSSValueEase:
return CubicBezierTimingFunction::create();
case CSSValueEaseIn:
return CubicBezierTimingFunction::create(CubicBezierTimingFunction::EaseIn);
case CSSValueEaseOut:
return CubicBezierTimingFunction::create(CubicBezierTimingFunction::EaseOut);
case CSSValueEaseInOut:
return CubicBezierTimingFunction::create(CubicBezierTimingFunction::EaseInOut);
case CSSValueStepStart:
return StepsTimingFunction::create(1, StepsTimingFunction::StepPosition::Start);
case CSSValueStepEnd:
return StepsTimingFunction::create(1, StepsTimingFunction::StepPosition::End);
default:
return nullptr;
}
}
if (is<CSSCubicBezierTimingFunctionValue>(value)) {
auto& cubicTimingFunction = downcast<CSSCubicBezierTimingFunctionValue>(value);
return CubicBezierTimingFunction::create(cubicTimingFunction.x1(), cubicTimingFunction.y1(), cubicTimingFunction.x2(), cubicTimingFunction.y2());
}
if (is<CSSStepsTimingFunctionValue>(value)) {
auto& stepsTimingFunction = downcast<CSSStepsTimingFunctionValue>(value);
return StepsTimingFunction::create(stepsTimingFunction.numberOfSteps(), stepsTimingFunction.stepPosition());
}
if (is<CSSSpringTimingFunctionValue>(value)) {
auto& springTimingFunction = downcast<CSSSpringTimingFunctionValue>(value);
return SpringTimingFunction::create(springTimingFunction.mass(), springTimingFunction.stiffness(), springTimingFunction.damping(), springTimingFunction.initialVelocity());
}
return nullptr;
}
String TimingFunction::cssText() const
{
if (m_type == TimingFunction::CubicBezierFunction) {
auto& function = downcast<CubicBezierTimingFunction>(*this);
if (function.x1() == 0.25 && function.y1() == 0.1 && function.x2() == 0.25 && function.y2() == 1.0)
return "ease";
if (function.x1() == 0.42 && !function.y1() && function.x2() == 1.0 && function.y2() == 1.0)
return "ease-in";
if (!function.x1() && !function.y1() && function.x2() == 0.58 && function.y2() == 1.0)
return "ease-out";
if (function.x1() == 0.42 && !function.y1() && function.x2() == 0.58 && function.y2() == 1.0)
return "ease-in-out";
return makeString("cubic-bezier(", function.x1(), ", ", function.y1(), ", ", function.x2(), ", ", function.y2(), ')');
}
if (m_type == TimingFunction::StepsFunction) {
auto& function = downcast<StepsTimingFunction>(*this);
if (function.stepPosition() == StepsTimingFunction::StepPosition::JumpEnd || function.stepPosition() == StepsTimingFunction::StepPosition::End)
return makeString("steps(", function.numberOfSteps(), ')');
}
TextStream stream;
stream << *this;
return stream.release();
}
} // namespace WebCore