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:
parent
f1f1977e2d
commit
040dac558e
3 changed files with 237 additions and 1 deletions
|
@ -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 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 };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue