diff --git a/Userland/Libraries/LibWeb/CSS/ComputedValues.h b/Userland/Libraries/LibWeb/CSS/ComputedValues.h index 7a13bebaf0..da5b23b0ca 100644 --- a/Userland/Libraries/LibWeb/CSS/ComputedValues.h +++ b/Userland/Libraries/LibWeb/CSS/ComputedValues.h @@ -41,6 +41,11 @@ public: float width { 0 }; }; +struct Transformation { + CSS::TransformFunction function; + Vector> values; +}; + struct FlexBasisData { CSS::FlexBasis type { CSS::FlexBasis::Auto }; CSS::Length length {}; @@ -109,6 +114,8 @@ public: Optional stroke() const { return m_inherited.stroke; } Optional const& stroke_width() const { return m_inherited.stroke_width; } + Vector transformations() const { return m_noninherited.transformations; } + ComputedValues clone_inherited_values() const { ComputedValues clone; @@ -168,6 +175,7 @@ protected: CSS::Overflow overflow_y { InitialValues::overflow() }; Optional opacity; Optional box_shadow {}; + Vector transformations {}; } m_noninherited; }; @@ -219,6 +227,7 @@ public: void set_opacity(Optional value) { m_noninherited.opacity = value; } void set_justify_content(CSS::JustifyContent value) { m_noninherited.justify_content = value; } void set_box_shadow(Optional value) { m_noninherited.box_shadow = move(value); } + void set_transformations(Vector value) { m_noninherited.transformations = move(value); } void set_fill(Color value) { m_inherited.fill = value; } void set_stroke(Color value) { m_inherited.stroke = value; } diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index 9b92610f47..cce3323e74 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -2788,6 +2788,45 @@ RefPtr Parser::parse_text_decoration_value(ParsingContext const& con return TextDecorationStyleValue::create(decoration_line.release_nonnull(), decoration_style.release_nonnull(), decoration_color.release_nonnull()); } +static Optional parse_transform_function_name(StringView name) +{ + if (name == "translateY") + return CSS::TransformFunction::TranslateY; + return {}; +} + +RefPtr Parser::parse_transform_value(ParsingContext const& context, Vector const& component_values) +{ + NonnullRefPtrVector transformations; + + for (auto& part : component_values) { + if (!part.is_function()) + return nullptr; + auto maybe_function = parse_transform_function_name(part.function().name()); + if (!maybe_function.has_value()) + return nullptr; + + NonnullRefPtrVector values; + for (auto& value : part.function().values()) { + if (value.is(Token::Type::Dimension)) { + auto maybe_length = parse_length(context, value); + if (!maybe_length.has_value()) + return nullptr; + values.append(LengthStyleValue::create(maybe_length.release_value())); + } else if (value.is(Token::Type::Number)) { + auto number = parse_numeric_value(context, value); + values.append(number.release_nonnull()); + } else { + dbgln("FIXME: Unsupported value type for transformation!"); + return nullptr; + } + } + + transformations.append(TransformationStyleValue::create(maybe_function.value(), move(values))); + } + return StyleValueList::create(move(transformations)); +} + RefPtr Parser::parse_as_css_value(PropertyID property_id) { auto component_values = parse_as_list_of_component_values(); @@ -2893,6 +2932,10 @@ Result, Parser::ParsingResult> Parser::parse_css_value if (auto parsed_value = parse_text_decoration_value(m_context, component_values)) return parsed_value.release_nonnull(); break; + case PropertyID::Transform: + if (auto parsed_value = parse_transform_value(m_context, component_values)) + return parsed_value.release_nonnull(); + break; default: break; } diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h index ff006da18a..9c357947ec 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h @@ -198,6 +198,7 @@ private: static RefPtr parse_list_style_value(ParsingContext const&, Vector const&); static RefPtr parse_overflow_value(ParsingContext const&, Vector const&); static RefPtr parse_text_decoration_value(ParsingContext const&, Vector const&); + static RefPtr parse_transform_value(ParsingContext const&, Vector const&); // calc() parsing, according to https://www.w3.org/TR/css-values-3/#calc-syntax static OwnPtr parse_calc_sum(ParsingContext const&, TokenStream&); diff --git a/Userland/Libraries/LibWeb/CSS/Properties.json b/Userland/Libraries/LibWeb/CSS/Properties.json index 2b82c4806c..843edc9f3e 100644 --- a/Userland/Libraries/LibWeb/CSS/Properties.json +++ b/Userland/Libraries/LibWeb/CSS/Properties.json @@ -605,6 +605,10 @@ "unitless-length" ] }, + "transform": { + "inherited": false, + "initial": "none" + }, "user-select": { "inherited": false, "initial": "auto" diff --git a/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp b/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp index e51111fd85..6fc9bc461b 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp @@ -424,6 +424,44 @@ Optional StyleProperties::justify_content() const } } +Vector StyleProperties::transformations() const +{ + auto value = property(CSS::PropertyID::Transform); + if (!value.has_value()) + return {}; + + if (value.value()->is_identifier() && value.value()->to_identifier() == CSS::ValueID::None) + return {}; + + if (!value.value()->is_value_list()) + return {}; + + auto& list = static_cast(*value.value()); + + Vector transformations; + + for (auto& it : list.values()) { + if (!it.is_transformation()) + return {}; + auto& transformation_style_value = static_cast(it); + CSS::Transformation transformation; + transformation.function = transformation_style_value.transform_function(); + Vector> values; + for (auto& transformation_value : transformation_style_value.values()) { + if (transformation_value.is_length()) { + values.append({ transformation_value.to_length() }); + } else if (transformation_value.is_numeric()) { + values.append({ static_cast(transformation_value).value() }); + } else { + dbgln("FIXME: Unsupported value in transform!"); + } + } + transformation.values = move(values); + transformations.append(move(transformation)); + } + return transformations; +} + Optional StyleProperties::align_items() const { auto value = property(CSS::PropertyID::AlignItems); diff --git a/Userland/Libraries/LibWeb/CSS/StyleProperties.h b/Userland/Libraries/LibWeb/CSS/StyleProperties.h index 2f0a0fa48f..36284f8549 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleProperties.h +++ b/Userland/Libraries/LibWeb/CSS/StyleProperties.h @@ -64,6 +64,8 @@ public: Optional background_repeat_y() const; Optional box_shadow() const; + Vector transformations() const; + const Gfx::Font& font(Layout::Node const& node) const { if (!m_font) diff --git a/Userland/Libraries/LibWeb/CSS/StyleValue.h b/Userland/Libraries/LibWeb/CSS/StyleValue.h index ac02a17d94..400586ac54 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleValue.h +++ b/Userland/Libraries/LibWeb/CSS/StyleValue.h @@ -219,6 +219,10 @@ enum class AlignItems { Stretch, }; +enum class TransformFunction { + TranslateY, +}; + class StyleValue : public RefCounted { public: virtual ~StyleValue(); @@ -249,6 +253,7 @@ public: ListStyle, Overflow, TextDecoration, + Transformation, }; Type type() const { return m_type; } @@ -276,6 +281,7 @@ public: bool is_list_style() const { return type() == Type::ListStyle; } bool is_overflow() const { return type() == Type::Overflow; } bool is_text_decoration() const { return type() == Type::TextDecoration; } + bool is_transformation() const { return type() == Type::Transformation; } bool is_builtin() const { return is_inherit() || is_initial() || is_unset(); } @@ -1060,6 +1066,34 @@ private: NonnullRefPtr m_color; }; +class TransformationStyleValue final : public StyleValue { +public: + static NonnullRefPtr create(CSS::TransformFunction transform_function, NonnullRefPtrVector&& values) + { + return adopt_ref(*new TransformationStyleValue(transform_function, move(values))); + } + virtual ~TransformationStyleValue() override { } + + CSS::TransformFunction transform_function() const { return m_transform_function; } + NonnullRefPtrVector values() const { return m_values; } + + virtual String to_string() const override + { + return String::formatted("TransformationStyleValue"); + } + +private: + TransformationStyleValue(CSS::TransformFunction transform_function, NonnullRefPtrVector&& values) + : StyleValue(Type::Transformation) + , m_transform_function(transform_function) + , m_values(move(values)) + { + } + + CSS::TransformFunction m_transform_function; + NonnullRefPtrVector m_values; +}; + class StyleValueList final : public StyleValue { public: static NonnullRefPtr create(NonnullRefPtrVector&& values) { return adopt_ref(*new StyleValueList(move(values))); } diff --git a/Userland/Libraries/LibWeb/Layout/Node.cpp b/Userland/Libraries/LibWeb/Layout/Node.cpp index 9863a37d10..d054ac96c3 100644 --- a/Userland/Libraries/LibWeb/Layout/Node.cpp +++ b/Userland/Libraries/LibWeb/Layout/Node.cpp @@ -347,6 +347,8 @@ void NodeWithStyle::apply_style(const CSS::StyleProperties& specified_style) computed_values.set_box_shadow(specified_style.box_shadow()); + computed_values.set_transformations(specified_style.transformations()); + auto do_border_style = [&](CSS::BorderData& border, CSS::PropertyID width_property, CSS::PropertyID color_property, CSS::PropertyID style_property) { // FIXME: The default border color value is `currentcolor`, but since we can't resolve that easily, // we just manually grab the value from `color`. This makes it dependent on `color` being