mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-09-11 05:25:32 +03: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.
This commit is contained in:
parent
6675ef3f24
commit
667e313731
Notes:
sideshowbarker
2024-07-16 22:51:10 +09:00
Author: https://github.com/mattco98 Commit: https://github.com/LadybirdBrowser/ladybird/commit/667e313731 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/173
@ -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 {};
|
||||
|
||||
// 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
|
||||
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)
|
||||
{
|
||||
switch (easing_value.easing_function()) {
|
||||
case CSS::EasingFunction::Linear:
|
||||
return Animations::linear_timing_function;
|
||||
case CSS::EasingFunction::Ease:
|
||||
return Animations::ease_timing_function;
|
||||
case CSS::EasingFunction::EaseIn:
|
||||
return Animations::ease_in_timing_function;
|
||||
case CSS::EasingFunction::EaseOut:
|
||||
return Animations::ease_out_timing_function;
|
||||
case CSS::EasingFunction::EaseInOut:
|
||||
return Animations::ease_in_out_timing_function;
|
||||
case CSS::EasingFunction::CubicBezier: {
|
||||
auto values = easing_value.values();
|
||||
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;
|
||||
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;
|
||||
|
||||
if (values.size() > 1) {
|
||||
auto identifier = values[1]->to_identifier();
|
||||
switch (identifier) {
|
||||
case CSS::ValueID::JumpStart:
|
||||
case CSS::ValueID::Start:
|
||||
switch (steps.position) {
|
||||
case CSS::EasingStyleValue::Steps::Position::Start:
|
||||
case CSS::EasingStyleValue::Steps::Position::JumpStart:
|
||||
jump_at_start = true;
|
||||
jump_at_end = false;
|
||||
break;
|
||||
case CSS::ValueID::JumpEnd:
|
||||
case CSS::ValueID::End:
|
||||
jump_at_start = false;
|
||||
case CSS::EasingStyleValue::Steps::Position::End:
|
||||
case CSS::EasingStyleValue::Steps::Position::JumpEnd:
|
||||
jump_at_end = true;
|
||||
break;
|
||||
case CSS::ValueID::JumpNone:
|
||||
jump_at_start = false;
|
||||
jump_at_end = false;
|
||||
case CSS::EasingStyleValue::Steps::Position::JumpBoth:
|
||||
jump_at_start = true;
|
||||
jump_at_end = true;
|
||||
break;
|
||||
default:
|
||||
case CSS::EasingStyleValue::Steps::Position::JumpNone:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return Animations::TimingFunction { Animations::StepsTimingFunction {
|
||||
.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;
|
||||
}
|
||||
return TimingFunction { StepsTimingFunction { steps.number_of_intervals, jump_at_start, jump_at_end } };
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
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();
|
||||
|
||||
StringView name;
|
||||
Optional<Vector<ComponentValue> const&> arguments;
|
||||
if (part.is(Token::Type::Ident)) {
|
||||
name = part.token().ident();
|
||||
} else if (part.is_function()) {
|
||||
name = part.function().name();
|
||||
arguments = part.function().values();
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
auto name = part.token().ident();
|
||||
auto maybe_simple_easing = [&] -> RefPtr<EasingStyleValue> {
|
||||
if (name == "linear"sv)
|
||||
return EasingStyleValue::create(EasingStyleValue::Linear {});
|
||||
if (name == "ease"sv)
|
||||
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_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());
|
||||
if (!maybe_simple_easing)
|
||||
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) {
|
||||
dbgln_if(CSS_PARSER_DEBUG, "Too many values in argument to {}. max: 1", name);
|
||||
transaction.commit();
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
dbgln_if(CSS_PARSER_DEBUG, "Required parameter at position {} is missing", argument_index);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
transaction.commit();
|
||||
return EasingStyleValue::create(function, move(values));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
if (!transition.easing)
|
||||
transition.easing = EasingStyleValue::create(EasingFunction::Ease, {});
|
||||
transition.easing = EasingStyleValue::create(EasingStyleValue::CubicBezier::ease());
|
||||
|
||||
transitions.append(move(transition));
|
||||
|
||||
|
@ -1557,7 +1557,8 @@ static void apply_animation_properties(DOM::Document& document, StyleProperties&
|
||||
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())
|
||||
timing_function = Animations::TimingFunction::from_easing_style_value(timing_property->as_easing());
|
||||
|
||||
|
@ -13,33 +13,105 @@
|
||||
|
||||
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
|
||||
{
|
||||
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;
|
||||
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())
|
||||
return MUST(builder.to_string());
|
||||
|
||||
builder.append('(');
|
||||
for (size_t i = 0; i < m_properties.values.size(); ++i) {
|
||||
builder.append(m_properties.values[i]->to_string());
|
||||
if (i != m_properties.values.size() - 1)
|
||||
builder.append(", "sv);
|
||||
}
|
||||
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()) {
|
||||
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());
|
||||
}
|
||||
|
||||
bool EasingStyleValue::Properties::operator==(Properties const& other) const
|
||||
{
|
||||
return easing_function == other.easing_function && values == other.values;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -10,38 +10,80 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibWeb/CSS/EasingFunctions.h>
|
||||
#include <LibWeb/CSS/StyleValue.h>
|
||||
|
||||
namespace Web::CSS {
|
||||
|
||||
class EasingStyleValue final : public StyleValueWithDefaultOperators<EasingStyleValue> {
|
||||
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;
|
||||
|
||||
CSS::EasingFunction easing_function() const { return m_properties.easing_function; }
|
||||
StyleValueVector values() const { return m_properties.values; }
|
||||
Function const& function() const { return m_function; }
|
||||
|
||||
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:
|
||||
EasingStyleValue(CSS::EasingFunction easing_function, StyleValueVector&& values)
|
||||
EasingStyleValue(Function function)
|
||||
: StyleValueWithDefaultOperators(Type::Easing)
|
||||
, m_properties { .easing_function = easing_function, .values = move(values) }
|
||||
, m_function(function)
|
||||
{
|
||||
}
|
||||
|
||||
struct Properties {
|
||||
CSS::EasingFunction easing_function;
|
||||
StyleValueVector values;
|
||||
bool operator==(Properties const& other) const;
|
||||
} m_properties;
|
||||
Function m_function;
|
||||
};
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user