LibWeb: Break Easing Function definitions into separate functions

This commit is contained in:
Gingeh 2024-11-03 10:36:44 +11:00 committed by Sam Atkins
parent 373c80db68
commit 3f79d93bd3
Notes: github-actions[bot] 2024-11-05 10:42:35 +00:00
2 changed files with 224 additions and 195 deletions

View file

@ -57,7 +57,34 @@ bool EasingStyleValue::CubicBezier::operator==(Web::CSS::EasingStyleValue::Cubic
return x1 == other.x1 && y1 == other.y1 && x2 == other.x2 && y2 == other.y2;
}
double EasingStyleValue::Function::evaluate_at(double input_progress, bool before_flag) const
double EasingStyleValue::Linear::evaluate_at(double input_progress, bool) const
{
return input_progress;
}
String EasingStyleValue::Linear::to_string() const
{
StringBuilder builder;
builder.append("linear"sv);
if (!stops.is_empty()) {
builder.append('(');
bool first = true;
for (auto const& stop : stops) {
if (!first)
builder.append(", "sv);
first = false;
builder.appendff("{}"sv, stop.offset);
if (stop.position.has_value())
builder.appendff(" {}"sv, stop.position.value());
}
builder.append(')');
}
return MUST(builder.to_string());
}
double EasingStyleValue::CubicBezier::evaluate_at(double input_progress, bool) const
{
constexpr static auto cubic_bezier_at = [](double x1, double x2, double t) {
auto a = 1.0 - 3.0 * x2 + 3.0 * x1;
@ -70,11 +97,6 @@ double EasingStyleValue::Function::evaluate_at(double input_progress, bool befor
return (a * t3) + (b * t2) + (c * t);
};
return visit(
[&](Linear const&) { return input_progress; },
[&](CubicBezier const& bezier) {
auto const& [x1, y1, x2, y2, cached_x_samples] = bezier;
// https://www.w3.org/TR/css-easing-1/#cubic-bezier-algo
// For input progress values outside the range [0, 1], the curve is extended infinitely using tangent of the curve
// at the closest endpoint as follows:
@ -116,16 +138,16 @@ double EasingStyleValue::Function::evaluate_at(double input_progress, bool befor
auto x = input_progress;
auto solve = [&](auto t) {
auto x = cubic_bezier_at(bezier.x1, bezier.x2, t);
auto y = cubic_bezier_at(bezier.y1, bezier.y2, t);
auto x = cubic_bezier_at(x1, x2, t);
auto y = cubic_bezier_at(y1, y2, t);
return CubicBezier::CachedSample { x, y, t };
};
if (cached_x_samples.is_empty())
cached_x_samples.append(solve(0.));
if (m_cached_x_samples.is_empty())
m_cached_x_samples.append(solve(0.));
size_t nearby_index = 0;
if (auto found = binary_search(cached_x_samples, x, &nearby_index, [](auto x, auto& sample) {
if (auto found = binary_search(m_cached_x_samples, x, &nearby_index, [](auto x, auto& sample) {
if (x - sample.x >= NumericLimits<double>::epsilon())
return 1;
if (x - sample.x <= NumericLimits<double>::epsilon())
@ -134,18 +156,18 @@ double EasingStyleValue::Function::evaluate_at(double input_progress, bool befor
}))
return found->y;
if (nearby_index == cached_x_samples.size() || nearby_index + 1 == cached_x_samples.size()) {
if (nearby_index == m_cached_x_samples.size() || nearby_index + 1 == m_cached_x_samples.size()) {
// Produce more samples until we have enough.
auto last_t = cached_x_samples.last().t;
auto last_x = cached_x_samples.last().x;
auto last_t = m_cached_x_samples.last().t;
auto last_x = m_cached_x_samples.last().x;
while (last_x <= x && last_t < 1.0) {
last_t += 1. / 60.;
auto solution = solve(last_t);
cached_x_samples.append(solution);
m_cached_x_samples.append(solution);
last_x = solution.x;
}
if (auto found = binary_search(cached_x_samples, x, &nearby_index, [](auto x, auto& sample) {
if (auto found = binary_search(m_cached_x_samples, x, &nearby_index, [](auto x, auto& sample) {
if (x - sample.x >= NumericLimits<double>::epsilon())
return 1;
if (x - sample.x <= NumericLimits<double>::epsilon())
@ -156,16 +178,34 @@ double EasingStyleValue::Function::evaluate_at(double input_progress, bool befor
}
// We have two samples on either side of the x value we want, so we can linearly interpolate between them.
auto& sample1 = cached_x_samples[nearby_index];
auto& sample2 = cached_x_samples[nearby_index + 1];
auto& sample1 = m_cached_x_samples[nearby_index];
auto& sample2 = m_cached_x_samples[nearby_index + 1];
auto factor = (x - sample1.x) / (sample2.x - sample1.x);
return sample1.y + factor * (sample2.y - sample1.y);
},
[&](Steps const& steps) {
}
String EasingStyleValue::CubicBezier::to_string() const
{
StringBuilder builder;
if (*this == CubicBezier::ease()) {
builder.append("ease"sv);
} else if (*this == CubicBezier::ease_in()) {
builder.append("ease-in"sv);
} else if (*this == CubicBezier::ease_out()) {
builder.append("ease-out"sv);
} else if (*this == CubicBezier::ease_in_out()) {
builder.append("ease-in-out"sv);
} else {
builder.appendff("cubic-bezier({}, {}, {}, {})", x1, y1, x2, y2);
}
return MUST(builder.to_string());
}
double EasingStyleValue::Steps::evaluate_at(double input_progress, bool before_flag) const
{
// https://www.w3.org/TR/css-easing-1/#step-easing-algo
// 1. Calculate the current step as floor(input progress value × steps).
auto [number_of_steps, position] = steps;
auto current_step = floor(input_progress * number_of_steps);
auto current_step = floor(input_progress * number_of_intervals);
// 2. If the step position property is one of:
// - jump-start,
@ -178,7 +218,7 @@ double EasingStyleValue::Function::evaluate_at(double input_progress, bool befor
// - 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.
auto step_progress = input_progress * number_of_steps;
auto step_progress = input_progress * number_of_intervals;
if (before_flag && trunc(step_progress) == step_progress)
current_step -= 1;
@ -191,7 +231,7 @@ double EasingStyleValue::Function::evaluate_at(double input_progress, bool befor
// jump-start or jump-end -> steps
// jump-none -> steps - 1
// jump-both -> steps + 1
auto jumps = steps.number_of_intervals;
auto jumps = number_of_intervals;
if (position == Steps::Position::JumpNone) {
jumps--;
} else if (position == Steps::Position::JumpBoth) {
@ -204,52 +244,18 @@ double EasingStyleValue::Function::evaluate_at(double input_progress, bool befor
// 7. The output progress value is current step / jumps.
return current_step / jumps;
});
}
String EasingStyleValue::Function::to_string() const
String EasingStyleValue::Steps::to_string() const
{
StringBuilder builder;
visit(
[&](Linear const& linear) {
builder.append("linear"sv);
if (!linear.stops.is_empty()) {
builder.append('(');
bool first = true;
for (auto const& stop : linear.stops) {
if (!first)
builder.append(", "sv);
first = false;
builder.appendff("{}"sv, stop.offset);
if (stop.position.has_value())
builder.appendff(" {}"sv, stop.position.value());
}
builder.append(')');
}
},
[&](CubicBezier const& bezier) {
if (bezier == CubicBezier::ease()) {
builder.append("ease"sv);
} else if (bezier == CubicBezier::ease_in()) {
builder.append("ease-in"sv);
} else if (bezier == CubicBezier::ease_out()) {
builder.append("ease-out"sv);
} else if (bezier == CubicBezier::ease_in_out()) {
builder.append("ease-in-out"sv);
} else {
builder.appendff("cubic-bezier({}, {}, {}, {})", bezier.x1, bezier.y1, bezier.x2, bezier.y2);
}
},
[&](Steps const& steps) {
if (steps == Steps::step_start()) {
if (*this == Steps::step_start()) {
builder.append("step-start"sv);
} else if (steps == Steps::step_end()) {
} else if (*this == Steps::step_end()) {
builder.append("step-end"sv);
} else {
auto position = [&] -> Optional<StringView> {
switch (steps.position) {
switch (this->position) {
case Steps::Position::JumpStart:
return "jump-start"sv;
case Steps::Position::JumpNone:
@ -263,13 +269,28 @@ String EasingStyleValue::Function::to_string() const
}
}();
if (position.has_value()) {
builder.appendff("steps({}, {})", steps.number_of_intervals, position.value());
builder.appendff("steps({}, {})", number_of_intervals, position.value());
} else {
builder.appendff("steps({})", steps.number_of_intervals);
builder.appendff("steps({})", number_of_intervals);
}
}
});
return MUST(builder.to_string());
}
double EasingStyleValue::Function::evaluate_at(double input_progress, bool before_flag) const
{
return visit(
[&](auto const& curve) {
return curve.evaluate_at(input_progress, before_flag);
});
}
String EasingStyleValue::Function::to_string() const
{
return visit(
[&](auto const& curve) {
return curve.to_string();
});
}
}

View file

@ -27,6 +27,9 @@ public:
Vector<Stop> stops;
bool operator==(Linear const&) const = default;
double evaluate_at(double input_progress, bool before_flag) const;
String to_string() const;
};
struct CubicBezier {
@ -49,6 +52,9 @@ public:
mutable Vector<CachedSample, 64> m_cached_x_samples {};
bool operator==(CubicBezier const&) const;
double evaluate_at(double input_progress, bool before_flag) const;
String to_string() const;
};
struct Steps {
@ -68,13 +74,15 @@ public:
Position position { Position::End };
bool operator==(Steps const&) const = default;
double evaluate_at(double input_progress, bool before_flag) const;
String to_string() const;
};
struct Function : public Variant<Linear, CubicBezier, Steps> {
using Variant::Variant;
double evaluate_at(double input_progress, bool before_flag) const;
String to_string() const;
};