diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 6d8fa041dc..2cc8600601 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -86,6 +86,7 @@ set(SOURCES CSS/StyleValues/IdentifierStyleValue.cpp CSS/StyleValues/ImageStyleValue.cpp CSS/StyleValues/LinearGradientStyleValue.cpp + CSS/StyleValues/RadialGradientStyleValue.cpp CSS/Supports.cpp CSS/SyntaxHighlighter/SyntaxHighlighter.cpp CSS/Time.cpp diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index ffed6fb725..fe52b1fc8a 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -52,6 +52,7 @@ #include #include #include +#include #include #include #include diff --git a/Userland/Libraries/LibWeb/CSS/StyleValue.cpp b/Userland/Libraries/LibWeb/CSS/StyleValue.cpp index 806cb84b8e..40712b95e3 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleValue.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleValue.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -1150,209 +1151,6 @@ ErrorOr PositionValue::serialize(StringBuilder& builder) const return {}; } -ErrorOr RadialGradientStyleValue::to_string() const -{ - StringBuilder builder; - if (is_repeating()) - TRY(builder.try_append("repeating-"sv)); - TRY(builder.try_appendff("radial-gradient({} "sv, - m_properties.ending_shape == EndingShape::Circle ? "circle"sv : "ellipse"sv)); - - TRY(m_properties.size.visit( - [&](Extent extent) -> ErrorOr { - return builder.try_append([&] { - switch (extent) { - case Extent::ClosestCorner: - return "closest-corner"sv; - case Extent::ClosestSide: - return "closest-side"sv; - case Extent::FarthestCorner: - return "farthest-corner"sv; - case Extent::FarthestSide: - return "farthest-side"sv; - default: - VERIFY_NOT_REACHED(); - } - }()); - }, - [&](CircleSize const& circle_size) -> ErrorOr { - return builder.try_append(TRY(circle_size.radius.to_string())); - }, - [&](EllipseSize const& ellipse_size) -> ErrorOr { - return builder.try_appendff("{} {}", TRY(ellipse_size.radius_a.to_string()), TRY(ellipse_size.radius_b.to_string())); - })); - - if (m_properties.position != PositionValue::center()) { - TRY(builder.try_appendff(" at "sv)); - TRY(m_properties.position.serialize(builder)); - } - - TRY(builder.try_append(", "sv)); - TRY(serialize_color_stop_list(builder, m_properties.color_stop_list)); - TRY(builder.try_append(')')); - return builder.to_string(); -} - -Gfx::FloatSize RadialGradientStyleValue::resolve_size(Layout::Node const& node, Gfx::FloatPoint center, Gfx::FloatRect const& size) const -{ - auto const side_shape = [&](auto distance_function) { - auto const distance_from = [&](float v, float a, float b, auto distance_function) { - return distance_function(fabs(a - v), fabs(b - v)); - }; - auto x_dist = distance_from(center.x(), size.left(), size.right(), distance_function); - auto y_dist = distance_from(center.y(), size.top(), size.bottom(), distance_function); - if (m_properties.ending_shape == EndingShape::Circle) { - auto dist = distance_function(x_dist, y_dist); - return Gfx::FloatSize { dist, dist }; - } else { - return Gfx::FloatSize { x_dist, y_dist }; - } - }; - - auto const closest_side_shape = [&] { - return side_shape(AK::min); - }; - - auto const farthest_side_shape = [&] { - return side_shape(AK::max); - }; - - auto const corner_distance = [&](auto distance_compare, Gfx::FloatPoint& corner) { - auto top_left_distance = size.top_left().distance_from(center); - auto top_right_distance = size.top_right().distance_from(center); - auto bottom_right_distance = size.bottom_right().distance_from(center); - auto bottom_left_distance = size.bottom_left().distance_from(center); - auto distance = top_left_distance; - if (distance_compare(top_right_distance, distance)) { - corner = size.top_right(); - distance = top_right_distance; - } - if (distance_compare(bottom_right_distance, distance)) { - corner = size.top_right(); - distance = bottom_right_distance; - } - if (distance_compare(bottom_left_distance, distance)) { - corner = size.top_right(); - distance = bottom_left_distance; - } - return distance; - }; - - auto const closest_corner_distance = [&](Gfx::FloatPoint& corner) { - return corner_distance([](float a, float b) { return a < b; }, corner); - }; - - auto const farthest_corner_distance = [&](Gfx::FloatPoint& corner) { - return corner_distance([](float a, float b) { return a > b; }, corner); - }; - - auto const corner_shape = [&](auto corner_distance, auto get_shape) { - Gfx::FloatPoint corner {}; - auto distance = corner_distance(corner); - if (m_properties.ending_shape == EndingShape::Ellipse) { - auto shape = get_shape(); - auto aspect_ratio = shape.width() / shape.height(); - auto p = corner - center; - auto radius_a = AK::sqrt(p.y() * p.y() * aspect_ratio * aspect_ratio + p.x() * p.x()); - auto radius_b = radius_a / aspect_ratio; - return Gfx::FloatSize { radius_a, radius_b }; - } - return Gfx::FloatSize { distance, distance }; - }; - - // https://w3c.github.io/csswg-drafts/css-images/#radial-gradient-syntax - auto resolved_size = m_properties.size.visit( - [&](Extent extent) { - switch (extent) { - case Extent::ClosestSide: - // The ending shape is sized so that it exactly meets the side of the gradient box closest to the gradient’s center. - // If the shape is an ellipse, it exactly meets the closest side in each dimension. - return closest_side_shape(); - case Extent::ClosestCorner: - // The ending shape is sized so that it passes through the corner of the gradient box closest to the gradient’s center. - // If the shape is an ellipse, the ending shape is given the same aspect-ratio it would have if closest-side were specified - return corner_shape(closest_corner_distance, closest_side_shape); - case Extent::FarthestCorner: - // Same as closest-corner, except the ending shape is sized based on the farthest corner. - // If the shape is an ellipse, the ending shape is given the same aspect ratio it would have if farthest-side were specified. - return corner_shape(farthest_corner_distance, farthest_side_shape); - case Extent::FarthestSide: - // Same as closest-side, except the ending shape is sized based on the farthest side(s). - return farthest_side_shape(); - default: - VERIFY_NOT_REACHED(); - } - }, - [&](CircleSize const& circle_size) { - auto radius = circle_size.radius.to_px(node); - return Gfx::FloatSize { radius, radius }; - }, - [&](EllipseSize const& ellipse_size) { - auto radius_a = ellipse_size.radius_a.resolved(node, CSS::Length::make_px(size.width())).to_px(node); - auto radius_b = ellipse_size.radius_b.resolved(node, CSS::Length::make_px(size.height())).to_px(node); - return Gfx::FloatSize { radius_a, radius_b }; - }); - - // Handle degenerate cases - // https://w3c.github.io/csswg-drafts/css-images/#degenerate-radials - - constexpr auto arbitrary_small_number = 1e-10; - constexpr auto arbitrary_large_number = 1e10; - - // If the ending shape is a circle with zero radius: - if (m_properties.ending_shape == EndingShape::Circle && resolved_size.is_empty()) { - // Render as if the ending shape was a circle whose radius was an arbitrary very small number greater than zero. - // This will make the gradient continue to look like a circle. - return Gfx::FloatSize { arbitrary_small_number, arbitrary_small_number }; - } - // If the ending shape has zero width (regardless of the height): - if (resolved_size.width() <= 0) { - // Render as if the ending shape was an ellipse whose height was an arbitrary very large number - // and whose width was an arbitrary very small number greater than zero. - // This will make the gradient look similar to a horizontal linear gradient that is mirrored across the center of the ellipse. - // It also means that all color-stop positions specified with a percentage resolve to 0px. - return Gfx::FloatSize { arbitrary_small_number, arbitrary_large_number }; - } - // Otherwise, if the ending shape has zero height: - if (resolved_size.height() <= 0) { - // Render as if the ending shape was an ellipse whose width was an arbitrary very large number and whose height - // was an arbitrary very small number greater than zero. This will make the gradient look like a solid-color image equal - // to the color of the last color-stop, or equal to the average color of the gradient if it’s repeating. - return Gfx::FloatSize { arbitrary_large_number, arbitrary_small_number }; - } - return resolved_size; -} - -void RadialGradientStyleValue::resolve_for_size(Layout::Node const& node, CSSPixelSize paint_size) const -{ - CSSPixelRect gradient_box { { 0, 0 }, paint_size }; - auto center = m_properties.position.resolved(node, gradient_box).to_type(); - auto gradient_size = resolve_size(node, center, gradient_box.to_type()); - if (m_resolved.has_value() && m_resolved->gradient_size == gradient_size) - return; - m_resolved = ResolvedData { - Painting::resolve_radial_gradient_data(node, gradient_size.to_type(), *this), - gradient_size, - center, - }; -} - -bool RadialGradientStyleValue::equals(StyleValue const& other) const -{ - if (type() != other.type()) - return false; - auto& other_gradient = other.as_radial_gradient(); - return m_properties == other_gradient.m_properties; -} - -void RadialGradientStyleValue::paint(PaintContext& context, DevicePixelRect const& dest_rect, CSS::ImageRendering) const -{ - VERIFY(m_resolved.has_value()); - Painting::paint_radial_gradient(context, dest_rect, m_resolved->data, - context.rounded_device_point(m_resolved->center.to_type()), - context.rounded_device_size(m_resolved->gradient_size.to_type())); -} - ErrorOr ListStyleStyleValue::to_string() const { return String::formatted("{} {} {}", TRY(m_properties.position->to_string()), TRY(m_properties.image->to_string()), TRY(m_properties.style_type->to_string())); diff --git a/Userland/Libraries/LibWeb/CSS/StyleValue.h b/Userland/Libraries/LibWeb/CSS/StyleValue.h index 1e298416b2..f58e7ad10d 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleValue.h +++ b/Userland/Libraries/LibWeb/CSS/StyleValue.h @@ -668,85 +668,6 @@ enum class GradientRepeating { No }; -class RadialGradientStyleValue final : public AbstractImageStyleValue { -public: - enum class EndingShape { - Circle, - Ellipse - }; - - enum class Extent { - ClosestCorner, - ClosestSide, - FarthestCorner, - FarthestSide - }; - - struct CircleSize { - Length radius; - bool operator==(CircleSize const&) const = default; - }; - - struct EllipseSize { - LengthPercentage radius_a; - LengthPercentage radius_b; - bool operator==(EllipseSize const&) const = default; - }; - - using Size = Variant; - - static ValueComparingNonnullRefPtr create(EndingShape ending_shape, Size size, PositionValue position, Vector color_stop_list, GradientRepeating repeating) - { - VERIFY(color_stop_list.size() >= 2); - return adopt_ref(*new RadialGradientStyleValue(ending_shape, size, position, move(color_stop_list), repeating)); - } - - virtual ErrorOr to_string() const override; - - void paint(PaintContext&, DevicePixelRect const& dest_rect, CSS::ImageRendering) const override; - - virtual bool equals(StyleValue const& other) const override; - - Vector const& color_stop_list() const - { - return m_properties.color_stop_list; - } - - bool is_paintable() const override { return true; } - - void resolve_for_size(Layout::Node const&, CSSPixelSize) const override; - - Gfx::FloatSize resolve_size(Layout::Node const&, Gfx::FloatPoint, Gfx::FloatRect const&) const; - - bool is_repeating() const { return m_properties.repeating == GradientRepeating::Yes; } - - virtual ~RadialGradientStyleValue() override = default; - -private: - RadialGradientStyleValue(EndingShape ending_shape, Size size, PositionValue position, Vector color_stop_list, GradientRepeating repeating) - : AbstractImageStyleValue(Type::RadialGradient) - , m_properties { .ending_shape = ending_shape, .size = size, .position = position, .color_stop_list = move(color_stop_list), .repeating = repeating } - { - } - - struct Properties { - EndingShape ending_shape; - Size size; - PositionValue position; - Vector color_stop_list; - GradientRepeating repeating; - bool operator==(Properties const&) const = default; - } m_properties; - - struct ResolvedData { - Painting::RadialGradientData data; - Gfx::FloatSize gradient_size; - Gfx::FloatPoint center; - }; - - mutable Optional m_resolved; -}; - class InheritStyleValue final : public StyleValueWithDefaultOperators { public: static ValueComparingNonnullRefPtr the() diff --git a/Userland/Libraries/LibWeb/CSS/StyleValues/RadialGradientStyleValue.cpp b/Userland/Libraries/LibWeb/CSS/StyleValues/RadialGradientStyleValue.cpp new file mode 100644 index 0000000000..27bc0cbd31 --- /dev/null +++ b/Userland/Libraries/LibWeb/CSS/StyleValues/RadialGradientStyleValue.cpp @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2021, Tobias Christiansen + * Copyright (c) 2021-2023, Sam Atkins + * Copyright (c) 2022-2023, MacDue + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "RadialGradientStyleValue.h" +#include + +namespace Web::CSS { + +// FIXME: Temporary until AbstractImageStyleValue.h exists. +static ErrorOr serialize_color_stop_list(StringBuilder& builder, auto const& color_stop_list) +{ + bool first = true; + for (auto const& element : color_stop_list) { + if (!first) + TRY(builder.try_append(", "sv)); + + if (element.transition_hint.has_value()) + TRY(builder.try_appendff("{}, "sv, TRY(element.transition_hint->value.to_string()))); + + TRY(serialize_a_srgb_value(builder, element.color_stop.color)); + for (auto position : Array { &element.color_stop.position, &element.color_stop.second_position }) { + if (position->has_value()) + TRY(builder.try_appendff(" {}"sv, TRY((*position)->to_string()))); + } + first = false; + } + return {}; +} + +ErrorOr RadialGradientStyleValue::to_string() const +{ + StringBuilder builder; + if (is_repeating()) + TRY(builder.try_append("repeating-"sv)); + TRY(builder.try_appendff("radial-gradient({} "sv, + m_properties.ending_shape == EndingShape::Circle ? "circle"sv : "ellipse"sv)); + + TRY(m_properties.size.visit( + [&](Extent extent) -> ErrorOr { + return builder.try_append([&] { + switch (extent) { + case Extent::ClosestCorner: + return "closest-corner"sv; + case Extent::ClosestSide: + return "closest-side"sv; + case Extent::FarthestCorner: + return "farthest-corner"sv; + case Extent::FarthestSide: + return "farthest-side"sv; + default: + VERIFY_NOT_REACHED(); + } + }()); + }, + [&](CircleSize const& circle_size) -> ErrorOr { + return builder.try_append(TRY(circle_size.radius.to_string())); + }, + [&](EllipseSize const& ellipse_size) -> ErrorOr { + return builder.try_appendff("{} {}", TRY(ellipse_size.radius_a.to_string()), TRY(ellipse_size.radius_b.to_string())); + })); + + if (m_properties.position != PositionValue::center()) { + TRY(builder.try_appendff(" at "sv)); + TRY(m_properties.position.serialize(builder)); + } + + TRY(builder.try_append(", "sv)); + TRY(serialize_color_stop_list(builder, m_properties.color_stop_list)); + TRY(builder.try_append(')')); + return builder.to_string(); +} + +Gfx::FloatSize RadialGradientStyleValue::resolve_size(Layout::Node const& node, Gfx::FloatPoint center, Gfx::FloatRect const& size) const +{ + auto const side_shape = [&](auto distance_function) { + auto const distance_from = [&](float v, float a, float b, auto distance_function) { + return distance_function(fabs(a - v), fabs(b - v)); + }; + auto x_dist = distance_from(center.x(), size.left(), size.right(), distance_function); + auto y_dist = distance_from(center.y(), size.top(), size.bottom(), distance_function); + if (m_properties.ending_shape == EndingShape::Circle) { + auto dist = distance_function(x_dist, y_dist); + return Gfx::FloatSize { dist, dist }; + } else { + return Gfx::FloatSize { x_dist, y_dist }; + } + }; + + auto const closest_side_shape = [&] { + return side_shape(AK::min); + }; + + auto const farthest_side_shape = [&] { + return side_shape(AK::max); + }; + + auto const corner_distance = [&](auto distance_compare, Gfx::FloatPoint& corner) { + auto top_left_distance = size.top_left().distance_from(center); + auto top_right_distance = size.top_right().distance_from(center); + auto bottom_right_distance = size.bottom_right().distance_from(center); + auto bottom_left_distance = size.bottom_left().distance_from(center); + auto distance = top_left_distance; + if (distance_compare(top_right_distance, distance)) { + corner = size.top_right(); + distance = top_right_distance; + } + if (distance_compare(bottom_right_distance, distance)) { + corner = size.top_right(); + distance = bottom_right_distance; + } + if (distance_compare(bottom_left_distance, distance)) { + corner = size.top_right(); + distance = bottom_left_distance; + } + return distance; + }; + + auto const closest_corner_distance = [&](Gfx::FloatPoint& corner) { + return corner_distance([](float a, float b) { return a < b; }, corner); + }; + + auto const farthest_corner_distance = [&](Gfx::FloatPoint& corner) { + return corner_distance([](float a, float b) { return a > b; }, corner); + }; + + auto const corner_shape = [&](auto corner_distance, auto get_shape) { + Gfx::FloatPoint corner {}; + auto distance = corner_distance(corner); + if (m_properties.ending_shape == EndingShape::Ellipse) { + auto shape = get_shape(); + auto aspect_ratio = shape.width() / shape.height(); + auto p = corner - center; + auto radius_a = AK::sqrt(p.y() * p.y() * aspect_ratio * aspect_ratio + p.x() * p.x()); + auto radius_b = radius_a / aspect_ratio; + return Gfx::FloatSize { radius_a, radius_b }; + } + return Gfx::FloatSize { distance, distance }; + }; + + // https://w3c.github.io/csswg-drafts/css-images/#radial-gradient-syntax + auto resolved_size = m_properties.size.visit( + [&](Extent extent) { + switch (extent) { + case Extent::ClosestSide: + // The ending shape is sized so that it exactly meets the side of the gradient box closest to the gradient’s center. + // If the shape is an ellipse, it exactly meets the closest side in each dimension. + return closest_side_shape(); + case Extent::ClosestCorner: + // The ending shape is sized so that it passes through the corner of the gradient box closest to the gradient’s center. + // If the shape is an ellipse, the ending shape is given the same aspect-ratio it would have if closest-side were specified + return corner_shape(closest_corner_distance, closest_side_shape); + case Extent::FarthestCorner: + // Same as closest-corner, except the ending shape is sized based on the farthest corner. + // If the shape is an ellipse, the ending shape is given the same aspect ratio it would have if farthest-side were specified. + return corner_shape(farthest_corner_distance, farthest_side_shape); + case Extent::FarthestSide: + // Same as closest-side, except the ending shape is sized based on the farthest side(s). + return farthest_side_shape(); + default: + VERIFY_NOT_REACHED(); + } + }, + [&](CircleSize const& circle_size) { + auto radius = circle_size.radius.to_px(node); + return Gfx::FloatSize { radius, radius }; + }, + [&](EllipseSize const& ellipse_size) { + auto radius_a = ellipse_size.radius_a.resolved(node, CSS::Length::make_px(size.width())).to_px(node); + auto radius_b = ellipse_size.radius_b.resolved(node, CSS::Length::make_px(size.height())).to_px(node); + return Gfx::FloatSize { radius_a, radius_b }; + }); + + // Handle degenerate cases + // https://w3c.github.io/csswg-drafts/css-images/#degenerate-radials + + constexpr auto arbitrary_small_number = 1e-10; + constexpr auto arbitrary_large_number = 1e10; + + // If the ending shape is a circle with zero radius: + if (m_properties.ending_shape == EndingShape::Circle && resolved_size.is_empty()) { + // Render as if the ending shape was a circle whose radius was an arbitrary very small number greater than zero. + // This will make the gradient continue to look like a circle. + return Gfx::FloatSize { arbitrary_small_number, arbitrary_small_number }; + } + // If the ending shape has zero width (regardless of the height): + if (resolved_size.width() <= 0) { + // Render as if the ending shape was an ellipse whose height was an arbitrary very large number + // and whose width was an arbitrary very small number greater than zero. + // This will make the gradient look similar to a horizontal linear gradient that is mirrored across the center of the ellipse. + // It also means that all color-stop positions specified with a percentage resolve to 0px. + return Gfx::FloatSize { arbitrary_small_number, arbitrary_large_number }; + } + // Otherwise, if the ending shape has zero height: + if (resolved_size.height() <= 0) { + // Render as if the ending shape was an ellipse whose width was an arbitrary very large number and whose height + // was an arbitrary very small number greater than zero. This will make the gradient look like a solid-color image equal + // to the color of the last color-stop, or equal to the average color of the gradient if it’s repeating. + return Gfx::FloatSize { arbitrary_large_number, arbitrary_small_number }; + } + return resolved_size; +} + +void RadialGradientStyleValue::resolve_for_size(Layout::Node const& node, CSSPixelSize paint_size) const +{ + CSSPixelRect gradient_box { { 0, 0 }, paint_size }; + auto center = m_properties.position.resolved(node, gradient_box).to_type(); + auto gradient_size = resolve_size(node, center, gradient_box.to_type()); + if (m_resolved.has_value() && m_resolved->gradient_size == gradient_size) + return; + m_resolved = ResolvedData { + Painting::resolve_radial_gradient_data(node, gradient_size.to_type(), *this), + gradient_size, + center, + }; +} + +bool RadialGradientStyleValue::equals(StyleValue const& other) const +{ + if (type() != other.type()) + return false; + auto& other_gradient = other.as_radial_gradient(); + return m_properties == other_gradient.m_properties; +} + +void RadialGradientStyleValue::paint(PaintContext& context, DevicePixelRect const& dest_rect, CSS::ImageRendering) const +{ + VERIFY(m_resolved.has_value()); + Painting::paint_radial_gradient(context, dest_rect, m_resolved->data, + context.rounded_device_point(m_resolved->center.to_type()), + context.rounded_device_size(m_resolved->gradient_size.to_type())); +} + +} diff --git a/Userland/Libraries/LibWeb/CSS/StyleValues/RadialGradientStyleValue.h b/Userland/Libraries/LibWeb/CSS/StyleValues/RadialGradientStyleValue.h new file mode 100644 index 0000000000..cfa0639e2e --- /dev/null +++ b/Userland/Libraries/LibWeb/CSS/StyleValues/RadialGradientStyleValue.h @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2021, Tobias Christiansen + * Copyright (c) 2021-2023, Sam Atkins + * Copyright (c) 2022-2023, MacDue + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace Web::CSS { + +class RadialGradientStyleValue final : public AbstractImageStyleValue { +public: + enum class EndingShape { + Circle, + Ellipse + }; + + enum class Extent { + ClosestCorner, + ClosestSide, + FarthestCorner, + FarthestSide + }; + + struct CircleSize { + Length radius; + bool operator==(CircleSize const&) const = default; + }; + + struct EllipseSize { + LengthPercentage radius_a; + LengthPercentage radius_b; + bool operator==(EllipseSize const&) const = default; + }; + + using Size = Variant; + + static ValueComparingNonnullRefPtr create(EndingShape ending_shape, Size size, PositionValue position, Vector color_stop_list, GradientRepeating repeating) + { + VERIFY(color_stop_list.size() >= 2); + return adopt_ref(*new RadialGradientStyleValue(ending_shape, size, position, move(color_stop_list), repeating)); + } + + virtual ErrorOr to_string() const override; + + void paint(PaintContext&, DevicePixelRect const& dest_rect, CSS::ImageRendering) const override; + + virtual bool equals(StyleValue const& other) const override; + + Vector const& color_stop_list() const + { + return m_properties.color_stop_list; + } + + bool is_paintable() const override { return true; } + + void resolve_for_size(Layout::Node const&, CSSPixelSize) const override; + + Gfx::FloatSize resolve_size(Layout::Node const&, Gfx::FloatPoint, Gfx::FloatRect const&) const; + + bool is_repeating() const { return m_properties.repeating == GradientRepeating::Yes; } + + virtual ~RadialGradientStyleValue() override = default; + +private: + RadialGradientStyleValue(EndingShape ending_shape, Size size, PositionValue position, Vector color_stop_list, GradientRepeating repeating) + : AbstractImageStyleValue(Type::RadialGradient) + , m_properties { .ending_shape = ending_shape, .size = size, .position = position, .color_stop_list = move(color_stop_list), .repeating = repeating } + { + } + + struct Properties { + EndingShape ending_shape; + Size size; + PositionValue position; + Vector color_stop_list; + GradientRepeating repeating; + bool operator==(Properties const&) const = default; + } m_properties; + + struct ResolvedData { + Painting::RadialGradientData data; + Gfx::FloatSize gradient_size; + Gfx::FloatPoint center; + }; + + mutable Optional m_resolved; +}; + +} diff --git a/Userland/Libraries/LibWeb/Painting/GradientPainting.cpp b/Userland/Libraries/LibWeb/Painting/GradientPainting.cpp index d409c63fb7..f7962f9b92 100644 --- a/Userland/Libraries/LibWeb/Painting/GradientPainting.cpp +++ b/Userland/Libraries/LibWeb/Painting/GradientPainting.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include namespace Web::Painting {