Line data Source code
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/. */
6 :
7 : #include "ComputedTimingFunction.h"
8 : #include "nsAlgorithm.h" // For clamped()
9 : #include "nsStyleUtil.h"
10 :
11 : namespace mozilla {
12 :
13 : void
14 4 : ComputedTimingFunction::Init(const nsTimingFunction &aFunction)
15 : {
16 4 : mType = aFunction.mType;
17 4 : if (nsTimingFunction::IsSplineType(mType)) {
18 8 : mTimingFunction.Init(aFunction.mFunc.mX1, aFunction.mFunc.mY1,
19 12 : aFunction.mFunc.mX2, aFunction.mFunc.mY2);
20 : } else {
21 0 : mStepsOrFrames = aFunction.mStepsOrFrames;
22 : }
23 4 : }
24 :
25 : static inline double
26 0 : StepTiming(uint32_t aSteps,
27 : double aPortion,
28 : ComputedTimingFunction::BeforeFlag aBeforeFlag,
29 : nsTimingFunction::Type aType)
30 : {
31 0 : MOZ_ASSERT(aType == nsTimingFunction::Type::StepStart ||
32 : aType == nsTimingFunction::Type::StepEnd, "invalid type");
33 :
34 : // Calculate current step using step-end behavior
35 0 : int32_t step = floor(aPortion * aSteps);
36 :
37 : // step-start is one step ahead
38 0 : if (aType == nsTimingFunction::Type::StepStart) {
39 0 : step++;
40 : }
41 :
42 : // If the "before flag" is set and we are at a transition point,
43 : // drop back a step
44 0 : if (aBeforeFlag == ComputedTimingFunction::BeforeFlag::Set &&
45 0 : fmod(aPortion * aSteps, 1) == 0) {
46 0 : step--;
47 : }
48 :
49 : // Convert to a progress value
50 0 : double result = double(step) / double(aSteps);
51 :
52 : // We should not produce a result outside [0, 1] unless we have an
53 : // input outside that range. This takes care of steps that would otherwise
54 : // occur at boundaries.
55 0 : if (result < 0.0 && aPortion >= 0.0) {
56 0 : return 0.0;
57 : }
58 0 : if (result > 1.0 && aPortion <= 1.0) {
59 0 : return 1.0;
60 : }
61 0 : return result;
62 : }
63 :
64 : static inline double
65 0 : FramesTiming(uint32_t aFrames, double aPortion)
66 : {
67 0 : MOZ_ASSERT(aFrames > 1, "the number of frames must be greater than 1");
68 0 : int32_t currentFrame = floor(aPortion * aFrames);
69 0 : double result = double(currentFrame) / double(aFrames - 1);
70 :
71 : // Don't overshoot the natural range of the animation (by producing an output
72 : // progress greater than 1.0) when we are at the exact end of its interval
73 : // (i.e. the input progress is 1.0).
74 0 : if (result > 1.0 && aPortion <= 1.0) {
75 0 : return 1.0;
76 : }
77 0 : return result;
78 : }
79 :
80 : double
81 4 : ComputedTimingFunction::GetValue(
82 : double aPortion,
83 : ComputedTimingFunction::BeforeFlag aBeforeFlag) const
84 : {
85 4 : if (HasSpline()) {
86 : // Check for a linear curve.
87 : // (GetSplineValue(), below, also checks this but doesn't work when
88 : // aPortion is outside the range [0.0, 1.0]).
89 4 : if (mTimingFunction.X1() == mTimingFunction.Y1() &&
90 0 : mTimingFunction.X2() == mTimingFunction.Y2()) {
91 0 : return aPortion;
92 : }
93 :
94 : // Ensure that we return 0 or 1 on both edges.
95 4 : if (aPortion == 0.0) {
96 2 : return 0.0;
97 : }
98 2 : if (aPortion == 1.0) {
99 0 : return 1.0;
100 : }
101 :
102 : // For negative values, try to extrapolate with tangent (p1 - p0) or,
103 : // if p1 is coincident with p0, with (p2 - p0).
104 2 : if (aPortion < 0.0) {
105 0 : if (mTimingFunction.X1() > 0.0) {
106 0 : return aPortion * mTimingFunction.Y1() / mTimingFunction.X1();
107 0 : } else if (mTimingFunction.Y1() == 0 && mTimingFunction.X2() > 0.0) {
108 0 : return aPortion * mTimingFunction.Y2() / mTimingFunction.X2();
109 : }
110 : // If we can't calculate a sensible tangent, don't extrapolate at all.
111 0 : return 0.0;
112 : }
113 :
114 : // For values greater than 1, try to extrapolate with tangent (p2 - p3) or,
115 : // if p2 is coincident with p3, with (p1 - p3).
116 2 : if (aPortion > 1.0) {
117 0 : if (mTimingFunction.X2() < 1.0) {
118 0 : return 1.0 + (aPortion - 1.0) *
119 0 : (mTimingFunction.Y2() - 1) / (mTimingFunction.X2() - 1);
120 0 : } else if (mTimingFunction.Y2() == 1 && mTimingFunction.X1() < 1.0) {
121 0 : return 1.0 + (aPortion - 1.0) *
122 0 : (mTimingFunction.Y1() - 1) / (mTimingFunction.X1() - 1);
123 : }
124 : // If we can't calculate a sensible tangent, don't extrapolate at all.
125 0 : return 1.0;
126 : }
127 :
128 2 : return mTimingFunction.GetSplineValue(aPortion);
129 : }
130 :
131 0 : return mType == nsTimingFunction::Type::Frames
132 0 : ? FramesTiming(mStepsOrFrames, aPortion)
133 0 : : StepTiming(mStepsOrFrames, aPortion, aBeforeFlag, mType);
134 : }
135 :
136 : int32_t
137 0 : ComputedTimingFunction::Compare(const ComputedTimingFunction& aRhs) const
138 : {
139 0 : if (mType != aRhs.mType) {
140 0 : return int32_t(mType) - int32_t(aRhs.mType);
141 : }
142 :
143 0 : if (mType == nsTimingFunction::Type::CubicBezier) {
144 0 : int32_t order = mTimingFunction.Compare(aRhs.mTimingFunction);
145 0 : if (order != 0) {
146 0 : return order;
147 : }
148 0 : } else if (mType == nsTimingFunction::Type::StepStart ||
149 0 : mType == nsTimingFunction::Type::StepEnd ||
150 0 : mType == nsTimingFunction::Type::Frames) {
151 0 : if (mStepsOrFrames != aRhs.mStepsOrFrames) {
152 0 : return int32_t(mStepsOrFrames) - int32_t(aRhs.mStepsOrFrames);
153 : }
154 : }
155 :
156 0 : return 0;
157 : }
158 :
159 : void
160 0 : ComputedTimingFunction::AppendToString(nsAString& aResult) const
161 : {
162 0 : switch (mType) {
163 : case nsTimingFunction::Type::CubicBezier:
164 0 : nsStyleUtil::AppendCubicBezierTimingFunction(mTimingFunction.X1(),
165 0 : mTimingFunction.Y1(),
166 0 : mTimingFunction.X2(),
167 0 : mTimingFunction.Y2(),
168 0 : aResult);
169 0 : break;
170 : case nsTimingFunction::Type::StepStart:
171 : case nsTimingFunction::Type::StepEnd:
172 0 : nsStyleUtil::AppendStepsTimingFunction(mType, mStepsOrFrames, aResult);
173 0 : break;
174 : case nsTimingFunction::Type::Frames:
175 0 : nsStyleUtil::AppendFramesTimingFunction(mStepsOrFrames, aResult);
176 0 : break;
177 : default:
178 0 : nsStyleUtil::AppendCubicBezierKeywordTimingFunction(mType, aResult);
179 0 : break;
180 : }
181 0 : }
182 :
183 : /* static */ int32_t
184 0 : ComputedTimingFunction::Compare(const Maybe<ComputedTimingFunction>& aLhs,
185 : const Maybe<ComputedTimingFunction>& aRhs)
186 : {
187 : // We can't use |operator<| for const Maybe<>& here because
188 : // 'ease' is prior to 'linear' which is represented by Nothing().
189 : // So we have to convert Nothing() as 'linear' and check it first.
190 0 : nsTimingFunction::Type lhsType = aLhs.isNothing() ?
191 0 : nsTimingFunction::Type::Linear : aLhs->GetType();
192 0 : nsTimingFunction::Type rhsType = aRhs.isNothing() ?
193 0 : nsTimingFunction::Type::Linear : aRhs->GetType();
194 :
195 0 : if (lhsType != rhsType) {
196 0 : return int32_t(lhsType) - int32_t(rhsType);
197 : }
198 :
199 : // Both of them are Nothing().
200 0 : if (lhsType == nsTimingFunction::Type::Linear) {
201 0 : return 0;
202 : }
203 :
204 : // Other types.
205 0 : return aLhs->Compare(aRhs.value());
206 : }
207 :
208 : } // namespace mozilla
|