mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-06-09 09:34:57 +09:00
LibWeb: Ensure discrete interpolated properties are non-transitionable
If a property is uses discrete interpolation and TransitionBehavior is not set to `AllowDiscrete` that property should be non-transitionable. This is now true for properties whose animation type is not discrete, but the animation type falls back to discrete.
This commit is contained in:
parent
922bf2033f
commit
b16f34767e
Notes:
github-actions[bot]
2025-05-27 11:34:35 +00:00
Author: https://github.com/tcl3
Commit: b16f34767e
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/4845
Reviewed-by: https://github.com/AtkinsSJ
Reviewed-by: https://github.com/gmta ✅
Reviewed-by: https://github.com/kalenikaliaksandr
22 changed files with 545 additions and 489 deletions
|
@ -150,11 +150,11 @@ double CSSTransition::timing_function_output_at_time(double t) const
|
|||
return m_keyframe_effect->timing_function().evaluate_at(progress, before_flag);
|
||||
}
|
||||
|
||||
NonnullRefPtr<CSSStyleValue const> CSSTransition::value_at_time(double t) const
|
||||
NonnullRefPtr<CSSStyleValue const> CSSTransition::value_at_time(double t, AllowDiscrete allow_discrete) const
|
||||
{
|
||||
// https://drafts.csswg.org/css-transitions/#application
|
||||
auto progress = timing_function_output_at_time(t);
|
||||
auto result = interpolate_property(*m_keyframe_effect->target(), m_transition_property, m_start_value, m_end_value, progress);
|
||||
auto result = interpolate_property(*m_keyframe_effect->target(), m_transition_property, m_start_value, m_end_value, progress, allow_discrete);
|
||||
if (result)
|
||||
return result.release_nonnull();
|
||||
return m_start_value;
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include <LibWeb/Animations/Animation.h>
|
||||
#include <LibWeb/CSS/CSSStyleValue.h>
|
||||
#include <LibWeb/CSS/Interpolation.h>
|
||||
#include <LibWeb/CSS/PropertyID.h>
|
||||
#include <LibWeb/CSS/StyleValues/EasingStyleValue.h>
|
||||
#include <LibWeb/CSS/Time.h>
|
||||
|
@ -37,7 +38,7 @@ public:
|
|||
double reversing_shortening_factor() const { return m_reversing_shortening_factor; }
|
||||
|
||||
double timing_function_output_at_time(double t) const;
|
||||
NonnullRefPtr<CSSStyleValue const> value_at_time(double t) const;
|
||||
NonnullRefPtr<CSSStyleValue const> value_at_time(double t, AllowDiscrete allow_discrete) const;
|
||||
|
||||
// This is designed to be created from AnimationEffect::Phase.
|
||||
enum class Phase : u8 {
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include <LibWeb/CSS/PropertyID.h>
|
||||
#include <LibWeb/CSS/StyleComputer.h>
|
||||
#include <LibWeb/CSS/StyleValues/AngleStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/BackgroundSizeStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/CSSColorValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/CSSKeywordValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/CalculatedStyleValue.h>
|
||||
|
@ -69,7 +70,16 @@ static NonnullRefPtr<CSSStyleValue const> with_keyword_values_resolved(DOM::Elem
|
|||
return value;
|
||||
}
|
||||
|
||||
static RefPtr<CSSStyleValue const> interpolate_scale(DOM::Element& element, CalculationContext calculation_context, CSSStyleValue const& a_from, CSSStyleValue const& a_to, float delta)
|
||||
static RefPtr<CSSStyleValue const> interpolate_discrete(CSSStyleValue const& from, CSSStyleValue const& to, float delta, AllowDiscrete allow_discrete)
|
||||
{
|
||||
if (from.equals(to))
|
||||
return from;
|
||||
if (allow_discrete == AllowDiscrete::No)
|
||||
return {};
|
||||
return delta >= 0.5f ? to : from;
|
||||
}
|
||||
|
||||
static RefPtr<CSSStyleValue const> interpolate_scale(DOM::Element& element, CalculationContext calculation_context, CSSStyleValue const& a_from, CSSStyleValue const& a_to, float delta, AllowDiscrete allow_discrete)
|
||||
{
|
||||
if (a_from.to_keyword() == Keyword::None && a_to.to_keyword() == Keyword::None)
|
||||
return a_from;
|
||||
|
@ -82,19 +92,24 @@ static RefPtr<CSSStyleValue const> interpolate_scale(DOM::Element& element, Calc
|
|||
auto const& from_transform = from.as_transformation();
|
||||
auto const& to_transform = to.as_transformation();
|
||||
|
||||
auto interpolated_x = interpolate_value(element, calculation_context, from_transform.values()[0], to_transform.values()[0], delta);
|
||||
auto interpolated_y = interpolate_value(element, calculation_context, from_transform.values()[1], to_transform.values()[1], delta);
|
||||
|
||||
auto interpolated_x = interpolate_value(element, calculation_context, from_transform.values()[0], to_transform.values()[0], delta, allow_discrete);
|
||||
if (!interpolated_x)
|
||||
return {};
|
||||
auto interpolated_y = interpolate_value(element, calculation_context, from_transform.values()[1], to_transform.values()[1], delta, allow_discrete);
|
||||
if (!interpolated_y)
|
||||
return {};
|
||||
RefPtr<CSSStyleValue const> interpolated_z;
|
||||
|
||||
if (from_transform.values().size() == 3 || to_transform.values().size() == 3) {
|
||||
static auto one_value = NumberStyleValue::create(1);
|
||||
auto from = from_transform.values().size() == 3 ? from_transform.values()[2] : one_value;
|
||||
auto to = to_transform.values().size() == 3 ? to_transform.values()[2] : one_value;
|
||||
interpolated_z = interpolate_value(element, calculation_context, from, to, delta);
|
||||
interpolated_z = interpolate_value(element, calculation_context, from, to, delta, allow_discrete);
|
||||
if (!interpolated_z)
|
||||
return {};
|
||||
}
|
||||
|
||||
StyleValueVector new_values = { interpolated_x, interpolated_y };
|
||||
StyleValueVector new_values = { *interpolated_x, *interpolated_y };
|
||||
if (interpolated_z && interpolated_z->is_number() && interpolated_z->as_number().number() != 1) {
|
||||
new_values.append(*interpolated_z);
|
||||
}
|
||||
|
@ -105,7 +120,7 @@ static RefPtr<CSSStyleValue const> interpolate_scale(DOM::Element& element, Calc
|
|||
move(new_values));
|
||||
}
|
||||
|
||||
static RefPtr<CSSStyleValue const> interpolate_translate(DOM::Element& element, CalculationContext calculation_context, CSSStyleValue const& a_from, CSSStyleValue const& a_to, float delta)
|
||||
static RefPtr<CSSStyleValue const> interpolate_translate(DOM::Element& element, CalculationContext calculation_context, CSSStyleValue const& a_from, CSSStyleValue const& a_to, float delta, AllowDiscrete allow_discrete)
|
||||
{
|
||||
if (a_from.to_keyword() == Keyword::None && a_to.to_keyword() == Keyword::None)
|
||||
return a_from;
|
||||
|
@ -119,18 +134,24 @@ static RefPtr<CSSStyleValue const> interpolate_translate(DOM::Element& element,
|
|||
auto const& from_transform = from.as_transformation();
|
||||
auto const& to_transform = to.as_transformation();
|
||||
|
||||
auto interpolated_x = interpolate_value(element, calculation_context, from_transform.values()[0], to_transform.values()[0], delta);
|
||||
auto interpolated_y = interpolate_value(element, calculation_context, from_transform.values()[1], to_transform.values()[1], delta);
|
||||
auto interpolated_x = interpolate_value(element, calculation_context, from_transform.values()[0], to_transform.values()[0], delta, allow_discrete);
|
||||
if (!interpolated_x)
|
||||
return {};
|
||||
auto interpolated_y = interpolate_value(element, calculation_context, from_transform.values()[1], to_transform.values()[1], delta, allow_discrete);
|
||||
if (!interpolated_y)
|
||||
return {};
|
||||
|
||||
RefPtr<CSSStyleValue const> interpolated_z;
|
||||
|
||||
if (from_transform.values().size() == 3 || to_transform.values().size() == 3) {
|
||||
auto from_z = from_transform.values().size() == 3 ? from_transform.values()[2] : zero_px;
|
||||
auto to_z = to_transform.values().size() == 3 ? to_transform.values()[2] : zero_px;
|
||||
interpolated_z = interpolate_value(element, calculation_context, from_z, to_z, delta);
|
||||
interpolated_z = interpolate_value(element, calculation_context, from_z, to_z, delta, allow_discrete);
|
||||
if (!interpolated_z)
|
||||
return {};
|
||||
}
|
||||
|
||||
StyleValueVector new_values = { interpolated_x, interpolated_y };
|
||||
StyleValueVector new_values = { *interpolated_x, *interpolated_y };
|
||||
if (interpolated_z && interpolated_z->is_length() && !interpolated_z->as_length().equals(zero_px)) {
|
||||
new_values.append(*interpolated_z);
|
||||
}
|
||||
|
@ -141,7 +162,7 @@ static RefPtr<CSSStyleValue const> interpolate_translate(DOM::Element& element,
|
|||
move(new_values));
|
||||
}
|
||||
|
||||
ValueComparingRefPtr<CSSStyleValue const> interpolate_property(DOM::Element& element, PropertyID property_id, CSSStyleValue const& a_from, CSSStyleValue const& a_to, float delta)
|
||||
ValueComparingRefPtr<CSSStyleValue const> interpolate_property(DOM::Element& element, PropertyID property_id, CSSStyleValue const& a_from, CSSStyleValue const& a_to, float delta, AllowDiscrete allow_discrete)
|
||||
{
|
||||
auto from = with_keyword_values_resolved(element, property_id, a_from);
|
||||
auto to = with_keyword_values_resolved(element, property_id, a_to);
|
||||
|
@ -153,14 +174,14 @@ ValueComparingRefPtr<CSSStyleValue const> interpolate_property(DOM::Element& ele
|
|||
auto animation_type = animation_type_from_longhand_property(property_id);
|
||||
switch (animation_type) {
|
||||
case AnimationType::ByComputedValue:
|
||||
return interpolate_value(element, calculation_context, from, to, delta);
|
||||
return interpolate_value(element, calculation_context, from, to, delta, allow_discrete);
|
||||
case AnimationType::None:
|
||||
return to;
|
||||
case AnimationType::RepeatableList:
|
||||
return interpolate_repeatable_list(element, calculation_context, from, to, delta);
|
||||
return interpolate_repeatable_list(element, calculation_context, from, to, delta, allow_discrete);
|
||||
case AnimationType::Custom: {
|
||||
if (property_id == PropertyID::Transform) {
|
||||
if (auto interpolated_transform = interpolate_transform(element, from, to, delta))
|
||||
if (auto interpolated_transform = interpolate_transform(element, from, to, delta, allow_discrete))
|
||||
return *interpolated_transform;
|
||||
|
||||
// https://drafts.csswg.org/css-transforms-1/#interpolation-of-transforms
|
||||
|
@ -169,14 +190,17 @@ ValueComparingRefPtr<CSSStyleValue const> interpolate_property(DOM::Element& ele
|
|||
// such a state, the transformed element is not rendered.
|
||||
return {};
|
||||
}
|
||||
if (property_id == PropertyID::BoxShadow)
|
||||
return interpolate_box_shadow(element, calculation_context, from, to, delta);
|
||||
if (property_id == PropertyID::BoxShadow) {
|
||||
if (auto interpolated_box_shadow = interpolate_box_shadow(element, calculation_context, from, to, delta, allow_discrete))
|
||||
return *interpolated_box_shadow;
|
||||
return interpolate_discrete(from, to, delta, allow_discrete);
|
||||
}
|
||||
|
||||
if (property_id == PropertyID::FontStyle) {
|
||||
auto static oblique_0deg_value = FontStyleStyleValue::create(FontStyle::Oblique, AngleStyleValue::create(Angle::make_degrees(0)));
|
||||
auto from_value = from->as_font_style().font_style() == FontStyle::Normal ? oblique_0deg_value : from;
|
||||
auto to_value = to->as_font_style().font_style() == FontStyle::Normal ? oblique_0deg_value : to;
|
||||
return interpolate_value(element, calculation_context, from_value, to_value, delta);
|
||||
return interpolate_value(element, calculation_context, from_value, to_value, delta, allow_discrete);
|
||||
}
|
||||
|
||||
// https://drafts.csswg.org/web-animations-1/#animating-visibility
|
||||
|
@ -196,7 +220,8 @@ ValueComparingRefPtr<CSSStyleValue const> interpolate_property(DOM::Element& ele
|
|||
return to;
|
||||
return CSSKeywordValue::create(Keyword::Visible);
|
||||
}
|
||||
return delta >= 0.5f ? to : from;
|
||||
|
||||
return interpolate_discrete(from, to, delta, allow_discrete);
|
||||
}
|
||||
|
||||
// https://drafts.csswg.org/css-contain/#content-visibility-animation
|
||||
|
@ -218,26 +243,32 @@ ValueComparingRefPtr<CSSStyleValue const> interpolate_property(DOM::Element& ele
|
|||
return to;
|
||||
return non_hidden_value;
|
||||
}
|
||||
return delta >= 0.5f ? to : from;
|
||||
return interpolate_discrete(from, to, delta, allow_discrete);
|
||||
}
|
||||
|
||||
if (property_id == PropertyID::Scale)
|
||||
return interpolate_scale(element, calculation_context, from, to, delta);
|
||||
if (property_id == PropertyID::Scale) {
|
||||
if (auto result = interpolate_scale(element, calculation_context, from, to, delta, allow_discrete))
|
||||
return result;
|
||||
return interpolate_discrete(from, to, delta, allow_discrete);
|
||||
}
|
||||
|
||||
if (property_id == PropertyID::Translate)
|
||||
return interpolate_translate(element, calculation_context, from, to, delta);
|
||||
if (property_id == PropertyID::Translate) {
|
||||
if (auto result = interpolate_translate(element, calculation_context, from, to, delta, allow_discrete))
|
||||
return result;
|
||||
return interpolate_discrete(from, to, delta, allow_discrete);
|
||||
}
|
||||
|
||||
// FIXME: Handle all custom animatable properties
|
||||
[[fallthrough]];
|
||||
}
|
||||
case AnimationType::Discrete:
|
||||
default:
|
||||
return delta >= 0.5f ? to : from;
|
||||
return interpolate_discrete(from, to, delta, allow_discrete);
|
||||
}
|
||||
}
|
||||
|
||||
// https://drafts.csswg.org/css-transitions/#transitionable
|
||||
bool property_values_are_transitionable(PropertyID property_id, CSSStyleValue const& old_value, CSSStyleValue const& new_value, TransitionBehavior transition_behavior)
|
||||
bool property_values_are_transitionable(PropertyID property_id, CSSStyleValue const& old_value, CSSStyleValue const& new_value, DOM::Element& element, TransitionBehavior transition_behavior)
|
||||
{
|
||||
// When comparing the before-change style and after-change style for a given property,
|
||||
// the property values are transitionable if they have an animation type that is neither not animatable nor discrete.
|
||||
|
@ -246,14 +277,15 @@ bool property_values_are_transitionable(PropertyID property_id, CSSStyleValue co
|
|||
if (animation_type == AnimationType::None || (transition_behavior != TransitionBehavior::AllowDiscrete && animation_type == AnimationType::Discrete))
|
||||
return false;
|
||||
|
||||
// FIXME: Even when a property is transitionable, the two values may not be. The spec uses the example of inset/non-inset shadows.
|
||||
(void)old_value;
|
||||
(void)new_value;
|
||||
// Even when a property is transitionable, the two values may not be. The spec uses the example of inset/non-inset shadows.
|
||||
if (transition_behavior != TransitionBehavior::AllowDiscrete && !interpolate_property(element, property_id, old_value, new_value, 0.5f, AllowDiscrete::No))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// A null return value means the interpolated matrix was not invertible or otherwise invalid
|
||||
RefPtr<CSSStyleValue const> interpolate_transform(DOM::Element& element, CSSStyleValue const& from, CSSStyleValue const& to, float delta)
|
||||
RefPtr<CSSStyleValue const> interpolate_transform(DOM::Element& element, CSSStyleValue const& from, CSSStyleValue const& to, float delta, AllowDiscrete)
|
||||
{
|
||||
// Note that the spec uses column-major notation, so all the matrix indexing is reversed.
|
||||
|
||||
|
@ -578,7 +610,7 @@ Color interpolate_color(Color from, Color to, float delta)
|
|||
return color;
|
||||
}
|
||||
|
||||
NonnullRefPtr<CSSStyleValue const> interpolate_box_shadow(DOM::Element& element, CalculationContext const& calculation_context, CSSStyleValue const& from, CSSStyleValue const& to, float delta)
|
||||
RefPtr<CSSStyleValue const> interpolate_box_shadow(DOM::Element& element, CalculationContext const& calculation_context, CSSStyleValue const& from, CSSStyleValue const& to, float delta, AllowDiscrete allow_discrete)
|
||||
{
|
||||
// https://drafts.csswg.org/css-backgrounds/#box-shadow
|
||||
// Animation type: by computed value, treating none as a zero-item list and appending blank shadows
|
||||
|
@ -626,12 +658,18 @@ NonnullRefPtr<CSSStyleValue const> interpolate_box_shadow(DOM::Element& element,
|
|||
for (size_t i = 0; i < from_shadows.size(); i++) {
|
||||
auto const& from_shadow = from_shadows[i]->as_shadow();
|
||||
auto const& to_shadow = to_shadows[i]->as_shadow();
|
||||
auto interpolated_offset_x = interpolate_value(element, calculation_context, from_shadow.offset_x(), to_shadow.offset_x(), delta, allow_discrete);
|
||||
auto interpolated_offset_y = interpolate_value(element, calculation_context, from_shadow.offset_y(), to_shadow.offset_y(), delta, allow_discrete);
|
||||
auto interpolated_blur_radius = interpolate_value(element, calculation_context, from_shadow.blur_radius(), to_shadow.blur_radius(), delta, allow_discrete);
|
||||
auto interpolated_spread_distance = interpolate_value(element, calculation_context, from_shadow.spread_distance(), to_shadow.spread_distance(), delta, allow_discrete);
|
||||
if (!interpolated_offset_x || !interpolated_offset_y || !interpolated_blur_radius || !interpolated_spread_distance)
|
||||
return {};
|
||||
auto result_shadow = ShadowStyleValue::create(
|
||||
CSSColorValue::create_from_color(interpolate_color(from_shadow.color()->to_color({}), to_shadow.color()->to_color({}), delta), ColorSyntax::Modern),
|
||||
interpolate_value(element, calculation_context, from_shadow.offset_x(), to_shadow.offset_x(), delta),
|
||||
interpolate_value(element, calculation_context, from_shadow.offset_y(), to_shadow.offset_y(), delta),
|
||||
interpolate_value(element, calculation_context, from_shadow.blur_radius(), to_shadow.blur_radius(), delta),
|
||||
interpolate_value(element, calculation_context, from_shadow.spread_distance(), to_shadow.spread_distance(), delta),
|
||||
*interpolated_offset_x,
|
||||
*interpolated_offset_y,
|
||||
*interpolated_blur_radius,
|
||||
*interpolated_spread_distance,
|
||||
delta >= 0.5f ? to_shadow.placement() : from_shadow.placement());
|
||||
result_shadows.unchecked_append(result_shadow);
|
||||
}
|
||||
|
@ -639,11 +677,6 @@ NonnullRefPtr<CSSStyleValue const> interpolate_box_shadow(DOM::Element& element,
|
|||
return StyleValueList::create(move(result_shadows), StyleValueList::Separator::Comma);
|
||||
}
|
||||
|
||||
enum class AllowDiscrete {
|
||||
Yes,
|
||||
No,
|
||||
};
|
||||
|
||||
static RefPtr<CSSStyleValue const> interpolate_value_impl(DOM::Element& element, CalculationContext const& calculation_context, CSSStyleValue const& from, CSSStyleValue const& to, float delta, AllowDiscrete allow_discrete)
|
||||
{
|
||||
if (from.type() != to.type()) {
|
||||
|
@ -707,18 +740,20 @@ static RefPtr<CSSStyleValue const> interpolate_value_impl(DOM::Element& element,
|
|||
// hard to understand how this interpolation works, but if instead we rewrite the values as "30px + 0%" and
|
||||
// "0px + 80%", then it is very simple to understand; we just interpolate each component separately.
|
||||
|
||||
auto interpolated_from = interpolate_value(element, calculation_context, from, from_base_type_and_default->default_value, delta);
|
||||
auto interpolated_to = interpolate_value(element, calculation_context, to_base_type_and_default->default_value, to, delta);
|
||||
auto interpolated_from = interpolate_value(element, calculation_context, from, from_base_type_and_default->default_value, delta, allow_discrete);
|
||||
auto interpolated_to = interpolate_value(element, calculation_context, to_base_type_and_default->default_value, to, delta, allow_discrete);
|
||||
if (!interpolated_from || !interpolated_to)
|
||||
return {};
|
||||
|
||||
Vector<NonnullRefPtr<CalculationNode const>> values;
|
||||
values.ensure_capacity(2);
|
||||
values.unchecked_append(to_calculation_node(interpolated_from));
|
||||
values.unchecked_append(to_calculation_node(interpolated_to));
|
||||
values.unchecked_append(to_calculation_node(*interpolated_from));
|
||||
values.unchecked_append(to_calculation_node(*interpolated_to));
|
||||
auto calc_node = SumCalculationNode::create(move(values));
|
||||
return CalculatedStyleValue::create(move(calc_node), CSSNumericType { to_base_type_and_default->base_type, 1 }, calculation_context);
|
||||
}
|
||||
|
||||
return delta >= 0.5f ? to : from;
|
||||
return {};
|
||||
}
|
||||
|
||||
static auto interpolate_length_percentage = [](LengthPercentage const& from, LengthPercentage const& to, float delta) -> Optional<LengthPercentage> {
|
||||
|
@ -732,6 +767,14 @@ static RefPtr<CSSStyleValue const> interpolate_value_impl(DOM::Element& element,
|
|||
switch (from.type()) {
|
||||
case CSSStyleValue::Type::Angle:
|
||||
return AngleStyleValue::create(Angle::make_degrees(interpolate_raw(from.as_angle().angle().to_degrees(), to.as_angle().angle().to_degrees(), delta)));
|
||||
case CSSStyleValue::Type::BackgroundSize: {
|
||||
auto interpolated_x = interpolate_length_percentage(from.as_background_size().size_x(), to.as_background_size().size_x(), delta);
|
||||
auto interpolated_y = interpolate_length_percentage(from.as_background_size().size_y(), to.as_background_size().size_y(), delta);
|
||||
if (!interpolated_x.has_value() || !interpolated_y.has_value())
|
||||
return {};
|
||||
|
||||
return BackgroundSizeStyleValue::create(*interpolated_x, *interpolated_y);
|
||||
}
|
||||
case CSSStyleValue::Type::Color: {
|
||||
Optional<Layout::NodeWithStyle const&> layout_node;
|
||||
if (auto node = element.layout_node())
|
||||
|
@ -747,14 +790,18 @@ static RefPtr<CSSStyleValue const> interpolate_value_impl(DOM::Element& element,
|
|||
if (auto interpolated_value = interpolate_length_percentage(from_offset, to_offset, delta); interpolated_value.has_value())
|
||||
return EdgeStyleValue::create(edge, *interpolated_value);
|
||||
|
||||
return delta >= 0.5f ? to : from;
|
||||
return {};
|
||||
}
|
||||
case CSSStyleValue::Type::FontStyle: {
|
||||
auto const& from_font_style = from.as_font_style();
|
||||
auto const& to_font_style = to.as_font_style();
|
||||
auto interpolated_font_style = interpolate_value(element, calculation_context, CSSKeywordValue::create(to_keyword(from_font_style.font_style())), CSSKeywordValue::create(to_keyword(to_font_style.font_style())), delta);
|
||||
auto interpolated_font_style = interpolate_value(element, calculation_context, CSSKeywordValue::create(to_keyword(from_font_style.font_style())), CSSKeywordValue::create(to_keyword(to_font_style.font_style())), delta, allow_discrete);
|
||||
if (!interpolated_font_style)
|
||||
return {};
|
||||
if (from_font_style.angle() && to_font_style.angle()) {
|
||||
auto interpolated_angle = interpolate_value(element, calculation_context, *from_font_style.angle(), *to_font_style.angle(), delta);
|
||||
auto interpolated_angle = interpolate_value(element, calculation_context, *from_font_style.angle(), *to_font_style.angle(), delta, allow_discrete);
|
||||
if (!interpolated_angle)
|
||||
return {};
|
||||
return FontStyleStyleValue::create(*keyword_to_font_style(interpolated_font_style->to_keyword()), interpolated_angle);
|
||||
}
|
||||
|
||||
|
@ -782,9 +829,11 @@ static RefPtr<CSSStyleValue const> interpolate_value_impl(DOM::Element& element,
|
|||
// FIXME: Interpolation of <position> is defined as the independent interpolation of each component (x, y) normalized as an offset from the top left corner as a <length-percentage>.
|
||||
auto const& from_position = from.as_position();
|
||||
auto const& to_position = to.as_position();
|
||||
return PositionStyleValue::create(
|
||||
interpolate_value(element, calculation_context, from_position.edge_x(), to_position.edge_x(), delta)->as_edge(),
|
||||
interpolate_value(element, calculation_context, from_position.edge_y(), to_position.edge_y(), delta)->as_edge());
|
||||
auto interpolated_edge_x = interpolate_value(element, calculation_context, from_position.edge_x(), to_position.edge_x(), delta, allow_discrete);
|
||||
auto interpolated_edge_y = interpolate_value(element, calculation_context, from_position.edge_y(), to_position.edge_y(), delta, allow_discrete);
|
||||
if (!interpolated_edge_x || !interpolated_edge_y)
|
||||
return {};
|
||||
return PositionStyleValue::create(interpolated_edge_x->as_edge(), interpolated_edge_y->as_edge());
|
||||
}
|
||||
case CSSStyleValue::Type::Ratio: {
|
||||
auto from_ratio = from.as_ratio().ratio();
|
||||
|
@ -793,7 +842,7 @@ static RefPtr<CSSStyleValue const> interpolate_value_impl(DOM::Element& element,
|
|||
// https://drafts.csswg.org/css-values/#combine-ratio
|
||||
// If either <ratio> is degenerate, the values cannot be interpolated.
|
||||
if (from_ratio.is_degenerate() || to_ratio.is_degenerate())
|
||||
return delta >= 0.5f ? to : from;
|
||||
return {};
|
||||
|
||||
// The interpolation of a <ratio> is defined by converting each <ratio> to a number by dividing the first value
|
||||
// by the second (so a ratio of 3 / 2 would become 1.5), taking the logarithm of that result (so the 1.5 would
|
||||
|
@ -809,9 +858,8 @@ static RefPtr<CSSStyleValue const> interpolate_value_impl(DOM::Element& element,
|
|||
auto from_rect = from.as_rect().rect();
|
||||
auto to_rect = to.as_rect().rect();
|
||||
|
||||
if (from_rect.top_edge.is_auto() != to_rect.top_edge.is_auto() || from_rect.right_edge.is_auto() != to_rect.right_edge.is_auto() || from_rect.bottom_edge.is_auto() != to_rect.bottom_edge.is_auto() || from_rect.left_edge.is_auto() != to_rect.left_edge.is_auto()) {
|
||||
return delta >= 0.5f ? to : from;
|
||||
}
|
||||
if (from_rect.top_edge.is_auto() != to_rect.top_edge.is_auto() || from_rect.right_edge.is_auto() != to_rect.right_edge.is_auto() || from_rect.bottom_edge.is_auto() != to_rect.bottom_edge.is_auto() || from_rect.left_edge.is_auto() != to_rect.left_edge.is_auto())
|
||||
return {};
|
||||
|
||||
// FIXME: Absolutize values
|
||||
return RectStyleValue::create({
|
||||
|
@ -827,31 +875,29 @@ static RefPtr<CSSStyleValue const> interpolate_value_impl(DOM::Element& element,
|
|||
auto const& from_list = from.as_value_list();
|
||||
auto const& to_list = to.as_value_list();
|
||||
if (from_list.size() != to_list.size())
|
||||
return delta >= 0.5f ? to : from;
|
||||
return {};
|
||||
|
||||
// FIXME: If the number of components or the types of corresponding components do not match,
|
||||
// or if any component value uses discrete animation and the two corresponding values do not match,
|
||||
// then the property values combine as discrete.
|
||||
StyleValueVector interpolated_values;
|
||||
interpolated_values.ensure_capacity(from_list.size());
|
||||
for (size_t i = 0; i < from_list.size(); ++i)
|
||||
interpolated_values.append(interpolate_value(element, calculation_context, from_list.values()[i], to_list.values()[i], delta));
|
||||
for (size_t i = 0; i < from_list.size(); ++i) {
|
||||
auto interpolated = interpolate_value(element, calculation_context, from_list.values()[i], to_list.values()[i], delta, AllowDiscrete::No);
|
||||
if (!interpolated)
|
||||
return {};
|
||||
|
||||
interpolated_values.append(*interpolated);
|
||||
}
|
||||
|
||||
return StyleValueList::create(move(interpolated_values), from_list.separator());
|
||||
}
|
||||
default:
|
||||
if (allow_discrete == AllowDiscrete::No)
|
||||
return {};
|
||||
return delta >= 0.5f ? to : from;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
NonnullRefPtr<CSSStyleValue const> interpolate_value(DOM::Element& element, CalculationContext const& calculation_context, CSSStyleValue const& from, CSSStyleValue const& to, float delta)
|
||||
{
|
||||
return *interpolate_value_impl(element, calculation_context, from, to, delta, AllowDiscrete::Yes);
|
||||
}
|
||||
|
||||
NonnullRefPtr<CSSStyleValue const> interpolate_repeatable_list(DOM::Element& element, CalculationContext const& calculation_context, CSSStyleValue const& from, CSSStyleValue const& to, float delta)
|
||||
RefPtr<CSSStyleValue const> interpolate_repeatable_list(DOM::Element& element, CalculationContext const& calculation_context, CSSStyleValue const& from, CSSStyleValue const& to, float delta, AllowDiscrete allow_discrete)
|
||||
{
|
||||
// https://www.w3.org/TR/web-animations/#repeatable-list
|
||||
// Same as by computed value except that if the two lists have differing numbers of items, they are first repeated to the least common multiple number of items.
|
||||
|
@ -864,7 +910,7 @@ NonnullRefPtr<CSSStyleValue const> interpolate_repeatable_list(DOM::Element& ele
|
|||
// then the property values combine as discrete
|
||||
auto list_size = AK::lcm(from_list.size(), to_list.size());
|
||||
for (size_t i = 0; i < list_size; ++i) {
|
||||
auto value = interpolate_value_impl(element, calculation_context, from_list.value_at(i, true), to_list.value_at(i, true), delta, AllowDiscrete::No);
|
||||
auto value = interpolate_value(element, calculation_context, from_list.value_at(i, true), to_list.value_at(i, true), delta, AllowDiscrete::No);
|
||||
if (!value)
|
||||
return false;
|
||||
append_callback(*value);
|
||||
|
@ -888,12 +934,19 @@ NonnullRefPtr<CSSStyleValue const> interpolate_repeatable_list(DOM::Element& ele
|
|||
else if (!to.is_value_list() && from.is_value_list())
|
||||
to_list = make_single_value_list(to, from.as_value_list().size(), to.as_value_list().separator());
|
||||
else if (!from.is_value_list() && !to.is_value_list())
|
||||
return interpolate_value(element, calculation_context, from, to, delta);
|
||||
return interpolate_value(element, calculation_context, from, to, delta, allow_discrete);
|
||||
|
||||
StyleValueVector interpolated_values;
|
||||
if (!make_repeatable_list(from_list->as_value_list(), to_list->as_value_list(), [&](auto const& value) { interpolated_values.append(value); }))
|
||||
return delta >= 0.5f ? to : from;
|
||||
return interpolate_discrete(from, to, delta, allow_discrete);
|
||||
return StyleValueList::create(move(interpolated_values), from_list->as_value_list().separator());
|
||||
}
|
||||
|
||||
RefPtr<CSSStyleValue const> interpolate_value(DOM::Element& element, CalculationContext const& calculation_context, CSSStyleValue const& from, CSSStyleValue const& to, float delta, AllowDiscrete allow_discrete)
|
||||
{
|
||||
if (auto result = interpolate_value_impl(element, calculation_context, from, to, delta, allow_discrete))
|
||||
return result;
|
||||
return interpolate_discrete(from, to, delta, allow_discrete);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -14,15 +14,19 @@ namespace Web::CSS {
|
|||
|
||||
struct CalculationContext;
|
||||
|
||||
ValueComparingRefPtr<CSSStyleValue const> interpolate_property(DOM::Element&, PropertyID, CSSStyleValue const& from, CSSStyleValue const& to, float delta);
|
||||
enum class AllowDiscrete {
|
||||
Yes,
|
||||
No,
|
||||
};
|
||||
ValueComparingRefPtr<CSSStyleValue const> interpolate_property(DOM::Element&, PropertyID, CSSStyleValue const& from, CSSStyleValue const& to, float delta, AllowDiscrete);
|
||||
|
||||
// https://drafts.csswg.org/css-transitions/#transitionable
|
||||
bool property_values_are_transitionable(PropertyID, CSSStyleValue const& old_value, CSSStyleValue const& new_value, TransitionBehavior transition_behavior);
|
||||
bool property_values_are_transitionable(PropertyID, CSSStyleValue const& old_value, CSSStyleValue const& new_value, DOM::Element&, TransitionBehavior);
|
||||
|
||||
NonnullRefPtr<CSSStyleValue const> interpolate_value(DOM::Element&, CalculationContext const&, CSSStyleValue const& from, CSSStyleValue const& to, float delta);
|
||||
NonnullRefPtr<CSSStyleValue const> interpolate_repeatable_list(DOM::Element&, CalculationContext const&, CSSStyleValue const& from, CSSStyleValue const& to, float delta);
|
||||
NonnullRefPtr<CSSStyleValue const> interpolate_box_shadow(DOM::Element&, CalculationContext const&, CSSStyleValue const& from, CSSStyleValue const& to, float delta);
|
||||
RefPtr<CSSStyleValue const> interpolate_transform(DOM::Element&, CSSStyleValue const& from, CSSStyleValue const& to, float delta);
|
||||
RefPtr<CSSStyleValue const> interpolate_value(DOM::Element&, CalculationContext const&, CSSStyleValue const& from, CSSStyleValue const& to, float delta, AllowDiscrete);
|
||||
RefPtr<CSSStyleValue const> interpolate_repeatable_list(DOM::Element&, CalculationContext const&, CSSStyleValue const& from, CSSStyleValue const& to, float delta, AllowDiscrete);
|
||||
RefPtr<CSSStyleValue const> interpolate_box_shadow(DOM::Element&, CalculationContext const&, CSSStyleValue const& from, CSSStyleValue const& to, float delta, AllowDiscrete);
|
||||
RefPtr<CSSStyleValue const> interpolate_transform(DOM::Element&, CSSStyleValue const& from, CSSStyleValue const& to, float delta, AllowDiscrete);
|
||||
|
||||
Color interpolate_color(Color from, Color to, float delta);
|
||||
|
||||
|
|
|
@ -1266,7 +1266,7 @@ void StyleComputer::collect_animation_into(DOM::Element& element, Optional<CSS::
|
|||
continue;
|
||||
}
|
||||
|
||||
if (auto next_value = interpolate_property(*effect->target(), it.key, *start, *end, progress_in_keyframe)) {
|
||||
if (auto next_value = interpolate_property(*effect->target(), it.key, *start, *end, progress_in_keyframe, AllowDiscrete::Yes)) {
|
||||
dbgln_if(LIBWEB_CSS_ANIMATION_DEBUG, "Interpolated value for property {} at {}: {} -> {} = {}", string_from_property_id(it.key), progress_in_keyframe, start->to_string(SerializationMode::Normal), end->to_string(SerializationMode::Normal), next_value->to_string(SerializationMode::Normal));
|
||||
computed_properties.set_animated_property(it.key, *next_value);
|
||||
} else {
|
||||
|
@ -1488,7 +1488,7 @@ void StyleComputer::start_needed_transitions(ComputedProperties const& previous_
|
|||
auto transition = CSSTransition::start_a_transition(element, property_id, document().transition_generation(),
|
||||
start_time, end_time, start_value, end_value, reversing_adjusted_start_value, reversing_shortening_factor);
|
||||
// Immediately set the property's value to the transition's current value, to prevent single-frame jumps.
|
||||
new_style.set_animated_property(property_id, transition->value_at_time(style_change_event_time));
|
||||
new_style.set_animated_property(property_id, transition->value_at_time(style_change_event_time, matching_transition_properties->transition_behavior == TransitionBehavior::AllowDiscrete ? AllowDiscrete::Yes : AllowDiscrete::No));
|
||||
};
|
||||
|
||||
// 1. If all of the following are true:
|
||||
|
@ -1498,7 +1498,7 @@ void StyleComputer::start_needed_transitions(ComputedProperties const& previous_
|
|||
// - there is a matching transition-property value, and
|
||||
(matching_transition_properties.has_value()) &&
|
||||
// - the before-change style is different from the after-change style for that property, and the values for the property are transitionable,
|
||||
(!before_change_value.equals(after_change_value) && property_values_are_transitionable(property_id, before_change_value, after_change_value, matching_transition_properties->transition_behavior)) &&
|
||||
(!before_change_value.equals(after_change_value) && property_values_are_transitionable(property_id, before_change_value, after_change_value, element, matching_transition_properties->transition_behavior)) &&
|
||||
// - the element does not have a completed transition for the property
|
||||
// or the end value of the completed transition is different from the after-change style for the property,
|
||||
(!has_completed_transition || !existing_transition->transition_end_value()->equals(after_change_value)) &&
|
||||
|
@ -1560,8 +1560,8 @@ void StyleComputer::start_needed_transitions(ComputedProperties const& previous_
|
|||
// 1. If the current value of the property in the running transition is equal to the value of the property in the after-change style,
|
||||
// or if these two values are not transitionable,
|
||||
// then implementations must cancel the running transition.
|
||||
auto current_value = existing_transition->value_at_time(style_change_event_time);
|
||||
if (current_value->equals(after_change_value) || !property_values_are_transitionable(property_id, current_value, after_change_value, matching_transition_properties->transition_behavior)) {
|
||||
auto current_value = existing_transition->value_at_time(style_change_event_time, matching_transition_properties->transition_behavior == TransitionBehavior::AllowDiscrete ? AllowDiscrete::Yes : AllowDiscrete::No);
|
||||
if (current_value->equals(after_change_value) || !property_values_are_transitionable(property_id, current_value, after_change_value, element, matching_transition_properties->transition_behavior)) {
|
||||
dbgln_if(CSS_TRANSITIONS_DEBUG, "Transition step 4.1");
|
||||
existing_transition->cancel();
|
||||
}
|
||||
|
@ -1570,7 +1570,7 @@ void StyleComputer::start_needed_transitions(ComputedProperties const& previous_
|
|||
// or if the current value of the property in the running transition is not transitionable with the value of the property in the after-change style,
|
||||
// then implementations must cancel the running transition.
|
||||
else if ((combined_duration(matching_transition_properties.value()) <= 0)
|
||||
|| !property_values_are_transitionable(property_id, current_value, after_change_value, matching_transition_properties->transition_behavior)) {
|
||||
|| !property_values_are_transitionable(property_id, current_value, after_change_value, element, matching_transition_properties->transition_behavior)) {
|
||||
dbgln_if(CSS_TRANSITIONS_DEBUG, "Transition step 4.2");
|
||||
existing_transition->cancel();
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue