1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-23 19:37:34 +00:00

LibWeb: Implement RadialGradientStyleValue

Adds a style value for `radial-gradient()`s and implements some helpers
for resolving their properties.
This commit is contained in:
MacDue 2022-11-11 18:13:00 +01:00 committed by Sam Atkins
parent f1f1977e2d
commit 040dac558e
3 changed files with 237 additions and 1 deletions

View file

@ -217,6 +217,12 @@ PositionStyleValue const& StyleValue::as_position() const
return static_cast<PositionStyleValue const&>(*this); return static_cast<PositionStyleValue const&>(*this);
} }
RadialGradientStyleValue const& StyleValue::as_radial_gradient() const
{
VERIFY(is_radial_gradient());
return static_cast<RadialGradientStyleValue const&>(*this);
}
RectStyleValue const& StyleValue::as_rect() const RectStyleValue const& StyleValue::as_rect() const
{ {
VERIFY(is_rect()); VERIFY(is_rect());
@ -1984,6 +1990,163 @@ bool PositionValue::operator==(PositionValue const& other) const
&& variant_equals(vertical_position, other.vertical_position)); && variant_equals(vertical_position, other.vertical_position));
} }
String RadialGradientStyleValue::to_string() const
{
StringBuilder builder;
builder.appendff("radial-gradient({} "sv,
m_ending_shape == EndingShape::Circle ? "circle"sv : "ellipse"sv);
m_size.visit(
[&](Extent extent) {
builder.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) { builder.append(circle_size.radius.to_string()); },
[&](EllipseSize const& ellipse_size) { builder.appendff("{} {}", ellipse_size.radius_a.to_string(), ellipse_size.radius_b.to_string()); });
if (m_position != PositionValue::center()) {
builder.appendff(" at "sv);
m_position.serialize(builder);
}
builder.append(", "sv);
serialize_color_stop_list(builder, m_color_stop_list);
builder.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_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<float>);
};
auto const farthest_side_shape = [&] {
return side_shape(AK::max<float>);
};
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_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
return m_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 gradients 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 gradients 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 };
});
}
void RadialGradientStyleValue::resolve_for_size(Layout::Node const&, Gfx::FloatSize const&) const
{
}
bool RadialGradientStyleValue::equals(StyleValue const& other) const
{
if (type() != other.type())
return false;
auto& other_gradient = other.as_radial_gradient();
return (m_ending_shape == other_gradient.m_ending_shape
&& variant_equals(m_size, other_gradient.m_size)
&& m_position == other_gradient.m_position
&& m_color_stop_list == other_gradient.m_color_stop_list);
}
void RadialGradientStyleValue::paint(PaintContext&, Gfx::IntRect const&, CSS::ImageRendering) const
{
}
String ConicGradientStyleValue::to_string() const String ConicGradientStyleValue::to_string() const
{ {
StringBuilder builder; StringBuilder builder;

View file

@ -241,6 +241,7 @@ public:
Overflow, Overflow,
Percentage, Percentage,
Position, Position,
RadialGradient,
Rect, Rect,
Resolution, Resolution,
Shadow, Shadow,
@ -255,7 +256,7 @@ public:
Type type() const { return m_type; } Type type() const { return m_type; }
bool is_abstract_image() const { return AK::first_is_one_of(type(), Type::Image, Type::LinearGradient, Type::ConicGradient); } bool is_abstract_image() const { return AK::first_is_one_of(type(), Type::Image, Type::LinearGradient, Type::ConicGradient, Type::RadialGradient); }
bool is_angle() const { return type() == Type::Angle; } bool is_angle() const { return type() == Type::Angle; }
bool is_background() const { return type() == Type::Background; } bool is_background() const { return type() == Type::Background; }
bool is_background_repeat() const { return type() == Type::BackgroundRepeat; } bool is_background_repeat() const { return type() == Type::BackgroundRepeat; }
@ -286,6 +287,7 @@ public:
bool is_overflow() const { return type() == Type::Overflow; } bool is_overflow() const { return type() == Type::Overflow; }
bool is_percentage() const { return type() == Type::Percentage; } bool is_percentage() const { return type() == Type::Percentage; }
bool is_position() const { return type() == Type::Position; } bool is_position() const { return type() == Type::Position; }
bool is_radial_gradient() const { return type() == Type::RadialGradient; }
bool is_rect() const { return type() == Type::Rect; } bool is_rect() const { return type() == Type::Rect; }
bool is_resolution() const { return type() == Type::Resolution; } bool is_resolution() const { return type() == Type::Resolution; }
bool is_shadow() const { return type() == Type::Shadow; } bool is_shadow() const { return type() == Type::Shadow; }
@ -330,6 +332,7 @@ public:
OverflowStyleValue const& as_overflow() const; OverflowStyleValue const& as_overflow() const;
PercentageStyleValue const& as_percentage() const; PercentageStyleValue const& as_percentage() const;
PositionStyleValue const& as_position() const; PositionStyleValue const& as_position() const;
RadialGradientStyleValue const& as_radial_gradient() const;
RectStyleValue const& as_rect() const; RectStyleValue const& as_rect() const;
ResolutionStyleValue const& as_resolution() const; ResolutionStyleValue const& as_resolution() const;
ShadowStyleValue const& as_shadow() const; ShadowStyleValue const& as_shadow() const;
@ -372,6 +375,7 @@ public:
OverflowStyleValue& as_overflow() { return const_cast<OverflowStyleValue&>(const_cast<StyleValue const&>(*this).as_overflow()); } OverflowStyleValue& as_overflow() { return const_cast<OverflowStyleValue&>(const_cast<StyleValue const&>(*this).as_overflow()); }
PercentageStyleValue& as_percentage() { return const_cast<PercentageStyleValue&>(const_cast<StyleValue const&>(*this).as_percentage()); } PercentageStyleValue& as_percentage() { return const_cast<PercentageStyleValue&>(const_cast<StyleValue const&>(*this).as_percentage()); }
PositionStyleValue& as_position() { return const_cast<PositionStyleValue&>(const_cast<StyleValue const&>(*this).as_position()); } PositionStyleValue& as_position() { return const_cast<PositionStyleValue&>(const_cast<StyleValue const&>(*this).as_position()); }
RadialGradientStyleValue& as_radial_gradient() { return const_cast<RadialGradientStyleValue&>(const_cast<StyleValue const&>(*this).as_radial_gradient()); }
RectStyleValue& as_rect() { return const_cast<RectStyleValue&>(const_cast<StyleValue const&>(*this).as_rect()); } RectStyleValue& as_rect() { return const_cast<RectStyleValue&>(const_cast<StyleValue const&>(*this).as_rect()); }
ResolutionStyleValue& as_resolution() { return const_cast<ResolutionStyleValue&>(const_cast<StyleValue const&>(*this).as_resolution()); } ResolutionStyleValue& as_resolution() { return const_cast<ResolutionStyleValue&>(const_cast<StyleValue const&>(*this).as_resolution()); }
ShadowStyleValue& as_shadow() { return const_cast<ShadowStyleValue&>(const_cast<StyleValue const&>(*this).as_shadow()); } ShadowStyleValue& as_shadow() { return const_cast<ShadowStyleValue&>(const_cast<StyleValue const&>(*this).as_shadow()); }
@ -1201,6 +1205,74 @@ enum class GradientRepeating {
No 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<Extent, CircleSize, EllipseSize>;
static NonnullRefPtr<RadialGradientStyleValue> create(EndingShape ending_shape, Size size, PositionValue position, Vector<LinearColorStopListElement> color_stop_list)
{
VERIFY(color_stop_list.size() >= 2);
return adopt_ref(*new RadialGradientStyleValue(ending_shape, size, position, move(color_stop_list)));
}
virtual String to_string() const override;
void paint(PaintContext&, Gfx::IntRect const& dest_rect, CSS::ImageRendering) const override;
virtual bool equals(StyleValue const& other) const override;
Vector<LinearColorStopListElement> const& color_stop_list() const
{
return m_color_stop_list;
}
bool is_paintable() const override { return true; }
void resolve_for_size(Layout::Node const&, Gfx::FloatSize const&) const override;
Gfx::FloatSize resolve_size(Layout::Node const&, Gfx::FloatPoint, Gfx::FloatRect const&) const;
virtual ~RadialGradientStyleValue() override = default;
private:
RadialGradientStyleValue(EndingShape ending_shape, Size size, PositionValue position, Vector<LinearColorStopListElement> color_stop_list)
: AbstractImageStyleValue(Type::RadialGradient)
, m_ending_shape(ending_shape)
, m_size(size)
, m_position(position)
, m_color_stop_list(move(color_stop_list))
{
}
EndingShape m_ending_shape;
Size m_size;
PositionValue m_position;
Vector<LinearColorStopListElement> m_color_stop_list;
};
class ConicGradientStyleValue final : public AbstractImageStyleValue { class ConicGradientStyleValue final : public AbstractImageStyleValue {
public: public:
static NonnullRefPtr<ConicGradientStyleValue> create(Angle from_angle, PositionValue position, Vector<AngularColorStopListElement> color_stop_list, GradientRepeating repeating) static NonnullRefPtr<ConicGradientStyleValue> create(Angle from_angle, PositionValue position, Vector<AngularColorStopListElement> color_stop_list, GradientRepeating repeating)

View file

@ -90,6 +90,7 @@ class Percentage;
class PercentageStyleValue; class PercentageStyleValue;
class PositionStyleValue; class PositionStyleValue;
class PropertyOwningCSSStyleDeclaration; class PropertyOwningCSSStyleDeclaration;
class RadialGradientStyleValue;
class RectStyleValue; class RectStyleValue;
class Resolution; class Resolution;
class ResolutionStyleValue; class ResolutionStyleValue;