mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-21 15:10:19 +00:00
LibWeb: Implement linear easing according to latest spec
This commit is contained in:
parent
3f79d93bd3
commit
c67ecf37f7
Notes:
github-actions[bot]
2024-11-05 10:42:28 +00:00
Author: https://github.com/Gingeh Commit: https://github.com/LadybirdBrowser/ladybird/commit/c67ecf37f72 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2151 Reviewed-by: https://github.com/AtkinsSJ ✅
9 changed files with 513 additions and 49 deletions
|
@ -186,12 +186,12 @@ public:
|
|||
}
|
||||
|
||||
template<typename TUnaryPredicate>
|
||||
Optional<VisibleType&> last_matching(TUnaryPredicate const& predicate)
|
||||
Optional<VisibleType const&> last_matching(TUnaryPredicate const& predicate) const
|
||||
requires(!contains_reference)
|
||||
{
|
||||
for (ssize_t i = size() - 1; i >= 0; --i) {
|
||||
if (predicate(at(i))) {
|
||||
return at(i);
|
||||
return Optional<VisibleType const&>(at(i));
|
||||
}
|
||||
}
|
||||
return {};
|
||||
|
|
264
Tests/LibWeb/Text/expected/WebAnimations/misc/easing-values.txt
Normal file
264
Tests/LibWeb/Text/expected/WebAnimations/misc/easing-values.txt
Normal file
|
@ -0,0 +1,264 @@
|
|||
linear
|
||||
0: 0.00
|
||||
10: 0.10
|
||||
20: 0.20
|
||||
30: 0.30
|
||||
40: 0.40
|
||||
50: 0.50
|
||||
60: 0.60
|
||||
70: 0.70
|
||||
80: 0.80
|
||||
90: 0.90
|
||||
100: 1.00
|
||||
linear(0, 1)
|
||||
0: 0.00
|
||||
10: 0.10
|
||||
20: 0.20
|
||||
30: 0.30
|
||||
40: 0.40
|
||||
50: 0.50
|
||||
60: 0.60
|
||||
70: 0.70
|
||||
80: 0.80
|
||||
90: 0.90
|
||||
100: 1.00
|
||||
linear(0, 0.5, 1)
|
||||
0: 0.00
|
||||
10: 0.10
|
||||
20: 0.20
|
||||
30: 0.30
|
||||
40: 0.40
|
||||
50: 0.50
|
||||
60: 0.60
|
||||
70: 0.70
|
||||
80: 0.80
|
||||
90: 0.90
|
||||
100: 1.00
|
||||
linear(0 0%, 0.5 10%, 1 100%)
|
||||
0: 0.00
|
||||
10: 0.50
|
||||
20: 0.56
|
||||
30: 0.61
|
||||
40: 0.67
|
||||
50: 0.72
|
||||
60: 0.78
|
||||
70: 0.83
|
||||
80: 0.89
|
||||
90: 0.94
|
||||
100: 1.00
|
||||
linear(0% 0, 10% 0.5, 100% 1)
|
||||
0: 0.00
|
||||
10: 0.50
|
||||
20: 0.56
|
||||
30: 0.61
|
||||
40: 0.67
|
||||
50: 0.72
|
||||
60: 0.78
|
||||
70: 0.83
|
||||
80: 0.89
|
||||
90: 0.94
|
||||
100: 1.00
|
||||
linear(0% 0, 1 100%)
|
||||
0: 0.00
|
||||
10: 0.10
|
||||
20: 0.20
|
||||
30: 0.30
|
||||
40: 0.40
|
||||
50: 0.50
|
||||
60: 0.60
|
||||
70: 0.70
|
||||
80: 0.80
|
||||
90: 0.90
|
||||
100: 1.00
|
||||
linear(0 0% 50%, 1 50% 100%)
|
||||
0: 0.00
|
||||
10: 0.00
|
||||
20: 0.00
|
||||
30: 0.00
|
||||
40: 0.00
|
||||
50: 1.00
|
||||
60: 1.00
|
||||
70: 1.00
|
||||
80: 1.00
|
||||
90: 1.00
|
||||
100: 1.00
|
||||
linear(0.5 0% 100%)
|
||||
0: 0.50
|
||||
10: 0.50
|
||||
20: 0.50
|
||||
30: 0.50
|
||||
40: 0.50
|
||||
50: 0.50
|
||||
60: 0.50
|
||||
70: 0.50
|
||||
80: 0.50
|
||||
90: 0.50
|
||||
100: 0.50
|
||||
ease
|
||||
0: 0.00
|
||||
10: 0.09
|
||||
20: 0.30
|
||||
30: 0.51
|
||||
40: 0.68
|
||||
50: 0.80
|
||||
60: 0.89
|
||||
70: 0.94
|
||||
80: 0.98
|
||||
90: 0.99
|
||||
100: 1.00
|
||||
ease-in
|
||||
0: 0.00
|
||||
10: 0.02
|
||||
20: 0.06
|
||||
30: 0.13
|
||||
40: 0.21
|
||||
50: 0.32
|
||||
60: 0.43
|
||||
70: 0.55
|
||||
80: 0.69
|
||||
90: 0.84
|
||||
100: 1.00
|
||||
ease-out
|
||||
0: 0.00
|
||||
10: 0.16
|
||||
20: 0.31
|
||||
30: 0.45
|
||||
40: 0.57
|
||||
50: 0.68
|
||||
60: 0.79
|
||||
70: 0.87
|
||||
80: 0.94
|
||||
90: 0.98
|
||||
100: 1.00
|
||||
ease-in-out
|
||||
0: 0.00
|
||||
10: 0.02
|
||||
20: 0.08
|
||||
30: 0.19
|
||||
40: 0.33
|
||||
50: 0.50
|
||||
60: 0.67
|
||||
70: 0.81
|
||||
80: 0.92
|
||||
90: 0.98
|
||||
100: 1.00
|
||||
cubic-bezier(0, 0, 0, 0)
|
||||
0: 0.00
|
||||
10: 0.10
|
||||
20: 0.20
|
||||
30: 0.30
|
||||
40: 0.40
|
||||
50: 0.50
|
||||
60: 0.60
|
||||
70: 0.70
|
||||
80: 0.80
|
||||
90: 0.90
|
||||
100: 1.00
|
||||
cubic-bezier(1, 1, 1, 1)
|
||||
0: 0.00
|
||||
10: 0.10
|
||||
20: 0.20
|
||||
30: 0.30
|
||||
40: 0.40
|
||||
50: 0.50
|
||||
60: 0.60
|
||||
70: 0.70
|
||||
80: 0.80
|
||||
90: 0.90
|
||||
100: 1.00
|
||||
cubic-bezier(1, 1000, 1, 1000)
|
||||
0: 0.00
|
||||
10: 1.00
|
||||
20: 1.00
|
||||
30: 1.00
|
||||
40: 1.00
|
||||
50: 1.00
|
||||
60: 1.00
|
||||
70: 1.00
|
||||
80: 1.00
|
||||
90: 1.00
|
||||
100: 1.00
|
||||
step-end
|
||||
0: 0.00
|
||||
10: 0.00
|
||||
20: 0.00
|
||||
30: 0.00
|
||||
40: 0.00
|
||||
50: 0.00
|
||||
60: 0.00
|
||||
70: 0.00
|
||||
80: 0.00
|
||||
90: 0.00
|
||||
100: 1.00
|
||||
steps(1000)
|
||||
0: 0.00
|
||||
10: 0.10
|
||||
20: 0.20
|
||||
30: 0.30
|
||||
40: 0.40
|
||||
50: 0.50
|
||||
60: 0.60
|
||||
70: 0.70
|
||||
80: 0.80
|
||||
90: 0.90
|
||||
100: 1.00
|
||||
steps(10, jump-start)
|
||||
0: 0.10
|
||||
10: 0.20
|
||||
20: 0.30
|
||||
30: 0.40
|
||||
40: 0.50
|
||||
50: 0.60
|
||||
60: 0.70
|
||||
70: 0.80
|
||||
80: 0.90
|
||||
90: 1.00
|
||||
100: 1.00
|
||||
steps(10, jump-end)
|
||||
0: 0.00
|
||||
10: 0.10
|
||||
20: 0.20
|
||||
30: 0.30
|
||||
40: 0.40
|
||||
50: 0.50
|
||||
60: 0.60
|
||||
70: 0.70
|
||||
80: 0.80
|
||||
90: 0.90
|
||||
100: 1.00
|
||||
steps(10, jump-none)
|
||||
0: 0.00
|
||||
10: 0.11
|
||||
20: 0.22
|
||||
30: 0.33
|
||||
40: 0.44
|
||||
50: 0.56
|
||||
60: 0.67
|
||||
70: 0.78
|
||||
80: 0.89
|
||||
90: 1.00
|
||||
100: 1.00
|
||||
steps(10, jump-both)
|
||||
0: 0.09
|
||||
10: 0.18
|
||||
20: 0.27
|
||||
30: 0.36
|
||||
40: 0.45
|
||||
50: 0.55
|
||||
60: 0.64
|
||||
70: 0.73
|
||||
80: 0.82
|
||||
90: 0.91
|
||||
100: 1.00
|
||||
steps(10, end)
|
||||
0: 0.00
|
||||
10: 0.10
|
||||
20: 0.20
|
||||
30: 0.30
|
||||
40: 0.40
|
||||
50: 0.50
|
||||
60: 0.60
|
||||
70: 0.70
|
||||
80: 0.80
|
||||
90: 0.90
|
||||
100: 1.00
|
|
@ -13,6 +13,7 @@
|
|||
"linear(5% 0, 10% 0.5, 100% 1)",
|
||||
"linear(5% 0, 1 100%)",
|
||||
"linear(-14, 27 210%)",
|
||||
"linear(0.5 5% 10%)",
|
||||
"ease",
|
||||
"ease-in",
|
||||
"ease-out",
|
||||
|
@ -39,7 +40,6 @@
|
|||
"linear(5 10)",
|
||||
"linear(5% 10%)",
|
||||
"linear(0.5 5% 10)",
|
||||
"linear(0.5 5% 10%)",
|
||||
"cubic-bezier(0, 0, 0)",
|
||||
"cubic-bezier(2, 0, 0, 0)",
|
||||
"cubic-bezier(0, 0, 2, 0)",
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
<body></body>
|
||||
<script src="../../include.js"></script>
|
||||
<script>
|
||||
test(() => {
|
||||
const easings = [
|
||||
"linear",
|
||||
"linear(0, 1)",
|
||||
"linear(0, 0.5, 1)",
|
||||
"linear(0 0%, 0.5 10%, 1 100%)",
|
||||
"linear(0% 0, 10% 0.5, 100% 1)",
|
||||
"linear(0% 0, 1 100%)",
|
||||
"linear(0 0% 50%, 1 50% 100%)",
|
||||
"linear(0.5 0% 100%)",
|
||||
// FIXME: "linear(-1, 2)"
|
||||
"ease",
|
||||
"ease-in",
|
||||
"ease-out",
|
||||
"ease-in-out",
|
||||
"cubic-bezier(0, 0, 0, 0)",
|
||||
"cubic-bezier(1, 1, 1, 1)",
|
||||
"cubic-bezier(1, 1000, 1, 1000)",
|
||||
// FIXME: "step-start",
|
||||
"step-end",
|
||||
"steps(1000)",
|
||||
"steps(10, jump-start)",
|
||||
"steps(10, jump-end)",
|
||||
"steps(10, jump-none)",
|
||||
"steps(10, jump-both)",
|
||||
// FIXME: "steps(10, start)",
|
||||
"steps(10, end)",
|
||||
];
|
||||
|
||||
for (const easing of easings) {
|
||||
const target = document.createElement('div');
|
||||
document.body.appendChild(target);
|
||||
println(easing);
|
||||
const animation = target.animate(
|
||||
{ opacity: ['0', '1'] },
|
||||
{
|
||||
duration: 100,
|
||||
fill: 'forwards',
|
||||
easing: easing,
|
||||
});
|
||||
const computed_style = getComputedStyle(target);
|
||||
for (let time = 0; time <= 100; time += 10) {
|
||||
animation.currentTime = time;
|
||||
println(`${time}: ${parseFloat(computed_style.opacity).toFixed(2)}`);
|
||||
}
|
||||
target.remove();
|
||||
}
|
||||
});
|
||||
</script>
|
|
@ -178,7 +178,7 @@ protected:
|
|||
JS::GCPtr<Animation> m_associated_animation {};
|
||||
|
||||
// https://www.w3.org/TR/web-animations-1/#time-transformations
|
||||
CSS::EasingStyleValue::Function m_timing_function { CSS::EasingStyleValue::Linear {} };
|
||||
CSS::EasingStyleValue::Function m_timing_function { CSS::EasingStyleValue::Linear::identity() };
|
||||
|
||||
// Used for calculating transitions in StyleComputer
|
||||
Phase m_previous_phase { Phase::Idle };
|
||||
|
|
|
@ -6236,19 +6236,19 @@ RefPtr<CSSStyleValue> Parser::parse_easing_value(TokenStream<ComponentValue>& to
|
|||
if (part.is(Token::Type::Ident)) {
|
||||
auto name = part.token().ident();
|
||||
auto maybe_simple_easing = [&] -> RefPtr<EasingStyleValue> {
|
||||
if (name == "linear"sv)
|
||||
return EasingStyleValue::create(EasingStyleValue::Linear {});
|
||||
if (name == "ease"sv)
|
||||
if (name.equals_ignoring_ascii_case("linear"sv))
|
||||
return EasingStyleValue::create(EasingStyleValue::Linear::identity());
|
||||
if (name.equals_ignoring_ascii_case("ease"sv))
|
||||
return EasingStyleValue::create(EasingStyleValue::CubicBezier::ease());
|
||||
if (name == "ease-in"sv)
|
||||
if (name.equals_ignoring_ascii_case("ease-in"sv))
|
||||
return EasingStyleValue::create(EasingStyleValue::CubicBezier::ease_in());
|
||||
if (name == "ease-out"sv)
|
||||
if (name.equals_ignoring_ascii_case("ease-out"sv))
|
||||
return EasingStyleValue::create(EasingStyleValue::CubicBezier::ease_out());
|
||||
if (name == "ease-in-out"sv)
|
||||
if (name.equals_ignoring_ascii_case("ease-in-out"sv))
|
||||
return EasingStyleValue::create(EasingStyleValue::CubicBezier::ease_in_out());
|
||||
if (name == "step-start"sv)
|
||||
if (name.equals_ignoring_ascii_case("step-start"sv))
|
||||
return EasingStyleValue::create(EasingStyleValue::Steps::step_start());
|
||||
if (name == "step-end"sv)
|
||||
if (name.equals_ignoring_ascii_case("step-end"sv))
|
||||
return EasingStyleValue::create(EasingStyleValue::Steps::step_end());
|
||||
return {};
|
||||
}();
|
||||
|
@ -6271,33 +6271,38 @@ RefPtr<CSSStyleValue> Parser::parse_easing_value(TokenStream<ComponentValue>& to
|
|||
argument.remove_all_matching([](auto& value) { return value.is(Token::Type::Whitespace); });
|
||||
|
||||
auto name = part.function().name;
|
||||
if (name == "linear"sv) {
|
||||
if (name.equals_ignoring_ascii_case("linear"sv)) {
|
||||
// linear() = linear( [ <number> && <percentage>{0,2} ]# )
|
||||
Vector<EasingStyleValue::Linear::Stop> stops;
|
||||
for (auto const& argument : comma_separated_arguments) {
|
||||
if (argument.is_empty() || argument.size() > 2)
|
||||
return nullptr;
|
||||
TokenStream argument_tokens { argument };
|
||||
|
||||
Optional<double> offset;
|
||||
Optional<double> position;
|
||||
Optional<double> output;
|
||||
Optional<double> first_input;
|
||||
Optional<double> second_input;
|
||||
|
||||
for (auto const& part : argument) {
|
||||
if (part.is(Token::Type::Number)) {
|
||||
if (offset.has_value())
|
||||
return nullptr;
|
||||
offset = part.token().number_value();
|
||||
} else if (part.is(Token::Type::Percentage)) {
|
||||
if (position.has_value())
|
||||
return nullptr;
|
||||
position = part.token().percentage();
|
||||
} else {
|
||||
return nullptr;
|
||||
};
|
||||
if (argument_tokens.next_token().is(Token::Type::Number))
|
||||
output = argument_tokens.consume_a_token().token().number_value();
|
||||
|
||||
if (argument_tokens.next_token().is(Token::Type::Percentage)) {
|
||||
first_input = argument_tokens.consume_a_token().token().percentage() / 100;
|
||||
if (argument_tokens.next_token().is(Token::Type::Percentage)) {
|
||||
second_input = argument_tokens.consume_a_token().token().percentage() / 100;
|
||||
}
|
||||
}
|
||||
|
||||
if (!offset.has_value())
|
||||
if (argument_tokens.next_token().is(Token::Type::Number)) {
|
||||
if (output.has_value())
|
||||
return nullptr;
|
||||
output = argument_tokens.consume_a_token().token().number_value();
|
||||
}
|
||||
|
||||
if (argument_tokens.has_next_token() || !output.has_value())
|
||||
return nullptr;
|
||||
|
||||
stops.append({ offset.value(), move(position) });
|
||||
stops.append({ output.value(), first_input, first_input.has_value() });
|
||||
if (second_input.has_value())
|
||||
stops.append({ output.value(), second_input, true });
|
||||
}
|
||||
|
||||
if (stops.is_empty())
|
||||
|
@ -6307,7 +6312,7 @@ RefPtr<CSSStyleValue> Parser::parse_easing_value(TokenStream<ComponentValue>& to
|
|||
return EasingStyleValue::create(EasingStyleValue::Linear { move(stops) });
|
||||
}
|
||||
|
||||
if (name == "cubic-bezier") {
|
||||
if (name.equals_ignoring_ascii_case("cubic-bezier"sv)) {
|
||||
if (comma_separated_arguments.size() != 4)
|
||||
return nullptr;
|
||||
|
||||
|
@ -6332,7 +6337,7 @@ RefPtr<CSSStyleValue> Parser::parse_easing_value(TokenStream<ComponentValue>& to
|
|||
return EasingStyleValue::create(bezier);
|
||||
}
|
||||
|
||||
if (name == "steps") {
|
||||
if (name.equals_ignoring_ascii_case("steps"sv)) {
|
||||
if (comma_separated_arguments.is_empty() || comma_separated_arguments.size() > 2)
|
||||
return nullptr;
|
||||
|
||||
|
|
|
@ -994,6 +994,8 @@ void StyleComputer::collect_animation_into(DOM::Element& element, Optional<CSS::
|
|||
|
||||
auto& keyframes = effect->key_frame_set()->keyframes_by_key;
|
||||
|
||||
// FIXME: Support progress values outside [0-1]
|
||||
output_progress = clamp(output_progress.value(), 0, 1);
|
||||
auto key = static_cast<u64>(output_progress.value() * 100.0 * Animations::KeyframeEffect::AnimationKeyFrameKeyScaleFactor);
|
||||
auto matching_keyframe_it = keyframes.find_largest_not_above_iterator(key);
|
||||
if (matching_keyframe_it.is_end()) {
|
||||
|
|
|
@ -14,6 +14,13 @@
|
|||
|
||||
namespace Web::CSS {
|
||||
|
||||
// https://drafts.csswg.org/css-easing-1/#valdef-easing-function-linear
|
||||
EasingStyleValue::Linear EasingStyleValue::Linear::identity()
|
||||
{
|
||||
static Linear linear { { { 0, {}, false }, { 1, {}, false } } };
|
||||
return linear;
|
||||
}
|
||||
|
||||
// NOTE: Magic cubic bezier values from https://www.w3.org/TR/css-easing-1/#valdef-cubic-bezier-easing-function-ease
|
||||
|
||||
EasingStyleValue::CubicBezier EasingStyleValue::CubicBezier::ease()
|
||||
|
@ -57,30 +64,154 @@ bool EasingStyleValue::CubicBezier::operator==(Web::CSS::EasingStyleValue::Cubic
|
|||
return x1 == other.x1 && y1 == other.y1 && x2 == other.x2 && y2 == other.y2;
|
||||
}
|
||||
|
||||
double EasingStyleValue::Linear::evaluate_at(double input_progress, bool) const
|
||||
// https://drafts.csswg.org/css-easing/#linear-canonicalization
|
||||
EasingStyleValue::Linear::Linear(Vector<EasingStyleValue::Linear::Stop> stops)
|
||||
{
|
||||
return input_progress;
|
||||
// To canonicalize a linear() function’s control points, perform the following:
|
||||
|
||||
// 1. If the first control point lacks an input progress value, set its input progress value to 0.
|
||||
if (!stops.first().input.has_value())
|
||||
stops.first().input = 0;
|
||||
|
||||
// 2. If the last control point lacks an input progress value, set its input progress value to 1.
|
||||
if (!stops.last().input.has_value())
|
||||
stops.last().input = 1;
|
||||
|
||||
// 3. If any control point has an input progress value that is less than
|
||||
// the input progress value of any preceding control point,
|
||||
// set its input progress value to the largest input progress value of any preceding control point.
|
||||
double largest_input = 0;
|
||||
for (auto stop : stops) {
|
||||
if (stop.input.has_value()) {
|
||||
if (stop.input.value() < largest_input) {
|
||||
stop.input = largest_input;
|
||||
} else {
|
||||
largest_input = stop.input.value();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. If any control point still lacks an input progress value,
|
||||
// then for each contiguous run of such control points,
|
||||
// set their input progress values so that they are evenly spaced
|
||||
// between the preceding and following control points with input progress values.
|
||||
Optional<size_t> run_start_idx;
|
||||
for (size_t idx = 0; idx < stops.size(); idx++) {
|
||||
auto stop = stops[idx];
|
||||
if (stop.input.has_value() && run_start_idx.has_value()) {
|
||||
// Note: this stop is immediately after a run
|
||||
// set inputs of [start, idx-1] stops to be evenly spaced between start-1 and idx
|
||||
auto start_input = stops[run_start_idx.value() - 1].input.value();
|
||||
auto end_input = stops[idx].input.value();
|
||||
auto run_stop_count = idx - run_start_idx.value() + 1;
|
||||
auto delta = (end_input - start_input) / run_stop_count;
|
||||
for (size_t run_idx = 0; run_idx < run_stop_count; run_idx++) {
|
||||
stops[run_idx + run_start_idx.value() - 1].input = start_input + delta * run_idx;
|
||||
}
|
||||
run_start_idx = {};
|
||||
} else if (!stop.input.has_value() && !run_start_idx.has_value()) {
|
||||
// Note: this stop is the start of a run
|
||||
run_start_idx = idx;
|
||||
}
|
||||
}
|
||||
|
||||
this->stops = move(stops);
|
||||
}
|
||||
|
||||
// https://drafts.csswg.org/css-easing/#linear-easing-function-output
|
||||
double EasingStyleValue::Linear::evaluate_at(double input_progress, bool before_flag) const
|
||||
{
|
||||
// To calculate linear easing output progress for a given linear easing function func,
|
||||
// an input progress value inputProgress, and an optional before flag (defaulting to false),
|
||||
// perform the following:
|
||||
|
||||
// 1. Let points be func’s control points.
|
||||
// 2. If points holds only a single item, return the output progress value of that item.
|
||||
if (stops.size() == 1)
|
||||
return stops[0].output;
|
||||
|
||||
// 3. If inputProgress matches the input progress value of the first point in points,
|
||||
// and the before flag is true, return the first point’s output progress value.
|
||||
if (input_progress == stops[0].input.value() && before_flag)
|
||||
return stops[0].output;
|
||||
|
||||
// 4. If inputProgress matches the input progress value of at least one point in points,
|
||||
// return the output progress value of the last such point.
|
||||
auto maybe_match = stops.last_matching([&](auto& stop) { return input_progress == stop.input.value(); });
|
||||
if (maybe_match.has_value())
|
||||
return maybe_match->output;
|
||||
|
||||
// 5. Otherwise, find two control points in points, A and B, which will be used for interpolation:
|
||||
Stop A;
|
||||
Stop B;
|
||||
|
||||
if (input_progress < stops[0].input.value()) {
|
||||
// 1. If inputProgress is smaller than any input progress value in points,
|
||||
// let A and B be the first two items in points.
|
||||
// If A and B have the same input progress value, return A’s output progress value.
|
||||
A = stops[0];
|
||||
B = stops[1];
|
||||
if (A.input == B.input)
|
||||
return A.output;
|
||||
} else if (input_progress > stops.last().input.value()) {
|
||||
// 2. If inputProgress is larger than any input progress value in points,
|
||||
// let A and B be the last two items in points.
|
||||
// If A and B have the same input progress value, return B’s output progress value.
|
||||
A = stops[stops.size() - 2];
|
||||
B = stops[stops.size() - 1];
|
||||
if (A.input == B.input)
|
||||
return B.output;
|
||||
} else {
|
||||
// 3. Otherwise, let A be the last control point whose input progress value is smaller than inputProgress,
|
||||
// and let B be the first control point whose input progress value is larger than inputProgress.
|
||||
A = stops.last_matching([&](auto& stop) { return stop.input.value() < input_progress; }).value();
|
||||
B = stops.first_matching([&](auto& stop) { return stop.input.value() > input_progress; }).value();
|
||||
}
|
||||
|
||||
// 6. Linearly interpolate (or extrapolate) inputProgress along the line defined by A and B, and return the result.
|
||||
auto factor = (input_progress - A.input.value()) / (B.input.value() - A.input.value());
|
||||
return A.output + factor * (B.output - A.output);
|
||||
}
|
||||
|
||||
// https://drafts.csswg.org/css-easing/#linear-easing-function-serializing
|
||||
String EasingStyleValue::Linear::to_string() const
|
||||
{
|
||||
StringBuilder builder;
|
||||
builder.append("linear"sv);
|
||||
if (!stops.is_empty()) {
|
||||
builder.append('(');
|
||||
// The linear keyword is serialized as itself.
|
||||
if (*this == identity())
|
||||
return "linear"_string;
|
||||
|
||||
bool first = true;
|
||||
for (auto const& stop : stops) {
|
||||
if (!first)
|
||||
builder.append(", "sv);
|
||||
// To serialize a linear() function:
|
||||
// 1. Let s be the string "linear(".
|
||||
StringBuilder builder;
|
||||
builder.append("linear("sv);
|
||||
|
||||
// 2. Serialize each control point of the function,
|
||||
// concatenate the results using the separator ", ",
|
||||
// and append the result to s.
|
||||
bool first = true;
|
||||
for (auto stop : stops) {
|
||||
if (first) {
|
||||
first = false;
|
||||
builder.appendff("{}"sv, stop.offset);
|
||||
if (stop.position.has_value())
|
||||
builder.appendff(" {}"sv, stop.position.value());
|
||||
} else {
|
||||
builder.append(", "sv);
|
||||
}
|
||||
|
||||
builder.append(')');
|
||||
// To serialize a linear() control point:
|
||||
// 1. Let s be the serialization, as a <number>, of the control point’s output progress value.
|
||||
builder.appendff("{}", stop.output);
|
||||
|
||||
// 2. If the control point originally lacked an input progress value, return s.
|
||||
// 3. Otherwise, append " " (U+0020 SPACE) to s,
|
||||
// then serialize the control point’s input progress value as a <percentage> and append it to s.
|
||||
if (stop.had_explicit_input) {
|
||||
builder.appendff(" {}%", stop.input.value() * 100);
|
||||
}
|
||||
|
||||
// 4. Return s.
|
||||
}
|
||||
|
||||
// 4. Append ")" to s, and return it.
|
||||
builder.append(')');
|
||||
return MUST(builder.to_string());
|
||||
}
|
||||
|
||||
|
|
|
@ -17,9 +17,15 @@ namespace Web::CSS {
|
|||
class EasingStyleValue final : public StyleValueWithDefaultOperators<EasingStyleValue> {
|
||||
public:
|
||||
struct Linear {
|
||||
static Linear identity();
|
||||
|
||||
struct Stop {
|
||||
double offset;
|
||||
Optional<double> position;
|
||||
double output;
|
||||
Optional<double> input;
|
||||
|
||||
// "NOTE: Serialization relies on whether or not an input progress value was originally supplied,
|
||||
// so that information should be retained in the internal representation."
|
||||
bool had_explicit_input;
|
||||
|
||||
bool operator==(Stop const&) const = default;
|
||||
};
|
||||
|
@ -30,6 +36,8 @@ public:
|
|||
|
||||
double evaluate_at(double input_progress, bool before_flag) const;
|
||||
String to_string() const;
|
||||
|
||||
Linear(Vector<Stop> stops);
|
||||
};
|
||||
|
||||
struct CubicBezier {
|
||||
|
|
Loading…
Reference in a new issue