summaryrefslogtreecommitdiffstats
path: root/dom/animation/ComputedTimingFunction.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/animation/ComputedTimingFunction.cpp')
-rw-r--r--dom/animation/ComputedTimingFunction.cpp194
1 files changed, 194 insertions, 0 deletions
diff --git a/dom/animation/ComputedTimingFunction.cpp b/dom/animation/ComputedTimingFunction.cpp
new file mode 100644
index 000000000..95b7fa785
--- /dev/null
+++ b/dom/animation/ComputedTimingFunction.cpp
@@ -0,0 +1,194 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ComputedTimingFunction.h"
+#include "nsAlgorithm.h" // For clamped()
+#include "nsStyleUtil.h"
+
+namespace mozilla {
+
+void
+ComputedTimingFunction::Init(const nsTimingFunction &aFunction)
+{
+ mType = aFunction.mType;
+ if (nsTimingFunction::IsSplineType(mType)) {
+ mTimingFunction.Init(aFunction.mFunc.mX1, aFunction.mFunc.mY1,
+ aFunction.mFunc.mX2, aFunction.mFunc.mY2);
+ } else {
+ mSteps = aFunction.mSteps;
+ }
+}
+
+static inline double
+StepTiming(uint32_t aSteps,
+ double aPortion,
+ ComputedTimingFunction::BeforeFlag aBeforeFlag,
+ nsTimingFunction::Type aType)
+{
+ MOZ_ASSERT(0.0 <= aPortion && aPortion <= 1.0, "out of range");
+ MOZ_ASSERT(aType == nsTimingFunction::Type::StepStart ||
+ aType == nsTimingFunction::Type::StepEnd, "invalid type");
+
+ if (aPortion == 1.0) {
+ return 1.0;
+ }
+
+ // Calculate current step using step-end behavior
+ uint32_t step = uint32_t(aPortion * aSteps); // floor
+
+ // step-start is one step ahead
+ if (aType == nsTimingFunction::Type::StepStart) {
+ step++;
+ }
+
+ // If the "before flag" is set and we are at a transition point,
+ // drop back a step (but only if we are not already at the zero point--
+ // we do this clamping here since |step| is an unsigned integer)
+ if (step != 0 &&
+ aBeforeFlag == ComputedTimingFunction::BeforeFlag::Set &&
+ fmod(aPortion * aSteps, 1) == 0) {
+ step--;
+ }
+
+ // Convert to a progress value
+ return double(step) / double(aSteps);
+}
+
+double
+ComputedTimingFunction::GetValue(
+ double aPortion,
+ ComputedTimingFunction::BeforeFlag aBeforeFlag) const
+{
+ if (HasSpline()) {
+ // Check for a linear curve.
+ // (GetSplineValue(), below, also checks this but doesn't work when
+ // aPortion is outside the range [0.0, 1.0]).
+ if (mTimingFunction.X1() == mTimingFunction.Y1() &&
+ mTimingFunction.X2() == mTimingFunction.Y2()) {
+ return aPortion;
+ }
+
+ // Ensure that we return 0 or 1 on both edges.
+ if (aPortion == 0.0) {
+ return 0.0;
+ }
+ if (aPortion == 1.0) {
+ return 1.0;
+ }
+
+ // For negative values, try to extrapolate with tangent (p1 - p0) or,
+ // if p1 is coincident with p0, with (p2 - p0).
+ if (aPortion < 0.0) {
+ if (mTimingFunction.X1() > 0.0) {
+ return aPortion * mTimingFunction.Y1() / mTimingFunction.X1();
+ } else if (mTimingFunction.Y1() == 0 && mTimingFunction.X2() > 0.0) {
+ return aPortion * mTimingFunction.Y2() / mTimingFunction.X2();
+ }
+ // If we can't calculate a sensible tangent, don't extrapolate at all.
+ return 0.0;
+ }
+
+ // For values greater than 1, try to extrapolate with tangent (p2 - p3) or,
+ // if p2 is coincident with p3, with (p1 - p3).
+ if (aPortion > 1.0) {
+ if (mTimingFunction.X2() < 1.0) {
+ return 1.0 + (aPortion - 1.0) *
+ (mTimingFunction.Y2() - 1) / (mTimingFunction.X2() - 1);
+ } else if (mTimingFunction.Y2() == 1 && mTimingFunction.X1() < 1.0) {
+ return 1.0 + (aPortion - 1.0) *
+ (mTimingFunction.Y1() - 1) / (mTimingFunction.X1() - 1);
+ }
+ // If we can't calculate a sensible tangent, don't extrapolate at all.
+ return 1.0;
+ }
+
+ return mTimingFunction.GetSplineValue(aPortion);
+ }
+
+ // Since we use endpoint-exclusive timing, the output of a steps(start) timing
+ // function when aPortion = 0.0 is the top of the first step. When aPortion is
+ // negative, however, we should use the bottom of the first step. We handle
+ // negative values of aPortion specially here since once we clamp aPortion
+ // to [0,1] below we will no longer be able to distinguish to the two cases.
+ if (aPortion < 0.0) {
+ return 0.0;
+ }
+
+ // Clamp in case of steps(end) and steps(start) for values greater than 1.
+ aPortion = clamped(aPortion, 0.0, 1.0);
+
+ return StepTiming(mSteps, aPortion, aBeforeFlag, mType);
+}
+
+int32_t
+ComputedTimingFunction::Compare(const ComputedTimingFunction& aRhs) const
+{
+ if (mType != aRhs.mType) {
+ return int32_t(mType) - int32_t(aRhs.mType);
+ }
+
+ if (mType == nsTimingFunction::Type::CubicBezier) {
+ int32_t order = mTimingFunction.Compare(aRhs.mTimingFunction);
+ if (order != 0) {
+ return order;
+ }
+ } else if (mType == nsTimingFunction::Type::StepStart ||
+ mType == nsTimingFunction::Type::StepEnd) {
+ if (mSteps != aRhs.mSteps) {
+ return int32_t(mSteps) - int32_t(aRhs.mSteps);
+ }
+ }
+
+ return 0;
+}
+
+void
+ComputedTimingFunction::AppendToString(nsAString& aResult) const
+{
+ switch (mType) {
+ case nsTimingFunction::Type::CubicBezier:
+ nsStyleUtil::AppendCubicBezierTimingFunction(mTimingFunction.X1(),
+ mTimingFunction.Y1(),
+ mTimingFunction.X2(),
+ mTimingFunction.Y2(),
+ aResult);
+ break;
+ case nsTimingFunction::Type::StepStart:
+ case nsTimingFunction::Type::StepEnd:
+ nsStyleUtil::AppendStepsTimingFunction(mType, mSteps, aResult);
+ break;
+ default:
+ nsStyleUtil::AppendCubicBezierKeywordTimingFunction(mType, aResult);
+ break;
+ }
+}
+
+/* static */ int32_t
+ComputedTimingFunction::Compare(const Maybe<ComputedTimingFunction>& aLhs,
+ const Maybe<ComputedTimingFunction>& aRhs)
+{
+ // We can't use |operator<| for const Maybe<>& here because
+ // 'ease' is prior to 'linear' which is represented by Nothing().
+ // So we have to convert Nothing() as 'linear' and check it first.
+ nsTimingFunction::Type lhsType = aLhs.isNothing() ?
+ nsTimingFunction::Type::Linear : aLhs->GetType();
+ nsTimingFunction::Type rhsType = aRhs.isNothing() ?
+ nsTimingFunction::Type::Linear : aRhs->GetType();
+
+ if (lhsType != rhsType) {
+ return int32_t(lhsType) - int32_t(rhsType);
+ }
+
+ // Both of them are Nothing().
+ if (lhsType == nsTimingFunction::Type::Linear) {
+ return 0;
+ }
+
+ // Other types.
+ return aLhs->Compare(aRhs.value());
+}
+
+} // namespace mozilla