LibWeb: Remove TimingFunction in favor of EasingStyleValue::Function

Now that EasingStyleValue is a lot nicer to use, there isn't much reason
to keep TimingFunction around.
This commit is contained in:
Matthew Olsson 2024-06-14 21:50:25 -07:00 committed by Andreas Kling
commit 7950992fc2
Notes: sideshowbarker 2024-07-16 20:05:14 +09:00
11 changed files with 189 additions and 302 deletions

View file

@ -9,6 +9,7 @@
*/
#include "EasingStyleValue.h"
#include <AK/BinarySearch.h>
#include <AK/StringBuilder.h>
namespace Web::CSS {
@ -49,10 +50,166 @@ EasingStyleValue::Steps EasingStyleValue::Steps::step_end() {
return steps;
}
String EasingStyleValue::to_string() const
bool EasingStyleValue::CubicBezier::operator==(Web::CSS::EasingStyleValue::CubicBezier const& other) const
{
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
{
constexpr static auto cubic_bezier_at = [](double x1, double x2, double t)
{
auto a = 1.0 - 3.0 * x2 + 3.0 * x1;
auto b = 3.0 * x2 - 6.0 * x1;
auto c = 3.0 * x1;
auto t2 = t * t;
auto t3 = t2 * t;
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:
// - For input progress values less than zero,
if (input_progress < 0.0) {
// 1. If the x value of P1 is greater than zero, use a straight line that passes through P1 and P0 as the
// tangent.
if (x1 > 0.0)
return y1 / x1 * input_progress;
// 2. Otherwise, if the x value of P2 is greater than zero, use a straight line that passes through P2 and P0 as
// the tangent.
if (x2 > 0.0)
return y2 / x2 * input_progress;
// 3. Otherwise, let the output progress value be zero for all input progress values in the range [-∞, 0).
return 0.0;
}
// - For input progress values greater than one,
if (input_progress > 1.0) {
// 1. If the x value of P2 is less than one, use a straight line that passes through P2 and P3 as the tangent.
if (x2 < 1.0)
return (1.0 - y2) / (1.0 - x2) * (input_progress - 1.0) + 1.0;
// 2. Otherwise, if the x value of P1 is less than one, use a straight line that passes through P1 and P3 as the
// tangent.
if (x1 < 1.0)
return (1.0 - y1) / (1.0 - x1) * (input_progress - 1.0) + 1.0;
// 3. Otherwise, let the output progress value be one for all input progress values in the range (1, ∞].
return 1.0;
}
// Note: The spec does not specify the precise algorithm for calculating values in the range [0, 1]:
// "The evaluation of this curve is covered in many sources such as [FUND-COMP-GRAPHICS]."
auto x = input_progress;
auto solve = [&](auto 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.));
size_t nearby_index = 0;
if (auto found = binary_search(cached_x_samples, x, &nearby_index, [](auto x, auto& sample) {
if (x > sample.x)
return 1;
if (x < sample.x)
return -1;
return 0;
}))
return found->y;
if (nearby_index == cached_x_samples.size() || nearby_index + 1 == 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;
while (last_x <= x && last_t < 1.0) {
last_t += 1. / 60.;
auto solution = solve(last_t);
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 (x > sample.x)
return 1;
if (x < sample.x)
return -1;
return 0;
}))
return found->y;
}
// 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 factor = (x - sample1.x) / (sample2.x - sample1.x);
return sample1.y + factor * (sample2.y - sample1.y);
},
[&](Steps const& steps) {
// 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);
// 2. If the step position property is one of:
// - jump-start,
// - jump-both,
// increment current step by one.
if (position == Steps::Position::JumpStart || position == Steps::Position::JumpBoth)
current_step += 1;
// 3. If both of the following conditions are true:
// - 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;
if (before_flag && trunc(step_progress) == step_progress)
current_step -= 1;
// 4. If input progress value ≥ 0 and current step < 0, let current step be zero.
if (input_progress >= 0.0 && current_step < 0.0)
current_step = 0.0;
// 5. Calculate jumps based on the step position as follows:
// jump-start or jump-end -> steps
// jump-none -> steps - 1
// jump-both -> steps + 1
auto jumps = steps.number_of_intervals;
if (position == Steps::Position::JumpNone) {
jumps--;
} else if (position == Steps::Position::JumpBoth) {
jumps++;
}
// 6. If input progress value ≤ 1 and current step > jumps, let current step be jumps.
if (input_progress <= 1.0 && current_step > jumps)
current_step = jumps;
// 7. The output progress value is current step / jumps.
return current_step / jumps;
});
}
String EasingStyleValue::Function::to_string() const
{
StringBuilder builder;
m_function.visit(
visit(
[&](Linear const& linear) {
builder.append("linear"sv);
if (!linear.stops.is_empty()) {