mirror of
https://github.com/SerenityOS/serenity
synced 2024-10-01 13:44:21 +00:00
LibWeb: Parse easing values manually
The values aren't that complex, so it doesn't make much sense to have a dedicated generator for them. Parsing them manually also allows us to have much more control over the produced values, so as a result of this change, EasingStyleValue becomes much more ergonomic. (cherry picked from commit 667e313731f06fabf2a3f75893c3e8f15a4172be, manually amended with the output of `git clang-format master`)
This commit is contained in:
parent
4bf4905131
commit
e301c1d038
|
@ -0,0 +1 @@
|
||||||
|
PASS
|
|
@ -0,0 +1,74 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<div id="foo"></div>
|
||||||
|
<script src="../../include.js"></script>
|
||||||
|
<script>
|
||||||
|
test(() => {
|
||||||
|
const div = document.getElementById("foo");
|
||||||
|
|
||||||
|
const validEasings = [
|
||||||
|
"linear",
|
||||||
|
"linear(0, 1)",
|
||||||
|
"linear(0, 0.5, 1)",
|
||||||
|
"linear(0 5%, 0.5 10%, 1 100%)",
|
||||||
|
"linear(5% 0, 10% 0.5, 100% 1)",
|
||||||
|
"linear(5% 0, 1 100%)",
|
||||||
|
"linear(-14, 27 210%)",
|
||||||
|
"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)",
|
||||||
|
"step-start",
|
||||||
|
"step-end",
|
||||||
|
"steps(1000)",
|
||||||
|
"steps(10, jump-start)",
|
||||||
|
"steps(10, jump-end)",
|
||||||
|
"steps(10, jump-none)",
|
||||||
|
"steps(10, jump-both)",
|
||||||
|
"steps(10, start)",
|
||||||
|
"steps(10, end)",
|
||||||
|
];
|
||||||
|
|
||||||
|
const invalidEasings = [
|
||||||
|
"abc",
|
||||||
|
"foo()",
|
||||||
|
"linear()",
|
||||||
|
"linear(a, b, c)",
|
||||||
|
"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)",
|
||||||
|
"steps(1.5)",
|
||||||
|
"steps(-1)",
|
||||||
|
"steps(0, jump-none)",
|
||||||
|
];
|
||||||
|
|
||||||
|
let numFailed = 0;
|
||||||
|
|
||||||
|
for (const easing of validEasings) {
|
||||||
|
try {
|
||||||
|
div.animate(null, { duration: 1, easing });
|
||||||
|
} catch {
|
||||||
|
println(`Failed to parse valid easing ${easing}`);
|
||||||
|
numFailed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const easing of invalidEasings) {
|
||||||
|
try {
|
||||||
|
div.animate(null, { duration: 1, easing });
|
||||||
|
println(`Successfully parsed invalid easing ${easing}`);
|
||||||
|
numFailed++;
|
||||||
|
} catch {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (numFailed === 0)
|
||||||
|
println(`PASS`);
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -184,7 +184,7 @@ protected:
|
||||||
JS::GCPtr<Animation> m_associated_animation {};
|
JS::GCPtr<Animation> m_associated_animation {};
|
||||||
|
|
||||||
// https://www.w3.org/TR/web-animations-1/#time-transformations
|
// https://www.w3.org/TR/web-animations-1/#time-transformations
|
||||||
TimingFunction m_timing_function { linear_timing_function };
|
TimingFunction m_timing_function { LinearTimingFunction {} };
|
||||||
|
|
||||||
// Used for calculating transitions in StyleComputer
|
// Used for calculating transitions in StyleComputer
|
||||||
Phase m_previous_phase { Phase::Idle };
|
Phase m_previous_phase { Phase::Idle };
|
||||||
|
|
|
@ -169,76 +169,39 @@ double StepsTimingFunction::operator()(double input_progress, bool before_flag)
|
||||||
|
|
||||||
TimingFunction TimingFunction::from_easing_style_value(CSS::EasingStyleValue const& easing_value)
|
TimingFunction TimingFunction::from_easing_style_value(CSS::EasingStyleValue const& easing_value)
|
||||||
{
|
{
|
||||||
switch (easing_value.easing_function()) {
|
return easing_value.function().visit(
|
||||||
case CSS::EasingFunction::Linear:
|
[](CSS::EasingStyleValue::Linear const& linear) {
|
||||||
return Animations::linear_timing_function;
|
if (!linear.stops.is_empty()) {
|
||||||
case CSS::EasingFunction::Ease:
|
dbgln("FIXME: Handle linear easing functions with stops");
|
||||||
return Animations::ease_timing_function;
|
}
|
||||||
case CSS::EasingFunction::EaseIn:
|
return TimingFunction { LinearTimingFunction {} };
|
||||||
return Animations::ease_in_timing_function;
|
},
|
||||||
case CSS::EasingFunction::EaseOut:
|
[](CSS::EasingStyleValue::CubicBezier const& bezier) {
|
||||||
return Animations::ease_out_timing_function;
|
return TimingFunction { CubicBezierTimingFunction { bezier.x1, bezier.y1, bezier.x2, bezier.y2 } };
|
||||||
case CSS::EasingFunction::EaseInOut:
|
},
|
||||||
return Animations::ease_in_out_timing_function;
|
[](CSS::EasingStyleValue::Steps const& steps) {
|
||||||
case CSS::EasingFunction::CubicBezier: {
|
auto jump_at_start = false;
|
||||||
auto values = easing_value.values();
|
auto jump_at_end = false;
|
||||||
return {
|
|
||||||
Animations::CubicBezierTimingFunction {
|
|
||||||
values[0]->as_number().number(),
|
|
||||||
values[1]->as_number().number(),
|
|
||||||
values[2]->as_number().number(),
|
|
||||||
values[3]->as_number().number(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case CSS::EasingFunction::Steps: {
|
|
||||||
auto values = easing_value.values();
|
|
||||||
auto jump_at_start = false;
|
|
||||||
auto jump_at_end = true;
|
|
||||||
|
|
||||||
if (values.size() > 1) {
|
switch (steps.position) {
|
||||||
auto identifier = values[1]->to_identifier();
|
case CSS::EasingStyleValue::Steps::Position::Start:
|
||||||
switch (identifier) {
|
case CSS::EasingStyleValue::Steps::Position::JumpStart:
|
||||||
case CSS::ValueID::JumpStart:
|
|
||||||
case CSS::ValueID::Start:
|
|
||||||
jump_at_start = true;
|
jump_at_start = true;
|
||||||
jump_at_end = false;
|
|
||||||
break;
|
break;
|
||||||
case CSS::ValueID::JumpEnd:
|
case CSS::EasingStyleValue::Steps::Position::End:
|
||||||
case CSS::ValueID::End:
|
case CSS::EasingStyleValue::Steps::Position::JumpEnd:
|
||||||
jump_at_start = false;
|
|
||||||
jump_at_end = true;
|
jump_at_end = true;
|
||||||
break;
|
break;
|
||||||
case CSS::ValueID::JumpNone:
|
case CSS::EasingStyleValue::Steps::Position::JumpBoth:
|
||||||
jump_at_start = false;
|
jump_at_start = true;
|
||||||
jump_at_end = false;
|
jump_at_end = true;
|
||||||
break;
|
break;
|
||||||
default:
|
case CSS::EasingStyleValue::Steps::Position::JumpNone:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return Animations::TimingFunction { Animations::StepsTimingFunction {
|
return TimingFunction { StepsTimingFunction { steps.number_of_intervals, jump_at_start, jump_at_end } };
|
||||||
.number_of_steps = static_cast<size_t>(max(values[0]->as_integer().integer(), !(jump_at_end && jump_at_start) ? 1 : 0)),
|
});
|
||||||
.jump_at_start = jump_at_start,
|
|
||||||
.jump_at_end = jump_at_end,
|
|
||||||
} };
|
|
||||||
}
|
|
||||||
case CSS::EasingFunction::StepEnd:
|
|
||||||
return Animations::TimingFunction { Animations::StepsTimingFunction {
|
|
||||||
.number_of_steps = 1,
|
|
||||||
.jump_at_start = false,
|
|
||||||
.jump_at_end = true,
|
|
||||||
} };
|
|
||||||
case CSS::EasingFunction::StepStart:
|
|
||||||
return Animations::TimingFunction { Animations::StepsTimingFunction {
|
|
||||||
.number_of_steps = 1,
|
|
||||||
.jump_at_start = true,
|
|
||||||
.jump_at_end = false,
|
|
||||||
} };
|
|
||||||
default:
|
|
||||||
return Animations::ease_timing_function;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
double TimingFunction::operator()(double input_progress, bool before_flag) const
|
double TimingFunction::operator()(double input_progress, bool before_flag) const
|
||||||
|
|
|
@ -56,11 +56,4 @@ struct TimingFunction {
|
||||||
double operator()(double input_progress, bool before_flag) const;
|
double operator()(double input_progress, bool before_flag) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
static TimingFunction linear_timing_function { LinearTimingFunction {} };
|
|
||||||
// NOTE: Magic values from <https://www.w3.org/TR/css-easing-1/#valdef-cubic-bezier-easing-function-ease>
|
|
||||||
static TimingFunction ease_timing_function { CubicBezierTimingFunction { 0.25, 0.1, 0.25, 1.0 } };
|
|
||||||
static TimingFunction ease_in_timing_function { CubicBezierTimingFunction { 0.42, 0.0, 1.0, 1.0 } };
|
|
||||||
static TimingFunction ease_out_timing_function { CubicBezierTimingFunction { 0.0, 0.0, 0.58, 1.0 } };
|
|
||||||
static TimingFunction ease_in_out_timing_function { CubicBezierTimingFunction { 0.42, 0.0, 0.58, 1.0 } };
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5065,102 +5065,169 @@ RefPtr<StyleValue> Parser::parse_easing_value(TokenStream<ComponentValue>& token
|
||||||
|
|
||||||
auto const& part = tokens.next_token();
|
auto const& part = tokens.next_token();
|
||||||
|
|
||||||
StringView name;
|
|
||||||
Optional<Vector<ComponentValue> const&> arguments;
|
|
||||||
if (part.is(Token::Type::Ident)) {
|
if (part.is(Token::Type::Ident)) {
|
||||||
name = part.token().ident();
|
auto name = part.token().ident();
|
||||||
} else if (part.is_function()) {
|
auto maybe_simple_easing = [&] -> RefPtr<EasingStyleValue> {
|
||||||
name = part.function().name();
|
if (name == "linear"sv)
|
||||||
arguments = part.function().values();
|
return EasingStyleValue::create(EasingStyleValue::Linear {});
|
||||||
} else {
|
if (name == "ease"sv)
|
||||||
return nullptr;
|
return EasingStyleValue::create(EasingStyleValue::CubicBezier::ease());
|
||||||
}
|
if (name == "ease-in"sv)
|
||||||
|
return EasingStyleValue::create(EasingStyleValue::CubicBezier::ease_in());
|
||||||
|
if (name == "ease-out"sv)
|
||||||
|
return EasingStyleValue::create(EasingStyleValue::CubicBezier::ease_out());
|
||||||
|
if (name == "ease-in-out"sv)
|
||||||
|
return EasingStyleValue::create(EasingStyleValue::CubicBezier::ease_in_out());
|
||||||
|
if (name == "step-start"sv)
|
||||||
|
return EasingStyleValue::create(EasingStyleValue::Steps::step_start());
|
||||||
|
if (name == "step-end"sv)
|
||||||
|
return EasingStyleValue::create(EasingStyleValue::Steps::step_end());
|
||||||
|
return {};
|
||||||
|
}();
|
||||||
|
|
||||||
auto maybe_function = easing_function_from_string(name);
|
if (!maybe_simple_easing)
|
||||||
if (!maybe_function.has_value())
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
auto function = maybe_function.release_value();
|
|
||||||
auto function_metadata = easing_function_metadata(function);
|
|
||||||
|
|
||||||
if (function_metadata.parameters.is_empty() && arguments.has_value()) {
|
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Too many arguments to {}. max: 0", name);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
StyleValueVector values;
|
|
||||||
size_t argument_index = 0;
|
|
||||||
if (arguments.has_value()) {
|
|
||||||
auto argument_tokens = TokenStream { *arguments };
|
|
||||||
auto arguments_values = parse_a_comma_separated_list_of_component_values(argument_tokens);
|
|
||||||
if (arguments_values.size() > function_metadata.parameters.size()) {
|
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Too many arguments to {}. max: {}", name, function_metadata.parameters.size());
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
|
||||||
for (auto& argument_values : arguments_values) {
|
|
||||||
// Prune any whitespace before and after the actual argument values.
|
|
||||||
argument_values.remove_all_matching([](auto& value) { return value.is(Token::Type::Whitespace); });
|
|
||||||
|
|
||||||
if (argument_values.size() != 1) {
|
transaction.commit();
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Too many values in argument to {}. max: 1", name);
|
return maybe_simple_easing;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!part.is_function())
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
TokenStream argument_tokens { part.function().values() };
|
||||||
|
auto comma_separated_arguments = parse_a_comma_separated_list_of_component_values(argument_tokens);
|
||||||
|
|
||||||
|
// Remove whitespace
|
||||||
|
for (auto& argument : comma_separated_arguments)
|
||||||
|
argument.remove_all_matching([](auto& value) { return value.is(Token::Type::Whitespace); });
|
||||||
|
|
||||||
|
auto name = part.function().name();
|
||||||
|
if (name == "linear"sv) {
|
||||||
|
Vector<EasingStyleValue::Linear::Stop> stops;
|
||||||
|
for (auto const& argument : comma_separated_arguments) {
|
||||||
|
if (argument.is_empty() || argument.size() > 2)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
Optional<double> offset;
|
||||||
|
Optional<double> position;
|
||||||
|
|
||||||
|
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 (!offset.has_value())
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
stops.append({ offset.value(), move(position) });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stops.is_empty())
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
transaction.commit();
|
||||||
|
return EasingStyleValue::create(EasingStyleValue::Linear { move(stops) });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name == "cubic-bezier") {
|
||||||
|
if (comma_separated_arguments.size() != 4)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
for (auto const& argument : comma_separated_arguments) {
|
||||||
|
if (argument.size() != 1)
|
||||||
|
return nullptr;
|
||||||
|
if (!argument[0].is(Token::Type::Number))
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
EasingStyleValue::CubicBezier bezier {
|
||||||
|
comma_separated_arguments[0][0].token().number_value(),
|
||||||
|
comma_separated_arguments[1][0].token().number_value(),
|
||||||
|
comma_separated_arguments[2][0].token().number_value(),
|
||||||
|
comma_separated_arguments[3][0].token().number_value(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (bezier.x1 < 0.0 || bezier.x1 > 1.0 || bezier.x2 < 0.0 || bezier.x2 > 1.0)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
transaction.commit();
|
||||||
|
return EasingStyleValue::create(bezier);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name == "steps") {
|
||||||
|
if (comma_separated_arguments.is_empty() || comma_separated_arguments.size() > 2)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
for (auto const& argument : comma_separated_arguments) {
|
||||||
|
if (argument.size() != 1)
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
EasingStyleValue::Steps steps;
|
||||||
|
|
||||||
|
auto intervals_argument = comma_separated_arguments[0][0];
|
||||||
|
if (!intervals_argument.is(Token::Type::Number))
|
||||||
|
return nullptr;
|
||||||
|
if (!intervals_argument.token().number().is_integer())
|
||||||
|
return nullptr;
|
||||||
|
auto intervals = intervals_argument.token().to_integer();
|
||||||
|
|
||||||
|
if (comma_separated_arguments.size() == 2) {
|
||||||
|
TokenStream identifier_stream { comma_separated_arguments[1] };
|
||||||
|
auto ident = parse_identifier_value(identifier_stream);
|
||||||
|
if (!ident)
|
||||||
|
return nullptr;
|
||||||
|
switch (ident->to_identifier()) {
|
||||||
|
case ValueID::JumpStart:
|
||||||
|
steps.position = EasingStyleValue::Steps::Position::JumpStart;
|
||||||
|
break;
|
||||||
|
case ValueID::JumpEnd:
|
||||||
|
steps.position = EasingStyleValue::Steps::Position::JumpEnd;
|
||||||
|
break;
|
||||||
|
case ValueID::JumpBoth:
|
||||||
|
steps.position = EasingStyleValue::Steps::Position::JumpBoth;
|
||||||
|
break;
|
||||||
|
case ValueID::JumpNone:
|
||||||
|
steps.position = EasingStyleValue::Steps::Position::JumpNone;
|
||||||
|
break;
|
||||||
|
case ValueID::Start:
|
||||||
|
steps.position = EasingStyleValue::Steps::Position::Start;
|
||||||
|
break;
|
||||||
|
case ValueID::End:
|
||||||
|
steps.position = EasingStyleValue::Steps::Position::End;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& value = argument_values[0];
|
|
||||||
auto value_as_stream = TokenStream { argument_values };
|
|
||||||
switch (function_metadata.parameters[argument_index].type) {
|
|
||||||
case EasingFunctionParameterType::Number: {
|
|
||||||
if (value.is(Token::Type::Number))
|
|
||||||
values.append(NumberStyleValue::create(value.token().number().value()));
|
|
||||||
else
|
|
||||||
return nullptr;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case EasingFunctionParameterType::NumberZeroToOne: {
|
|
||||||
if (value.is(Token::Type::Number) && value.token().number_value() >= 0 && value.token().number_value() <= 1)
|
|
||||||
values.append(NumberStyleValue::create(value.token().number().value()));
|
|
||||||
else
|
|
||||||
return nullptr;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case EasingFunctionParameterType::Integer: {
|
|
||||||
if (value.is(Token::Type::Number) && value.token().number().is_integer())
|
|
||||||
values.append(IntegerStyleValue::create(value.token().number().integer_value()));
|
|
||||||
else
|
|
||||||
return nullptr;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case EasingFunctionParameterType::StepPosition: {
|
|
||||||
if (!value.is(Token::Type::Ident))
|
|
||||||
return nullptr;
|
|
||||||
auto ident = parse_identifier_value(value_as_stream);
|
|
||||||
if (!ident)
|
|
||||||
return nullptr;
|
|
||||||
switch (ident->to_identifier()) {
|
|
||||||
case ValueID::JumpStart:
|
|
||||||
case ValueID::JumpEnd:
|
|
||||||
case ValueID::JumpNone:
|
|
||||||
case ValueID::Start:
|
|
||||||
case ValueID::End:
|
|
||||||
values.append(*ident);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
++argument_index;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Perform extra validation
|
||||||
|
// https://drafts.csswg.org/css-easing/#funcdef-step-easing-function-steps
|
||||||
|
// The first parameter specifies the number of intervals in the function. It must be a positive integer greater than 0
|
||||||
|
// unless the second parameter is jump-none in which case it must be a positive integer greater than 1.
|
||||||
|
if (steps.position == EasingStyleValue::Steps::Position::JumpNone) {
|
||||||
|
if (intervals < 1)
|
||||||
|
return nullptr;
|
||||||
|
} else if (intervals < 0) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
steps.number_of_intervals = intervals;
|
||||||
|
transaction.commit();
|
||||||
|
return EasingStyleValue::create(steps);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argument_index < function_metadata.parameters.size() && !function_metadata.parameters[argument_index].is_optional) {
|
return nullptr;
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Required parameter at position {} is missing", argument_index);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
transaction.commit();
|
|
||||||
return EasingStyleValue::create(function, move(values));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://www.w3.org/TR/css-transforms-1/#transform-property
|
// https://www.w3.org/TR/css-transforms-1/#transform-property
|
||||||
|
@ -5483,7 +5550,7 @@ RefPtr<StyleValue> Parser::parse_transition_value(TokenStream<ComponentValue>& t
|
||||||
transition.property_name = CustomIdentStyleValue::create("all"_fly_string);
|
transition.property_name = CustomIdentStyleValue::create("all"_fly_string);
|
||||||
|
|
||||||
if (!transition.easing)
|
if (!transition.easing)
|
||||||
transition.easing = EasingStyleValue::create(EasingFunction::Ease, {});
|
transition.easing = EasingStyleValue::create(EasingStyleValue::CubicBezier::ease());
|
||||||
|
|
||||||
transitions.append(move(transition));
|
transitions.append(move(transition));
|
||||||
|
|
||||||
|
|
|
@ -1557,7 +1557,8 @@ static void apply_animation_properties(DOM::Document& document, StyleProperties&
|
||||||
play_state = *play_state_value;
|
play_state = *play_state_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
Animations::TimingFunction timing_function = Animations::ease_timing_function;
|
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;
|
||||||
if (auto timing_property = style.maybe_null_property(PropertyID::AnimationTimingFunction); timing_property && timing_property->is_easing())
|
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 = Animations::TimingFunction::from_easing_style_value(timing_property->as_easing());
|
||||||
|
|
||||||
|
|
|
@ -13,33 +13,107 @@
|
||||||
|
|
||||||
namespace Web::CSS {
|
namespace Web::CSS {
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
{
|
||||||
|
static CubicBezier bezier { 0.25, 0.1, 0.25, 1.0 };
|
||||||
|
return bezier;
|
||||||
|
}
|
||||||
|
|
||||||
|
EasingStyleValue::CubicBezier EasingStyleValue::CubicBezier::ease_in()
|
||||||
|
{
|
||||||
|
static CubicBezier bezier { 0.42, 0.0, 1.0, 1.0 };
|
||||||
|
return bezier;
|
||||||
|
}
|
||||||
|
|
||||||
|
EasingStyleValue::CubicBezier EasingStyleValue::CubicBezier::ease_out()
|
||||||
|
{
|
||||||
|
static CubicBezier bezier { 0.0, 0.0, 0.58, 1.0 };
|
||||||
|
return bezier;
|
||||||
|
}
|
||||||
|
|
||||||
|
EasingStyleValue::CubicBezier EasingStyleValue::CubicBezier::ease_in_out()
|
||||||
|
{
|
||||||
|
static CubicBezier bezier { 0.42, 0.0, 0.58, 1.0 };
|
||||||
|
return bezier;
|
||||||
|
}
|
||||||
|
|
||||||
|
EasingStyleValue::Steps EasingStyleValue::Steps::step_start()
|
||||||
|
{
|
||||||
|
static Steps steps { 1, Steps::Position::Start };
|
||||||
|
return steps;
|
||||||
|
}
|
||||||
|
|
||||||
|
EasingStyleValue::Steps EasingStyleValue::Steps::step_end()
|
||||||
|
{
|
||||||
|
static Steps steps { 1, Steps::Position::End };
|
||||||
|
return steps;
|
||||||
|
}
|
||||||
|
|
||||||
String EasingStyleValue::to_string() const
|
String EasingStyleValue::to_string() const
|
||||||
{
|
{
|
||||||
if (m_properties.easing_function == EasingFunction::StepStart)
|
|
||||||
return "steps(1, start)"_string;
|
|
||||||
if (m_properties.easing_function == EasingFunction::StepEnd)
|
|
||||||
return "steps(1, end)"_string;
|
|
||||||
|
|
||||||
StringBuilder builder;
|
StringBuilder builder;
|
||||||
builder.append(CSS::to_string(m_properties.easing_function));
|
m_function.visit(
|
||||||
|
[&](Linear const& linear) {
|
||||||
|
builder.append("linear"sv);
|
||||||
|
if (!linear.stops.is_empty()) {
|
||||||
|
builder.append('(');
|
||||||
|
|
||||||
if (m_properties.values.is_empty())
|
bool first = true;
|
||||||
return MUST(builder.to_string());
|
for (auto const& stop : linear.stops) {
|
||||||
|
if (!first)
|
||||||
builder.append('(');
|
builder.append(", "sv);
|
||||||
for (size_t i = 0; i < m_properties.values.size(); ++i) {
|
first = false;
|
||||||
builder.append(m_properties.values[i]->to_string());
|
builder.appendff("{}"sv, stop.offset);
|
||||||
if (i != m_properties.values.size() - 1)
|
if (stop.position.has_value())
|
||||||
builder.append(", "sv);
|
builder.appendff(" {}"sv, stop.position.value());
|
||||||
}
|
}
|
||||||
builder.append(')');
|
|
||||||
|
|
||||||
|
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()) {
|
||||||
|
builder.append("step-start"sv);
|
||||||
|
} else if (steps == Steps::step_end()) {
|
||||||
|
builder.append("step-end"sv);
|
||||||
|
} else {
|
||||||
|
auto position = [&] -> Optional<StringView> {
|
||||||
|
switch (steps.position) {
|
||||||
|
case Steps::Position::JumpStart:
|
||||||
|
return "jump-start"sv;
|
||||||
|
case Steps::Position::JumpNone:
|
||||||
|
return "jump-none"sv;
|
||||||
|
case Steps::Position::JumpBoth:
|
||||||
|
return "jump-both"sv;
|
||||||
|
case Steps::Position::Start:
|
||||||
|
return "start"sv;
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
if (position.has_value()) {
|
||||||
|
builder.appendff("steps({}, {})", steps.number_of_intervals, position.value());
|
||||||
|
} else {
|
||||||
|
builder.appendff("steps({})", steps.number_of_intervals);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
return MUST(builder.to_string());
|
return MUST(builder.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EasingStyleValue::Properties::operator==(Properties const& other) const
|
|
||||||
{
|
|
||||||
return easing_function == other.easing_function && values == other.values;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,38 +10,80 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <LibWeb/CSS/EasingFunctions.h>
|
|
||||||
#include <LibWeb/CSS/StyleValue.h>
|
#include <LibWeb/CSS/StyleValue.h>
|
||||||
|
|
||||||
namespace Web::CSS {
|
namespace Web::CSS {
|
||||||
|
|
||||||
class EasingStyleValue final : public StyleValueWithDefaultOperators<EasingStyleValue> {
|
class EasingStyleValue final : public StyleValueWithDefaultOperators<EasingStyleValue> {
|
||||||
public:
|
public:
|
||||||
static ValueComparingNonnullRefPtr<EasingStyleValue> create(CSS::EasingFunction easing_function, StyleValueVector&& values)
|
struct Linear {
|
||||||
|
struct Stop {
|
||||||
|
double offset;
|
||||||
|
Optional<double> position;
|
||||||
|
|
||||||
|
bool operator==(Stop const&) const = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
Vector<Stop> stops;
|
||||||
|
|
||||||
|
bool operator==(Linear const&) const = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CubicBezier {
|
||||||
|
static CubicBezier ease();
|
||||||
|
static CubicBezier ease_in();
|
||||||
|
static CubicBezier ease_out();
|
||||||
|
static CubicBezier ease_in_out();
|
||||||
|
|
||||||
|
double x1;
|
||||||
|
double y1;
|
||||||
|
double x2;
|
||||||
|
double y2;
|
||||||
|
|
||||||
|
bool operator==(CubicBezier const&) const = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Steps {
|
||||||
|
enum class Position {
|
||||||
|
JumpStart,
|
||||||
|
JumpEnd,
|
||||||
|
JumpNone,
|
||||||
|
JumpBoth,
|
||||||
|
Start,
|
||||||
|
End,
|
||||||
|
};
|
||||||
|
|
||||||
|
static Steps step_start();
|
||||||
|
static Steps step_end();
|
||||||
|
|
||||||
|
unsigned int number_of_intervals;
|
||||||
|
Position position { Position::End };
|
||||||
|
|
||||||
|
bool operator==(Steps const&) const = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
using Function = Variant<Linear, CubicBezier, Steps>;
|
||||||
|
|
||||||
|
static ValueComparingNonnullRefPtr<EasingStyleValue> create(Function const& function)
|
||||||
{
|
{
|
||||||
return adopt_ref(*new (nothrow) EasingStyleValue(easing_function, move(values)));
|
return adopt_ref(*new (nothrow) EasingStyleValue(function));
|
||||||
}
|
}
|
||||||
virtual ~EasingStyleValue() override = default;
|
virtual ~EasingStyleValue() override = default;
|
||||||
|
|
||||||
CSS::EasingFunction easing_function() const { return m_properties.easing_function; }
|
Function const& function() const { return m_function; }
|
||||||
StyleValueVector values() const { return m_properties.values; }
|
|
||||||
|
|
||||||
virtual String to_string() const override;
|
virtual String to_string() const override;
|
||||||
|
|
||||||
bool properties_equal(EasingStyleValue const& other) const { return m_properties == other.m_properties; }
|
bool properties_equal(EasingStyleValue const& other) const { return m_function == other.m_function; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
EasingStyleValue(CSS::EasingFunction easing_function, StyleValueVector&& values)
|
EasingStyleValue(Function function)
|
||||||
: StyleValueWithDefaultOperators(Type::Easing)
|
: StyleValueWithDefaultOperators(Type::Easing)
|
||||||
, m_properties { .easing_function = easing_function, .values = move(values) }
|
, m_function(function)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Properties {
|
Function m_function;
|
||||||
CSS::EasingFunction easing_function;
|
|
||||||
StyleValueVector values;
|
|
||||||
bool operator==(Properties const& other) const;
|
|
||||||
} m_properties;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue