1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "ComputedTimingFunction.h"
8 #include "mozilla/ServoBindings.h"
9 #include "nsAlgorithm.h" // For clamped()
13 void ComputedTimingFunction::Init(const nsTimingFunction
& aFunction
) {
14 const StyleComputedTimingFunction
& timing
= aFunction
.mTiming
;
16 case StyleComputedTimingFunction::Tag::Keyword
: {
17 mType
= static_cast<Type
>(static_cast<uint8_t>(timing
.keyword
._0
));
20 static_cast<uint8_t>(StyleTimingKeyword::Linear
) == 0 &&
21 static_cast<uint8_t>(StyleTimingKeyword::Ease
) == 1 &&
22 static_cast<uint8_t>(StyleTimingKeyword::EaseIn
) == 2 &&
23 static_cast<uint8_t>(StyleTimingKeyword::EaseOut
) == 3 &&
24 static_cast<uint8_t>(StyleTimingKeyword::EaseInOut
) == 4,
25 "transition timing function constants not as expected");
27 static const float timingFunctionValues
[5][4] = {
28 {0.00f
, 0.00f
, 1.00f
, 1.00f
}, // linear
29 {0.25f
, 0.10f
, 0.25f
, 1.00f
}, // ease
30 {0.42f
, 0.00f
, 1.00f
, 1.00f
}, // ease-in
31 {0.00f
, 0.00f
, 0.58f
, 1.00f
}, // ease-out
32 {0.42f
, 0.00f
, 0.58f
, 1.00f
} // ease-in-out
34 const float(&values
)[4] = timingFunctionValues
[uint8_t(mType
)];
35 mTimingFunction
.Init(values
[0], values
[1], values
[2], values
[3]);
38 case StyleComputedTimingFunction::Tag::CubicBezier
:
39 mType
= Type::CubicBezier
;
40 mTimingFunction
.Init(timing
.cubic_bezier
.x1
, timing
.cubic_bezier
.y1
,
41 timing
.cubic_bezier
.x2
, timing
.cubic_bezier
.y2
);
43 case StyleComputedTimingFunction::Tag::Steps
:
45 mSteps
.mSteps
= static_cast<uint32_t>(timing
.steps
._0
);
46 mSteps
.mPos
= timing
.steps
._1
;
51 static inline double StepTiming(
52 const ComputedTimingFunction::StepFunc
& aStepFunc
, double aPortion
,
53 ComputedTimingFunction::BeforeFlag aBeforeFlag
) {
54 // Use the algorithm defined in the spec:
55 // https://drafts.csswg.org/css-easing-1/#step-timing-function-algo
57 // Calculate current step.
58 int32_t currentStep
= floor(aPortion
* aStepFunc
.mSteps
);
60 // Increment current step if it is jump-start or start.
61 if (aStepFunc
.mPos
== StyleStepPosition::Start
||
62 aStepFunc
.mPos
== StyleStepPosition::JumpStart
||
63 aStepFunc
.mPos
== StyleStepPosition::JumpBoth
) {
67 // If the "before flag" is set and we are at a transition point,
69 if (aBeforeFlag
== ComputedTimingFunction::BeforeFlag::Set
&&
70 fmod(aPortion
* aStepFunc
.mSteps
, 1) == 0) {
74 // We should not produce a result outside [0, 1] unless we have an
75 // input outside that range. This takes care of steps that would otherwise
76 // occur at boundaries.
77 if (aPortion
>= 0.0 && currentStep
< 0) {
81 int32_t jumps
= aStepFunc
.mSteps
;
82 if (aStepFunc
.mPos
== StyleStepPosition::JumpBoth
) {
84 } else if (aStepFunc
.mPos
== StyleStepPosition::JumpNone
) {
88 if (aPortion
<= 1.0 && currentStep
> jumps
) {
92 // Convert to the output progress value.
93 MOZ_ASSERT(jumps
> 0, "`jumps` should be a positive integer");
94 return double(currentStep
) / double(jumps
);
97 double ComputedTimingFunction::GetValue(
98 double aPortion
, ComputedTimingFunction::BeforeFlag aBeforeFlag
) const {
100 // Check for a linear curve.
101 // (GetSplineValue(), below, also checks this but doesn't work when
102 // aPortion is outside the range [0.0, 1.0]).
103 if (mTimingFunction
.X1() == mTimingFunction
.Y1() &&
104 mTimingFunction
.X2() == mTimingFunction
.Y2()) {
108 // Ensure that we return 0 or 1 on both edges.
109 if (aPortion
== 0.0) {
112 if (aPortion
== 1.0) {
116 // For negative values, try to extrapolate with tangent (p1 - p0) or,
117 // if p1 is coincident with p0, with (p2 - p0).
118 if (aPortion
< 0.0) {
119 if (mTimingFunction
.X1() > 0.0) {
120 return aPortion
* mTimingFunction
.Y1() / mTimingFunction
.X1();
121 } else if (mTimingFunction
.Y1() == 0 && mTimingFunction
.X2() > 0.0) {
122 return aPortion
* mTimingFunction
.Y2() / mTimingFunction
.X2();
124 // If we can't calculate a sensible tangent, don't extrapolate at all.
128 // For values greater than 1, try to extrapolate with tangent (p2 - p3) or,
129 // if p2 is coincident with p3, with (p1 - p3).
130 if (aPortion
> 1.0) {
131 if (mTimingFunction
.X2() < 1.0) {
132 return 1.0 + (aPortion
- 1.0) * (mTimingFunction
.Y2() - 1) /
133 (mTimingFunction
.X2() - 1);
134 } else if (mTimingFunction
.Y2() == 1 && mTimingFunction
.X1() < 1.0) {
135 return 1.0 + (aPortion
- 1.0) * (mTimingFunction
.Y1() - 1) /
136 (mTimingFunction
.X1() - 1);
138 // If we can't calculate a sensible tangent, don't extrapolate at all.
142 return mTimingFunction
.GetSplineValue(aPortion
);
145 return StepTiming(mSteps
, aPortion
, aBeforeFlag
);
148 int32_t ComputedTimingFunction::Compare(
149 const ComputedTimingFunction
& aRhs
) const {
150 if (mType
!= aRhs
.mType
) {
151 return int32_t(mType
) - int32_t(aRhs
.mType
);
154 if (mType
== Type::CubicBezier
) {
155 int32_t order
= mTimingFunction
.Compare(aRhs
.mTimingFunction
);
159 } else if (mType
== Type::Step
) {
160 if (mSteps
.mPos
!= aRhs
.mSteps
.mPos
) {
161 return int32_t(mSteps
.mPos
) - int32_t(aRhs
.mSteps
.mPos
);
162 } else if (mSteps
.mSteps
!= aRhs
.mSteps
.mSteps
) {
163 return int32_t(mSteps
.mSteps
) - int32_t(aRhs
.mSteps
.mSteps
);
170 void ComputedTimingFunction::AppendToString(nsACString
& aResult
) const {
171 nsTimingFunction timing
;
173 case Type::CubicBezier
:
174 timing
.mTiming
= StyleComputedTimingFunction::CubicBezier(
175 mTimingFunction
.X1(), mTimingFunction
.Y1(), mTimingFunction
.X2(),
176 mTimingFunction
.Y2());
180 StyleComputedTimingFunction::Steps(mSteps
.mSteps
, mSteps
.mPos
);
186 case Type::EaseInOut
:
187 timing
.mTiming
= StyleComputedTimingFunction::Keyword(
188 static_cast<StyleTimingKeyword
>(mType
));
191 MOZ_ASSERT_UNREACHABLE("Unsupported timing type");
193 Servo_SerializeEasing(&timing
, &aResult
);
197 int32_t ComputedTimingFunction::Compare(
198 const Maybe
<ComputedTimingFunction
>& aLhs
,
199 const Maybe
<ComputedTimingFunction
>& aRhs
) {
200 // We can't use |operator<| for const Maybe<>& here because
201 // 'ease' is prior to 'linear' which is represented by Nothing().
202 // So we have to convert Nothing() as 'linear' and check it first.
203 Type lhsType
= aLhs
.isNothing() ? Type::Linear
: aLhs
->GetType();
204 Type rhsType
= aRhs
.isNothing() ? Type::Linear
: aRhs
->GetType();
206 if (lhsType
!= rhsType
) {
207 return int32_t(lhsType
) - int32_t(rhsType
);
210 // Both of them are Nothing().
211 if (lhsType
== Type::Linear
) {
216 return aLhs
->Compare(aRhs
.value());
219 } // namespace mozilla