mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-21 23:20:20 +00:00
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:
parent
ac35f76e67
commit
7950992fc2
Notes:
sideshowbarker
2024-07-16 20:05:14 +09:00
Author: https://github.com/mattco98 Commit: https://github.com/LadybirdBrowser/ladybird/commit/7950992fc2 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/173
11 changed files with 189 additions and 302 deletions
|
@ -16,7 +16,5 @@ source_set("Animations") {
|
|||
"DocumentTimeline.h",
|
||||
"KeyframeEffect.cpp",
|
||||
"KeyframeEffect.h",
|
||||
"TimingFunction.cpp",
|
||||
"TimingFunction.h",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@ EffectTiming AnimationEffect::get_timing() const
|
|||
.iterations = m_iteration_count,
|
||||
.duration = m_iteration_duration,
|
||||
.direction = m_playback_direction,
|
||||
.easing = m_easing_function,
|
||||
.easing = m_timing_function.to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -111,7 +111,7 @@ ComputedEffectTiming AnimationEffect::get_computed_timing() const
|
|||
.iterations = m_iteration_count,
|
||||
.duration = duration,
|
||||
.direction = m_playback_direction,
|
||||
.easing = m_easing_function,
|
||||
.easing = m_timing_function.to_string(),
|
||||
},
|
||||
|
||||
end_time(),
|
||||
|
@ -159,6 +159,7 @@ WebIDL::ExceptionOr<void> AnimationEffect::update_timing(OptionalEffectTiming ti
|
|||
easing_value = parse_easing_string(realm(), timing.easing.value());
|
||||
if (!easing_value)
|
||||
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Invalid easing function"sv };
|
||||
VERIFY(easing_value->is_easing());
|
||||
}
|
||||
|
||||
// 5. Assign each member that exists in input to the corresponding timing property of effect as follows:
|
||||
|
@ -192,10 +193,8 @@ WebIDL::ExceptionOr<void> AnimationEffect::update_timing(OptionalEffectTiming ti
|
|||
m_playback_direction = timing.direction.value();
|
||||
|
||||
// - easing → timing function
|
||||
if (easing_value) {
|
||||
m_easing_function = timing.easing.value();
|
||||
m_timing_function = TimingFunction::from_easing_style_value(easing_value->as_easing());
|
||||
}
|
||||
if (easing_value)
|
||||
m_timing_function = easing_value->as_easing().function();
|
||||
|
||||
if (auto animation = m_associated_animation)
|
||||
animation->effect_timing_changed({});
|
||||
|
@ -590,7 +589,7 @@ Optional<double> AnimationEffect::transformed_progress() const
|
|||
|
||||
// 3. Return the result of evaluating the animation effect’s timing function passing directed progress as the input progress value and
|
||||
// before flag as the before flag.
|
||||
return m_timing_function(directed_progress.value(), before_flag);
|
||||
return m_timing_function.evaluate_at(directed_progress.value(), before_flag);
|
||||
}
|
||||
|
||||
RefPtr<CSS::StyleValue const> AnimationEffect::parse_easing_string(JS::Realm& realm, StringView value)
|
||||
|
|
|
@ -9,10 +9,10 @@
|
|||
#include <AK/Optional.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/Variant.h>
|
||||
#include <LibWeb/Animations/TimingFunction.h>
|
||||
#include <LibWeb/Bindings/AnimationEffectPrototype.h>
|
||||
#include <LibWeb/Bindings/PlatformObject.h>
|
||||
#include <LibWeb/CSS/Enums.h>
|
||||
#include <LibWeb/CSS/StyleValues/EasingStyleValue.h>
|
||||
|
||||
namespace Web::Animations {
|
||||
|
||||
|
@ -92,11 +92,8 @@ public:
|
|||
Bindings::PlaybackDirection playback_direction() const { return m_playback_direction; }
|
||||
void set_playback_direction(Bindings::PlaybackDirection playback_direction) { m_playback_direction = playback_direction; }
|
||||
|
||||
String const& easing_function() const { return m_easing_function; }
|
||||
void set_easing_function(String easing_function) { m_easing_function = move(easing_function); }
|
||||
|
||||
TimingFunction const& timing_function() { return m_timing_function; }
|
||||
void set_timing_function(TimingFunction value) { m_timing_function = move(value); }
|
||||
CSS::EasingStyleValue::Function const& timing_function() { return m_timing_function; }
|
||||
void set_timing_function(CSS::EasingStyleValue::Function value) { m_timing_function = move(value); }
|
||||
|
||||
JS::GCPtr<Animation> associated_animation() const { return m_associated_animation; }
|
||||
void set_associated_animation(JS::GCPtr<Animation> value);
|
||||
|
@ -177,14 +174,11 @@ protected:
|
|||
// https://www.w3.org/TR/web-animations-1/#playback-direction
|
||||
Bindings::PlaybackDirection m_playback_direction { Bindings::PlaybackDirection::Normal };
|
||||
|
||||
// https://www.w3.org/TR/css-easing-1/#easing-function
|
||||
String m_easing_function { "linear"_string };
|
||||
|
||||
// https://www.w3.org/TR/web-animations-1/#animation-associated-effect
|
||||
JS::GCPtr<Animation> m_associated_animation {};
|
||||
|
||||
// https://www.w3.org/TR/web-animations-1/#time-transformations
|
||||
TimingFunction m_timing_function { LinearTimingFunction {} };
|
||||
CSS::EasingStyleValue::Function m_timing_function { CSS::EasingStyleValue::Linear {} };
|
||||
|
||||
// Used for calculating transitions in StyleComputer
|
||||
Phase m_previous_phase { Phase::Idle };
|
||||
|
|
|
@ -762,7 +762,6 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<KeyframeEffect>> KeyframeEffect::construct_
|
|||
effect->m_playback_direction = source->m_playback_direction;
|
||||
|
||||
// - timing function.
|
||||
effect->m_easing_function = source->m_easing_function;
|
||||
effect->m_timing_function = source->m_timing_function;
|
||||
|
||||
return effect;
|
||||
|
|
|
@ -1,212 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Ali Mohammad Pur <mpfard@serenityos.org>
|
||||
* Copyright (c) 2023, Matthew Olsson <mattco@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/BinarySearch.h>
|
||||
#include <LibWeb/Animations/TimingFunction.h>
|
||||
#include <LibWeb/CSS/StyleValues/EasingStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/IntegerStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
|
||||
#include <math.h>
|
||||
|
||||
namespace Web::Animations {
|
||||
|
||||
// https://www.w3.org/TR/css-easing-1/#linear-easing-function
|
||||
double LinearTimingFunction::operator()(double input_progress, bool) const
|
||||
{
|
||||
return input_progress;
|
||||
}
|
||||
|
||||
static double 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);
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/css-easing-1/#cubic-bezier-algo
|
||||
double CubicBezierTimingFunction::operator()(double input_progress, bool) const
|
||||
{
|
||||
// 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 CachedSample { x, y, t };
|
||||
};
|
||||
|
||||
if (m_cached_x_samples.is_empty())
|
||||
m_cached_x_samples.append(solve(0.));
|
||||
|
||||
size_t nearby_index = 0;
|
||||
if (auto found = binary_search(m_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 == m_cached_x_samples.size() || nearby_index + 1 == m_cached_x_samples.size()) {
|
||||
// Produce more samples until we have enough.
|
||||
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);
|
||||
m_cached_x_samples.append(solution);
|
||||
last_x = solution.x;
|
||||
}
|
||||
|
||||
if (auto found = binary_search(m_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 = 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);
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/css-easing-1/#step-easing-algo
|
||||
double StepsTimingFunction::operator()(double input_progress, bool before_flag) const
|
||||
{
|
||||
// 1. Calculate the current step as floor(input progress value × 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 (jump_at_start)
|
||||
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
|
||||
double jumps;
|
||||
if (jump_at_start ^ jump_at_end)
|
||||
jumps = number_of_steps;
|
||||
else if (jump_at_start && jump_at_end)
|
||||
jumps = number_of_steps + 1;
|
||||
else
|
||||
jumps = number_of_steps - 1;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
TimingFunction TimingFunction::from_easing_style_value(CSS::EasingStyleValue const& easing_value)
|
||||
{
|
||||
return easing_value.function().visit(
|
||||
[](CSS::EasingStyleValue::Linear const& linear) {
|
||||
if (!linear.stops.is_empty()) {
|
||||
dbgln("FIXME: Handle linear easing functions with stops");
|
||||
}
|
||||
return TimingFunction { LinearTimingFunction {} };
|
||||
},
|
||||
[](CSS::EasingStyleValue::CubicBezier const& bezier) {
|
||||
return TimingFunction { CubicBezierTimingFunction { bezier.x1, bezier.y1, bezier.x2, bezier.y2 } };
|
||||
},
|
||||
[](CSS::EasingStyleValue::Steps const& steps) {
|
||||
auto jump_at_start = false;
|
||||
auto jump_at_end = false;
|
||||
|
||||
switch (steps.position) {
|
||||
case CSS::EasingStyleValue::Steps::Position::Start:
|
||||
case CSS::EasingStyleValue::Steps::Position::JumpStart:
|
||||
jump_at_start = true;
|
||||
break;
|
||||
case CSS::EasingStyleValue::Steps::Position::End:
|
||||
case CSS::EasingStyleValue::Steps::Position::JumpEnd:
|
||||
jump_at_end = true;
|
||||
break;
|
||||
case CSS::EasingStyleValue::Steps::Position::JumpBoth:
|
||||
jump_at_start = true;
|
||||
jump_at_end = true;
|
||||
break;
|
||||
case CSS::EasingStyleValue::Steps::Position::JumpNone:
|
||||
break;
|
||||
}
|
||||
|
||||
return TimingFunction { StepsTimingFunction { steps.number_of_intervals, jump_at_start, jump_at_end } };
|
||||
});
|
||||
}
|
||||
|
||||
double TimingFunction::operator()(double input_progress, bool before_flag) const
|
||||
{
|
||||
return function.visit([&](auto const& f) { return f(input_progress, before_flag); });
|
||||
}
|
||||
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Ali Mohammad Pur <mpfard@serenityos.org>
|
||||
* Copyright (c) 2023, Matthew Olsson <mattco@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Function.h>
|
||||
#include <AK/Vector.h>
|
||||
|
||||
namespace Web::CSS {
|
||||
class EasingStyleValue;
|
||||
}
|
||||
|
||||
namespace Web::Animations {
|
||||
|
||||
// https://www.w3.org/TR/css-easing-1/#the-linear-easing-function
|
||||
struct LinearTimingFunction {
|
||||
double operator()(double t, bool) const;
|
||||
};
|
||||
|
||||
// https://www.w3.org/TR/css-easing-1/#cubic-bezier-easing-functions
|
||||
struct CubicBezierTimingFunction {
|
||||
double x1;
|
||||
double y1;
|
||||
double x2;
|
||||
double y2;
|
||||
|
||||
struct CachedSample {
|
||||
double x;
|
||||
double y;
|
||||
double t;
|
||||
};
|
||||
|
||||
mutable Vector<CachedSample, 64> m_cached_x_samples = {};
|
||||
|
||||
double operator()(double input_progress, bool) const;
|
||||
};
|
||||
|
||||
// https://www.w3.org/TR/css-easing-1/#step-easing-functions
|
||||
struct StepsTimingFunction {
|
||||
size_t number_of_steps;
|
||||
bool jump_at_start;
|
||||
bool jump_at_end;
|
||||
|
||||
double operator()(double input_progress, bool before_flag) const;
|
||||
};
|
||||
|
||||
struct TimingFunction {
|
||||
static TimingFunction from_easing_style_value(CSS::EasingStyleValue const&);
|
||||
|
||||
Variant<LinearTimingFunction, CubicBezierTimingFunction, StepsTimingFunction> function;
|
||||
|
||||
double operator()(double input_progress, bool before_flag) const;
|
||||
};
|
||||
|
||||
}
|
|
@ -9,7 +9,6 @@ set(SOURCES
|
|||
Animations/AnimationTimeline.cpp
|
||||
Animations/DocumentTimeline.cpp
|
||||
Animations/KeyframeEffect.cpp
|
||||
Animations/TimingFunction.cpp
|
||||
ARIA/AriaData.cpp
|
||||
ARIA/ARIAMixin.cpp
|
||||
ARIA/Roles.cpp
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <LibWeb/Animations/Animation.h>
|
||||
#include <LibWeb/Animations/TimingFunction.h>
|
||||
#include <LibWeb/CSS/PropertyID.h>
|
||||
#include <LibWeb/CSS/StyleValue.h>
|
||||
#include <LibWeb/CSS/Time.h>
|
||||
|
|
|
@ -26,7 +26,6 @@
|
|||
#include <LibGfx/Font/WOFF2/Font.h>
|
||||
#include <LibWeb/Animations/AnimationEffect.h>
|
||||
#include <LibWeb/Animations/DocumentTimeline.h>
|
||||
#include <LibWeb/Animations/TimingFunction.h>
|
||||
#include <LibWeb/CSS/AnimationEvent.h>
|
||||
#include <LibWeb/CSS/CSSAnimation.h>
|
||||
#include <LibWeb/CSS/CSSFontFaceRule.h>
|
||||
|
@ -1557,10 +1556,9 @@ static void apply_animation_properties(DOM::Document& document, StyleProperties&
|
|||
play_state = *play_state_value;
|
||||
}
|
||||
|
||||
static Animations::TimingFunction ease_timing_function = Animations::TimingFunction::from_easing_style_value(*CSS::EasingStyleValue::create(CSS::EasingStyleValue::CubicBezier::ease()));
|
||||
Animations::TimingFunction timing_function = ease_timing_function;
|
||||
CSS::EasingStyleValue::Function timing_function { CSS::EasingStyleValue::CubicBezier::ease() };
|
||||
if (auto timing_property = style.maybe_null_property(PropertyID::AnimationTimingFunction); timing_property && timing_property->is_easing())
|
||||
timing_function = Animations::TimingFunction::from_easing_style_value(timing_property->as_easing());
|
||||
timing_function = timing_property->as_easing().function();
|
||||
|
||||
auto iteration_duration = duration.has_value()
|
||||
? Variant<double, String> { duration.release_value().to_milliseconds() }
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -40,7 +40,15 @@ public:
|
|||
double x2;
|
||||
double y2;
|
||||
|
||||
bool operator==(CubicBezier const&) const = default;
|
||||
struct CachedSample {
|
||||
double x;
|
||||
double y;
|
||||
double t;
|
||||
};
|
||||
|
||||
mutable Vector<CachedSample, 64> m_cached_x_samples {};
|
||||
|
||||
bool operator==(CubicBezier const&) const;
|
||||
};
|
||||
|
||||
struct Steps {
|
||||
|
@ -62,7 +70,14 @@ public:
|
|||
bool operator==(Steps const&) const = default;
|
||||
};
|
||||
|
||||
using Function = Variant<Linear, CubicBezier, Steps>;
|
||||
struct Function : public Variant<Linear, CubicBezier, Steps>
|
||||
{
|
||||
using Variant::Variant;
|
||||
|
||||
double evaluate_at(double input_progress, bool before_flag) const;
|
||||
|
||||
String to_string() const;
|
||||
};
|
||||
|
||||
static ValueComparingNonnullRefPtr<EasingStyleValue> create(Function const& function)
|
||||
{
|
||||
|
@ -72,7 +87,7 @@ public:
|
|||
|
||||
Function const& function() const { return m_function; }
|
||||
|
||||
virtual String to_string() const override;
|
||||
virtual String to_string() const override { return m_function.to_string(); }
|
||||
|
||||
bool properties_equal(EasingStyleValue const& other) const { return m_function == other.m_function; }
|
||||
|
||||
|
|
Loading…
Reference in a new issue