diff options
Diffstat (limited to 'Userland/Libraries/LibWeb/Painting')
27 files changed, 1206 insertions, 19 deletions
diff --git a/Userland/Libraries/LibWeb/Painting/ButtonPaintable.cpp b/Userland/Libraries/LibWeb/Painting/ButtonPaintable.cpp new file mode 100644 index 0000000000..c05a80bf92 --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/ButtonPaintable.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <LibWeb/HTML/HTMLImageElement.h> +#include <LibWeb/Layout/ButtonBox.h> +#include <LibWeb/Painting/ButtonPaintable.h> + +namespace Web::Painting { + +NonnullOwnPtr<ButtonPaintable> ButtonPaintable::create(Layout::ButtonBox const& layout_box) +{ + return adopt_own(*new ButtonPaintable(layout_box)); +} + +ButtonPaintable::ButtonPaintable(Layout::ButtonBox const& layout_box) + : Paintable(layout_box) +{ +} + +Layout::ButtonBox const& ButtonPaintable::layout_box() const +{ + return static_cast<Layout::ButtonBox const&>(m_layout_box); +} + +void ButtonPaintable::paint(PaintContext& context, PaintPhase phase) const +{ + if (!is_visible()) + return; + + Paintable::paint(context, phase); + + if (phase == PaintPhase::Foreground) { + auto text_rect = enclosing_int_rect(absolute_rect()); + if (layout_box().being_pressed()) + text_rect.translate_by(1, 1); + context.painter().draw_text(text_rect, layout_box().dom_node().value(), layout_box().font(), Gfx::TextAlignment::Center, computed_values().color()); + } +} + +} diff --git a/Userland/Libraries/LibWeb/Painting/ButtonPaintable.h b/Userland/Libraries/LibWeb/Painting/ButtonPaintable.h new file mode 100644 index 0000000000..a8483594eb --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/ButtonPaintable.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2022, Andreas Kling <kling@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <LibWeb/Layout/ButtonBox.h> +#include <LibWeb/Painting/Paintable.h> + +namespace Web::Painting { + +class ButtonPaintable final : public Paintable { +public: + static NonnullOwnPtr<ButtonPaintable> create(Layout::ButtonBox const&); + + virtual void paint(PaintContext&, PaintPhase) const override; + + Layout::ButtonBox const& layout_box() const; + +private: + ButtonPaintable(Layout::ButtonBox const&); +}; + +} diff --git a/Userland/Libraries/LibWeb/Painting/CanvasPaintable.cpp b/Userland/Libraries/LibWeb/Painting/CanvasPaintable.cpp new file mode 100644 index 0000000000..e2ff5039e9 --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/CanvasPaintable.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2022, Andreas Kling <kling@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <LibWeb/Painting/CanvasPaintable.h> + +namespace Web::Painting { + +NonnullOwnPtr<CanvasPaintable> CanvasPaintable::create(Layout::CanvasBox const& layout_box) +{ + return adopt_own(*new CanvasPaintable(layout_box)); +} + +CanvasPaintable::CanvasPaintable(Layout::CanvasBox const& layout_box) + : Paintable(layout_box) +{ +} + +Layout::CanvasBox const& CanvasPaintable::layout_box() const +{ + return static_cast<Layout::CanvasBox const&>(m_layout_box); +} + +void CanvasPaintable::paint(PaintContext& context, PaintPhase phase) const +{ + if (!layout_box().is_visible()) + return; + + Paintable::paint(context, phase); + + if (phase == PaintPhase::Foreground) { + // FIXME: This should be done at a different level. Also rect() does not include padding etc! + if (!context.viewport_rect().intersects(enclosing_int_rect(absolute_rect()))) + return; + + if (layout_box().dom_node().bitmap()) + context.painter().draw_scaled_bitmap(rounded_int_rect(absolute_rect()), *layout_box().dom_node().bitmap(), layout_box().dom_node().bitmap()->rect(), 1.0f, to_gfx_scaling_mode(computed_values().image_rendering())); + } +} + +} diff --git a/Userland/Libraries/LibWeb/Painting/CanvasPaintable.h b/Userland/Libraries/LibWeb/Painting/CanvasPaintable.h new file mode 100644 index 0000000000..c43707233a --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/CanvasPaintable.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2022, Andreas Kling <kling@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <LibWeb/Layout/CanvasBox.h> +#include <LibWeb/Painting/Paintable.h> + +namespace Web::Painting { + +class CanvasPaintable final : public Paintable { +public: + static NonnullOwnPtr<CanvasPaintable> create(Layout::CanvasBox const&); + + virtual void paint(PaintContext&, PaintPhase) const override; + + Layout::CanvasBox const& layout_box() const; + +private: + CanvasPaintable(Layout::CanvasBox const&); +}; + +} diff --git a/Userland/Libraries/LibWeb/Painting/CheckBoxPaintable.cpp b/Userland/Libraries/LibWeb/Painting/CheckBoxPaintable.cpp new file mode 100644 index 0000000000..33e480f78e --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/CheckBoxPaintable.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <LibGfx/StylePainter.h> +#include <LibWeb/HTML/HTMLImageElement.h> +#include <LibWeb/Layout/CheckBox.h> +#include <LibWeb/Painting/CheckBoxPaintable.h> + +namespace Web::Painting { + +NonnullOwnPtr<CheckBoxPaintable> CheckBoxPaintable::create(Layout::CheckBox const& layout_box) +{ + return adopt_own(*new CheckBoxPaintable(layout_box)); +} + +CheckBoxPaintable::CheckBoxPaintable(Layout::CheckBox const& layout_box) + : Paintable(layout_box) +{ +} + +Layout::CheckBox const& CheckBoxPaintable::layout_box() const +{ + return static_cast<Layout::CheckBox const&>(m_layout_box); +} + +void CheckBoxPaintable::paint(PaintContext& context, PaintPhase phase) const +{ + if (!is_visible()) + return; + + Paintable::paint(context, phase); + + if (phase == PaintPhase::Foreground) + Gfx::StylePainter::paint_check_box(context.painter(), enclosing_int_rect(absolute_rect()), context.palette(), layout_box().dom_node().enabled(), layout_box().dom_node().checked(), layout_box().being_pressed()); +} + +} diff --git a/Userland/Libraries/LibWeb/Painting/CheckBoxPaintable.h b/Userland/Libraries/LibWeb/Painting/CheckBoxPaintable.h new file mode 100644 index 0000000000..51cf9a5dbb --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/CheckBoxPaintable.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2022, Andreas Kling <kling@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <LibWeb/Layout/CheckBox.h> +#include <LibWeb/Painting/Paintable.h> + +namespace Web::Painting { + +class CheckBoxPaintable final : public Paintable { +public: + static NonnullOwnPtr<CheckBoxPaintable> create(Layout::CheckBox const&); + + virtual void paint(PaintContext&, PaintPhase) const override; + + Layout::CheckBox const& layout_box() const; + +private: + CheckBoxPaintable(Layout::CheckBox const&); +}; + +} diff --git a/Userland/Libraries/LibWeb/Painting/ImagePaintable.cpp b/Userland/Libraries/LibWeb/Painting/ImagePaintable.cpp new file mode 100644 index 0000000000..ec5f445330 --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/ImagePaintable.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <LibGfx/StylePainter.h> +#include <LibWeb/HTML/HTMLImageElement.h> +#include <LibWeb/Layout/ImageBox.h> +#include <LibWeb/Painting/ImagePaintable.h> + +namespace Web::Painting { + +NonnullOwnPtr<ImagePaintable> ImagePaintable::create(Layout::ImageBox const& layout_box) +{ + return adopt_own(*new ImagePaintable(layout_box)); +} + +ImagePaintable::ImagePaintable(Layout::ImageBox const& layout_box) + : Paintable(layout_box) +{ +} + +Layout::ImageBox const& ImagePaintable::layout_box() const +{ + return static_cast<Layout::ImageBox const&>(m_layout_box); +} + +void ImagePaintable::paint(PaintContext& context, PaintPhase phase) const +{ + if (!is_visible()) + return; + + // FIXME: This should be done at a different level. Also rect() does not include padding etc! + if (!context.viewport_rect().intersects(enclosing_int_rect(absolute_rect()))) + return; + + Paintable::paint(context, phase); + + if (phase == PaintPhase::Foreground) { + if (layout_box().renders_as_alt_text()) { + auto& image_element = verify_cast<HTML::HTMLImageElement>(*dom_node()); + context.painter().set_font(Gfx::FontDatabase::default_font()); + Gfx::StylePainter::paint_frame(context.painter(), enclosing_int_rect(absolute_rect()), context.palette(), Gfx::FrameShape::Container, Gfx::FrameShadow::Sunken, 2); + auto alt = image_element.alt(); + if (alt.is_empty()) + alt = image_element.src(); + context.painter().draw_text(enclosing_int_rect(absolute_rect()), alt, Gfx::TextAlignment::Center, computed_values().color(), Gfx::TextElision::Right); + } else if (auto bitmap = layout_box().image_loader().bitmap(layout_box().image_loader().current_frame_index())) { + context.painter().draw_scaled_bitmap(rounded_int_rect(absolute_rect()), *bitmap, bitmap->rect(), 1.0f, to_gfx_scaling_mode(computed_values().image_rendering())); + } + } +} + +} diff --git a/Userland/Libraries/LibWeb/Painting/ImagePaintable.h b/Userland/Libraries/LibWeb/Painting/ImagePaintable.h new file mode 100644 index 0000000000..8ede3c2514 --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/ImagePaintable.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2022, Andreas Kling <kling@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <LibWeb/Layout/ImageBox.h> +#include <LibWeb/Painting/Paintable.h> + +namespace Web::Painting { + +class ImagePaintable final : public Paintable { +public: + static NonnullOwnPtr<ImagePaintable> create(Layout::ImageBox const&); + + virtual void paint(PaintContext&, PaintPhase) const override; + + Layout::ImageBox const& layout_box() const; + +private: + ImagePaintable(Layout::ImageBox const&); +}; + +} diff --git a/Userland/Libraries/LibWeb/Painting/MarkerPaintable.cpp b/Userland/Libraries/LibWeb/Painting/MarkerPaintable.cpp new file mode 100644 index 0000000000..5cffd99a87 --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/MarkerPaintable.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <LibGfx/StylePainter.h> +#include <LibWeb/Layout/ListItemMarkerBox.h> +#include <LibWeb/Painting/MarkerPaintable.h> + +namespace Web::Painting { + +NonnullOwnPtr<MarkerPaintable> MarkerPaintable::create(Layout::ListItemMarkerBox const& layout_box) +{ + return adopt_own(*new MarkerPaintable(layout_box)); +} + +MarkerPaintable::MarkerPaintable(Layout::ListItemMarkerBox const& layout_box) + : Paintable(layout_box) +{ +} + +Layout::ListItemMarkerBox const& MarkerPaintable::layout_box() const +{ + return static_cast<Layout::ListItemMarkerBox const&>(m_layout_box); +} + +void MarkerPaintable::paint(PaintContext& context, PaintPhase phase) const +{ + if (phase != PaintPhase::Foreground) + return; + + auto enclosing = enclosing_int_rect(absolute_rect()); + + if (auto const* list_style_image = layout_box().list_style_image_bitmap()) { + context.painter().blit(enclosing.location(), *list_style_image, list_style_image->rect()); + return; + } + + auto color = computed_values().color(); + + int marker_width = (int)enclosing.height() / 2; + Gfx::IntRect marker_rect { 0, 0, marker_width, marker_width }; + marker_rect.center_within(enclosing); + + switch (layout_box().list_style_type()) { + case CSS::ListStyleType::Square: + context.painter().fill_rect(marker_rect, color); + break; + case CSS::ListStyleType::Circle: + // For some reason for draw_ellipse() the ellipse is outside of the rect while for fill_ellipse() the ellipse is inside. + // Scale the marker_rect with sqrt(2) to get an ellipse arc (circle) that appears as if it was inside of the marker_rect. + marker_rect.set_height(marker_rect.height() / 1.41); + marker_rect.set_width(marker_rect.width() / 1.41); + marker_rect.center_within(enclosing); + context.painter().draw_ellipse_intersecting(marker_rect, color); + break; + case CSS::ListStyleType::Disc: + context.painter().fill_ellipse(marker_rect, color); + break; + case CSS::ListStyleType::Decimal: + case CSS::ListStyleType::DecimalLeadingZero: + case CSS::ListStyleType::LowerAlpha: + case CSS::ListStyleType::LowerLatin: + case CSS::ListStyleType::LowerRoman: + case CSS::ListStyleType::UpperAlpha: + case CSS::ListStyleType::UpperLatin: + case CSS::ListStyleType::UpperRoman: + if (layout_box().text().is_null()) + break; + context.painter().draw_text(enclosing, layout_box().text(), Gfx::TextAlignment::Center); + break; + case CSS::ListStyleType::None: + return; + + default: + VERIFY_NOT_REACHED(); + } +} + +} diff --git a/Userland/Libraries/LibWeb/Painting/MarkerPaintable.h b/Userland/Libraries/LibWeb/Painting/MarkerPaintable.h new file mode 100644 index 0000000000..e4f38d542a --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/MarkerPaintable.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2022, Andreas Kling <kling@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <LibWeb/Layout/ListItemMarkerBox.h> +#include <LibWeb/Painting/Paintable.h> + +namespace Web::Painting { + +class MarkerPaintable final : public Paintable { +public: + static NonnullOwnPtr<MarkerPaintable> create(Layout::ListItemMarkerBox const&); + + virtual void paint(PaintContext&, PaintPhase) const override; + + Layout::ListItemMarkerBox const& layout_box() const; + +private: + MarkerPaintable(Layout::ListItemMarkerBox const&); +}; + +} diff --git a/Userland/Libraries/LibWeb/Painting/NestedBrowsingContextPaintable.cpp b/Userland/Libraries/LibWeb/Painting/NestedBrowsingContextPaintable.cpp new file mode 100644 index 0000000000..5ef89071b2 --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/NestedBrowsingContextPaintable.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <AK/Debug.h> +#include <LibWeb/HTML/BrowsingContextContainer.h> +#include <LibWeb/Layout/FrameBox.h> +#include <LibWeb/Layout/InitialContainingBlock.h> +#include <LibWeb/Painting/NestedBrowsingContextPaintable.h> + +namespace Web::Painting { + +NonnullOwnPtr<NestedBrowsingContextPaintable> NestedBrowsingContextPaintable::create(Layout::FrameBox const& layout_box) +{ + return adopt_own(*new NestedBrowsingContextPaintable(layout_box)); +} + +NestedBrowsingContextPaintable::NestedBrowsingContextPaintable(Layout::FrameBox const& layout_box) + : Paintable(layout_box) +{ +} + +Layout::FrameBox const& NestedBrowsingContextPaintable::layout_box() const +{ + return static_cast<Layout::FrameBox const&>(m_layout_box); +} + +void NestedBrowsingContextPaintable::paint(PaintContext& context, PaintPhase phase) const +{ + Paintable::paint(context, phase); + + if (phase == PaintPhase::Foreground) { + auto* hosted_document = layout_box().dom_node().content_document_without_origin_check(); + if (!hosted_document) + return; + auto* hosted_layout_tree = hosted_document->layout_node(); + if (!hosted_layout_tree) + return; + + context.painter().save(); + auto old_viewport_rect = context.viewport_rect(); + + context.painter().add_clip_rect(enclosing_int_rect(absolute_rect())); + context.painter().translate(absolute_x(), absolute_y()); + + context.set_viewport_rect({ {}, layout_box().dom_node().nested_browsing_context()->size() }); + const_cast<Layout::InitialContainingBlock*>(hosted_layout_tree)->paint_all_phases(context); + + context.set_viewport_rect(old_viewport_rect); + context.painter().restore(); + + if constexpr (HIGHLIGHT_FOCUSED_FRAME_DEBUG) { + if (layout_box().dom_node().nested_browsing_context()->is_focused_context()) { + context.painter().draw_rect(absolute_rect().to_type<int>(), Color::Cyan); + } + } + } +} + +} diff --git a/Userland/Libraries/LibWeb/Painting/NestedBrowsingContextPaintable.h b/Userland/Libraries/LibWeb/Painting/NestedBrowsingContextPaintable.h new file mode 100644 index 0000000000..b524c090f4 --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/NestedBrowsingContextPaintable.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2022, Andreas Kling <kling@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <LibWeb/Layout/FrameBox.h> +#include <LibWeb/Painting/Paintable.h> + +namespace Web::Painting { + +class NestedBrowsingContextPaintable final : public Paintable { +public: + static NonnullOwnPtr<NestedBrowsingContextPaintable> create(Layout::FrameBox const&); + + virtual void paint(PaintContext&, PaintPhase) const override; + + Layout::FrameBox const& layout_box() const; + +private: + NestedBrowsingContextPaintable(Layout::FrameBox const&); +}; + +} diff --git a/Userland/Libraries/LibWeb/Painting/Paintable.cpp b/Userland/Libraries/LibWeb/Painting/Paintable.cpp index faeb943b45..a0852301a9 100644 --- a/Userland/Libraries/LibWeb/Painting/Paintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/Paintable.cpp @@ -4,8 +4,12 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include <LibWeb/DOM/Document.h> +#include <LibWeb/HTML/HTMLHtmlElement.h> #include <LibWeb/Layout/BlockContainer.h> +#include <LibWeb/Painting/BackgroundPainting.h> #include <LibWeb/Painting/Paintable.h> +#include <LibWeb/Painting/ShadowPainting.h> namespace Web::Painting { @@ -82,4 +86,200 @@ Painting::StackingContext* Paintable::enclosing_stacking_context() VERIFY_NOT_REACHED(); } +void Paintable::paint(PaintContext& context, PaintPhase phase) const +{ + if (!is_visible()) + return; + + if (phase == PaintPhase::Background) { + paint_background(context); + paint_box_shadow(context); + } + + if (phase == PaintPhase::Border) { + paint_border(context); + } + + if (phase == PaintPhase::Overlay && m_layout_box.dom_node() && m_layout_box.document().inspected_node() == m_layout_box.dom_node()) { + auto content_rect = absolute_rect(); + + auto margin_box = box_model().margin_box(); + Gfx::FloatRect margin_rect; + margin_rect.set_x(absolute_x() - margin_box.left); + margin_rect.set_width(content_width() + margin_box.left + margin_box.right); + margin_rect.set_y(absolute_y() - margin_box.top); + margin_rect.set_height(content_height() + margin_box.top + margin_box.bottom); + + auto border_rect = absolute_border_box_rect(); + auto padding_rect = absolute_padding_box_rect(); + + auto paint_inspector_rect = [&](Gfx::FloatRect const& rect, Color color) { + context.painter().fill_rect(enclosing_int_rect(rect), Color(color).with_alpha(100)); + context.painter().draw_rect(enclosing_int_rect(rect), Color(color)); + }; + + paint_inspector_rect(margin_rect, Color::Yellow); + paint_inspector_rect(padding_rect, Color::Cyan); + paint_inspector_rect(border_rect, Color::Green); + paint_inspector_rect(content_rect, Color::Magenta); + + StringBuilder builder; + if (m_layout_box.dom_node()) + builder.append(m_layout_box.dom_node()->debug_description()); + else + builder.append(m_layout_box.debug_description()); + builder.appendff(" {}x{} @ {},{}", border_rect.width(), border_rect.height(), border_rect.x(), border_rect.y()); + auto size_text = builder.to_string(); + auto size_text_rect = border_rect; + size_text_rect.set_y(border_rect.y() + border_rect.height()); + size_text_rect.set_top(size_text_rect.top()); + size_text_rect.set_width((float)context.painter().font().width(size_text) + 4); + size_text_rect.set_height(context.painter().font().glyph_height() + 4); + context.painter().fill_rect(enclosing_int_rect(size_text_rect), context.palette().color(Gfx::ColorRole::Tooltip)); + context.painter().draw_rect(enclosing_int_rect(size_text_rect), context.palette().threed_shadow1()); + context.painter().draw_text(enclosing_int_rect(size_text_rect), size_text, Gfx::TextAlignment::Center, context.palette().color(Gfx::ColorRole::TooltipText)); + } + + if (phase == PaintPhase::FocusOutline && m_layout_box.dom_node() && m_layout_box.dom_node()->is_element() && verify_cast<DOM::Element>(*m_layout_box.dom_node()).is_focused()) { + context.painter().draw_rect(enclosing_int_rect(absolute_rect()), context.palette().focus_outline()); + } +} + +void Paintable::paint_border(PaintContext& context) const +{ + auto borders_data = BordersData { + .top = computed_values().border_top(), + .right = computed_values().border_right(), + .bottom = computed_values().border_bottom(), + .left = computed_values().border_left(), + }; + paint_all_borders(context, absolute_border_box_rect(), normalized_border_radius_data(), borders_data); +} + +void Paintable::paint_background(PaintContext& context) const +{ + // If the body's background properties were propagated to the root element, do no re-paint the body's background. + if (m_layout_box.is_body() && document().html_element()->should_use_body_background_properties()) + return; + + Gfx::IntRect background_rect; + Color background_color = computed_values().background_color(); + auto* background_layers = &computed_values().background_layers(); + + if (m_layout_box.is_root_element()) { + // CSS 2.1 Appendix E.2: If the element is a root element, paint the background over the entire canvas. + background_rect = context.viewport_rect(); + + // Section 2.11.2: If the computed value of background-image on the root element is none and its background-color is transparent, + // user agents must instead propagate the computed values of the background properties from that element’s first HTML BODY child element. + if (document().html_element()->should_use_body_background_properties()) { + background_layers = document().background_layers(); + background_color = document().background_color(context.palette()); + } + } else { + background_rect = enclosing_int_rect(absolute_padding_box_rect()); + } + + // HACK: If the Box has a border, use the bordered_rect to paint the background. + // This way if we have a border-radius there will be no gap between the filling and actual border. + if (computed_values().border_top().width || computed_values().border_right().width || computed_values().border_bottom().width || computed_values().border_left().width) + background_rect = enclosing_int_rect(absolute_border_box_rect()); + + Painting::paint_background(context, m_layout_box, background_rect, background_color, background_layers, normalized_border_radius_data()); +} + +void Paintable::paint_box_shadow(PaintContext& context) const +{ + auto box_shadow_data = computed_values().box_shadow(); + if (box_shadow_data.is_empty()) + return; + + Vector<BoxShadowData> resolved_box_shadow_data; + resolved_box_shadow_data.ensure_capacity(box_shadow_data.size()); + for (auto const& layer : box_shadow_data) { + resolved_box_shadow_data.empend( + layer.color, + static_cast<int>(layer.offset_x.to_px(m_layout_box)), + static_cast<int>(layer.offset_y.to_px(m_layout_box)), + static_cast<int>(layer.blur_radius.to_px(m_layout_box)), + static_cast<int>(layer.spread_distance.to_px(m_layout_box)), + layer.placement == CSS::BoxShadowPlacement::Outer ? BoxShadowPlacement::Outer : BoxShadowPlacement::Inner); + } + Painting::paint_box_shadow(context, enclosing_int_rect(absolute_border_box_rect()), resolved_box_shadow_data); +} + +BorderRadiusData Paintable::normalized_border_radius_data() const +{ + return Painting::normalized_border_radius_data(m_layout_box, absolute_border_box_rect(), + computed_values().border_top_left_radius(), + computed_values().border_top_right_radius(), + computed_values().border_bottom_right_radius(), + computed_values().border_bottom_left_radius()); +} + +void Paintable::before_children_paint(PaintContext& context, PaintPhase) const +{ + // FIXME: Support more overflow variations. + if (computed_values().overflow_x() == CSS::Overflow::Hidden && computed_values().overflow_y() == CSS::Overflow::Hidden) { + context.painter().save(); + context.painter().add_clip_rect(enclosing_int_rect(absolute_border_box_rect())); + } +} + +void Paintable::after_children_paint(PaintContext& context, PaintPhase) const +{ + // FIXME: Support more overflow variations. + if (computed_values().overflow_x() == CSS::Overflow::Hidden && computed_values().overflow_y() == CSS::Overflow::Hidden) + context.painter().restore(); +} + +void PaintableWithLines::paint(PaintContext& context, PaintPhase phase) const +{ + if (!is_visible()) + return; + + Paintable::paint(context, phase); + + if (m_line_boxes.is_empty()) + return; + + bool should_clip_overflow = computed_values().overflow_x() != CSS::Overflow::Visible && computed_values().overflow_y() != CSS::Overflow::Visible; + + if (should_clip_overflow) { + context.painter().save(); + // FIXME: Handle overflow-x and overflow-y being different values. + context.painter().add_clip_rect(enclosing_int_rect(absolute_padding_box_rect())); + auto scroll_offset = static_cast<Layout::BlockContainer const&>(m_layout_box).scroll_offset(); + context.painter().translate(-scroll_offset.to_type<int>()); + } + + for (auto& line_box : m_line_boxes) { + for (auto& fragment : line_box.fragments()) { + if (context.should_show_line_box_borders()) + context.painter().draw_rect(enclosing_int_rect(fragment.absolute_rect()), Color::Green); + const_cast<Layout::LineBoxFragment&>(fragment).paint(context, phase); + } + } + + if (should_clip_overflow) { + context.painter().restore(); + } + + // FIXME: Merge this loop with the above somehow.. + if (phase == PaintPhase::FocusOutline) { + for (auto& line_box : m_line_boxes) { + for (auto& fragment : line_box.fragments()) { + auto* node = fragment.layout_node().dom_node(); + if (!node) + continue; + auto* parent = node->parent_element(); + if (!parent) + continue; + if (parent->is_focused()) + context.painter().draw_rect(enclosing_int_rect(fragment.absolute_rect()), context.palette().focus_outline()); + } + } + } +} + } diff --git a/Userland/Libraries/LibWeb/Painting/Paintable.h b/Userland/Libraries/LibWeb/Painting/Paintable.h index 43c013c12e..3bfe1540e6 100644 --- a/Userland/Libraries/LibWeb/Painting/Paintable.h +++ b/Userland/Libraries/LibWeb/Painting/Paintable.h @@ -22,10 +22,14 @@ public: virtual ~Paintable(); + virtual void paint(PaintContext&, PaintPhase) const; + + bool is_visible() const { return m_layout_box.is_visible(); } + Layout::Box const& m_layout_box; - auto& box_model() { return m_layout_box.box_model(); } auto const& box_model() const { return m_layout_box.box_model(); } + auto const& computed_values() const { return m_layout_box.computed_values(); } struct OverflowData { Gfx::FloatRect scrollable_overflow_rect; @@ -109,10 +113,22 @@ public: void set_stacking_context(NonnullOwnPtr<Painting::StackingContext> context) { m_stacking_context = move(context); } StackingContext* enclosing_stacking_context(); + DOM::Node const* dom_node() const { return m_layout_box.dom_node(); } + DOM::Document const& document() const { return m_layout_box.document(); } + + virtual void before_children_paint(PaintContext&, PaintPhase) const; + virtual void after_children_paint(PaintContext&, PaintPhase) const; + protected: explicit Paintable(Layout::Box const&); + virtual void paint_border(PaintContext&) const; + virtual void paint_background(PaintContext&) const; + virtual void paint_box_shadow(PaintContext&) const; + private: + Painting::BorderRadiusData normalized_border_radius_data() const; + OwnPtr<Painting::StackingContext> m_stacking_context; }; @@ -138,6 +154,8 @@ public: } } + virtual void paint(PaintContext&, PaintPhase) const override; + private: PaintableWithLines(Layout::BlockContainer const&); diff --git a/Userland/Libraries/LibWeb/Painting/ProgressPaintable.cpp b/Userland/Libraries/LibWeb/Painting/ProgressPaintable.cpp new file mode 100644 index 0000000000..de1f8cb7a7 --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/ProgressPaintable.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <LibGfx/StylePainter.h> +#include <LibWeb/Painting/ProgressPaintable.h> + +namespace Web::Painting { + +NonnullOwnPtr<ProgressPaintable> ProgressPaintable::create(Layout::Progress const& layout_box) +{ + return adopt_own(*new ProgressPaintable(layout_box)); +} + +ProgressPaintable::ProgressPaintable(Layout::Progress const& layout_box) + : Paintable(layout_box) +{ +} + +Layout::Progress const& ProgressPaintable::layout_box() const +{ + return static_cast<Layout::Progress const&>(m_layout_box); +} + +void ProgressPaintable::paint(PaintContext& context, PaintPhase phase) const +{ + if (!is_visible()) + return; + + if (phase == PaintPhase::Foreground) { + // FIXME: This does not support floating point value() and max() + Gfx::StylePainter::paint_progressbar(context.painter(), enclosing_int_rect(absolute_rect()), context.palette(), 0, layout_box().dom_node().max(), layout_box().dom_node().value(), ""); + } +} + +} diff --git a/Userland/Libraries/LibWeb/Painting/ProgressPaintable.h b/Userland/Libraries/LibWeb/Painting/ProgressPaintable.h new file mode 100644 index 0000000000..097132d352 --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/ProgressPaintable.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2022, Andreas Kling <kling@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <LibWeb/Layout/Progress.h> +#include <LibWeb/Painting/Paintable.h> + +namespace Web::Painting { + +class ProgressPaintable final : public Paintable { +public: + static NonnullOwnPtr<ProgressPaintable> create(Layout::Progress const&); + + virtual void paint(PaintContext&, PaintPhase) const override; + + Layout::Progress const& layout_box() const; + +private: + ProgressPaintable(Layout::Progress const&); +}; + +} diff --git a/Userland/Libraries/LibWeb/Painting/RadioButtonPaintable.cpp b/Userland/Libraries/LibWeb/Painting/RadioButtonPaintable.cpp new file mode 100644 index 0000000000..47b5d4a09f --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/RadioButtonPaintable.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <LibGfx/StylePainter.h> +#include <LibWeb/HTML/HTMLImageElement.h> +#include <LibWeb/Layout/RadioButton.h> +#include <LibWeb/Painting/RadioButtonPaintable.h> + +namespace Web::Painting { + +NonnullOwnPtr<RadioButtonPaintable> RadioButtonPaintable::create(Layout::RadioButton const& layout_box) +{ + return adopt_own(*new RadioButtonPaintable(layout_box)); +} + +RadioButtonPaintable::RadioButtonPaintable(Layout::RadioButton const& layout_box) + : Paintable(layout_box) +{ +} + +Layout::RadioButton const& RadioButtonPaintable::layout_box() const +{ + return static_cast<Layout::RadioButton const&>(m_layout_box); +} + +void RadioButtonPaintable::paint(PaintContext& context, PaintPhase phase) const +{ + if (!is_visible()) + return; + + Paintable::paint(context, phase); + + if (phase == PaintPhase::Foreground) + Gfx::StylePainter::paint_radio_button(context.painter(), enclosing_int_rect(absolute_rect()), context.palette(), layout_box().dom_node().checked(), layout_box().being_pressed()); +} + +} diff --git a/Userland/Libraries/LibWeb/Painting/RadioButtonPaintable.h b/Userland/Libraries/LibWeb/Painting/RadioButtonPaintable.h new file mode 100644 index 0000000000..bf356587eb --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/RadioButtonPaintable.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2022, Andreas Kling <kling@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <LibWeb/Layout/RadioButton.h> +#include <LibWeb/Painting/Paintable.h> + +namespace Web::Painting { + +class RadioButtonPaintable final : public Paintable { +public: + static NonnullOwnPtr<RadioButtonPaintable> create(Layout::RadioButton const&); + + virtual void paint(PaintContext&, PaintPhase) const override; + + Layout::RadioButton const& layout_box() const; + +private: + RadioButtonPaintable(Layout::RadioButton const&); +}; + +} diff --git a/Userland/Libraries/LibWeb/Painting/SVGGeometryPaintable.cpp b/Userland/Libraries/LibWeb/Painting/SVGGeometryPaintable.cpp new file mode 100644 index 0000000000..f238b9a8a8 --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/SVGGeometryPaintable.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <LibGfx/AntiAliasingPainter.h> +#include <LibWeb/Layout/ImageBox.h> +#include <LibWeb/Painting/SVGGeometryPaintable.h> +#include <LibWeb/SVG/SVGSVGElement.h> + +namespace Web::Painting { + +NonnullOwnPtr<SVGGeometryPaintable> SVGGeometryPaintable::create(Layout::SVGGeometryBox const& layout_box) +{ + return adopt_own(*new SVGGeometryPaintable(layout_box)); +} + +SVGGeometryPaintable::SVGGeometryPaintable(Layout::SVGGeometryBox const& layout_box) + : SVGGraphicsPaintable(layout_box) +{ +} + +Layout::SVGGeometryBox const& SVGGeometryPaintable::layout_box() const +{ + return static_cast<Layout::SVGGeometryBox const&>(m_layout_box); +} + +void SVGGeometryPaintable::paint(PaintContext& context, PaintPhase phase) const +{ + if (!is_visible()) + return; + + SVGGraphicsPaintable::paint(context, phase); + + if (phase != PaintPhase::Foreground) + return; + + auto& geometry_element = layout_box().dom_node(); + + Gfx::AntiAliasingPainter painter { context.painter() }; + auto& svg_context = context.svg_context(); + + auto offset = svg_context.svg_element_position(); + painter.translate(offset); + + auto const* svg_element = geometry_element.first_ancestor_of_type<SVG::SVGSVGElement>(); + auto maybe_view_box = svg_element->view_box(); + + context.painter().add_clip_rect(enclosing_int_rect(absolute_rect())); + + Gfx::Path path = const_cast<SVG::SVGGeometryElement&>(geometry_element).get_path(); + + if (maybe_view_box.has_value()) { + Gfx::Path new_path; + auto scaling = layout_box().viewbox_scaling(); + auto origin = layout_box().viewbox_origin(); + + auto transform_point = [&scaling, &origin](Gfx::FloatPoint const& point) -> Gfx::FloatPoint { + auto new_point = point; + new_point.translate_by({ -origin.x(), -origin.y() }); + new_point.scale_by(scaling); + return new_point; + }; + + for (auto& segment : path.segments()) { + switch (segment.type()) { + case Gfx::Segment::Type::Invalid: + break; + case Gfx::Segment::Type::MoveTo: + new_path.move_to(transform_point(segment.point())); + break; + case Gfx::Segment::Type::LineTo: + new_path.line_to(transform_point(segment.point())); + break; + case Gfx::Segment::Type::QuadraticBezierCurveTo: { + auto& quadratic_bezier_segment = static_cast<Gfx::QuadraticBezierCurveSegment const&>(segment); + new_path.quadratic_bezier_curve_to(transform_point(quadratic_bezier_segment.through()), transform_point(quadratic_bezier_segment.point())); + break; + } + case Gfx::Segment::Type::CubicBezierCurveTo: { + auto& cubic_bezier_segment = static_cast<Gfx::CubicBezierCurveSegment const&>(segment); + new_path.cubic_bezier_curve_to(transform_point(cubic_bezier_segment.through_0()), transform_point(cubic_bezier_segment.through_1()), transform_point(cubic_bezier_segment.point())); + break; + } + case Gfx::Segment::Type::EllipticalArcTo: { + auto& elliptical_arc_segment = static_cast<Gfx::EllipticalArcSegment const&>(segment); + new_path.elliptical_arc_to(transform_point(elliptical_arc_segment.point()), elliptical_arc_segment.radii().scaled(scaling, scaling), elliptical_arc_segment.x_axis_rotation(), false, false); + break; + } + } + } + + path = new_path; + } + + if (auto fill_color = geometry_element.fill_color().value_or(svg_context.fill_color()); fill_color.alpha() > 0) { + // We need to fill the path before applying the stroke, however the filled + // path must be closed, whereas the stroke path may not necessary be closed. + // Copy the path and close it for filling, but use the previous path for stroke + auto closed_path = path; + closed_path.close(); + + // Fills are computed as though all paths are closed (https://svgwg.org/svg2-draft/painting.html#FillProperties) + painter.fill_path( + closed_path, + fill_color, + Gfx::Painter::WindingRule::EvenOdd); + } + + if (auto stroke_color = geometry_element.stroke_color().value_or(svg_context.stroke_color()); stroke_color.alpha() > 0) { + painter.stroke_path( + path, + stroke_color, + geometry_element.stroke_width().value_or(svg_context.stroke_width())); + } + + painter.translate(-offset); + context.painter().clear_clip_rect(); +} + +} diff --git a/Userland/Libraries/LibWeb/Painting/SVGGeometryPaintable.h b/Userland/Libraries/LibWeb/Painting/SVGGeometryPaintable.h new file mode 100644 index 0000000000..4ae8d553a4 --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/SVGGeometryPaintable.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2022, Andreas Kling <kling@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <LibWeb/Layout/SVGGeometryBox.h> +#include <LibWeb/Painting/SVGGraphicsPaintable.h> + +namespace Web::Painting { + +class SVGGeometryPaintable : public SVGGraphicsPaintable { +public: + static NonnullOwnPtr<SVGGeometryPaintable> create(Layout::SVGGeometryBox const&); + + virtual void paint(PaintContext&, PaintPhase) const override; + + Layout::SVGGeometryBox const& layout_box() const; + +protected: + SVGGeometryPaintable(Layout::SVGGeometryBox const&); +}; + +} diff --git a/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.cpp b/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.cpp new file mode 100644 index 0000000000..b13f202a64 --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <LibWeb/Layout/ImageBox.h> +#include <LibWeb/Painting/SVGGraphicsPaintable.h> + +namespace Web::Painting { + +SVGGraphicsPaintable::SVGGraphicsPaintable(Layout::SVGGraphicsBox const& layout_box) + : SVGPaintable(layout_box) +{ +} + +Layout::SVGGraphicsBox const& SVGGraphicsPaintable::layout_box() const +{ + return static_cast<Layout::SVGGraphicsBox const&>(m_layout_box); +} + +void SVGGraphicsPaintable::before_children_paint(PaintContext& context, PaintPhase phase) const +{ + SVGPaintable::before_children_paint(context, phase); + if (phase != PaintPhase::Foreground) + return; + + auto& graphics_element = verify_cast<SVG::SVGGraphicsElement>(layout_box().dom_node()); + + if (graphics_element.fill_color().has_value()) + context.svg_context().set_fill_color(graphics_element.fill_color().value()); + if (graphics_element.stroke_color().has_value()) + context.svg_context().set_stroke_color(graphics_element.stroke_color().value()); + if (graphics_element.stroke_width().has_value()) + context.svg_context().set_stroke_width(graphics_element.stroke_width().value()); +} + +} diff --git a/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.h b/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.h new file mode 100644 index 0000000000..dffdec0b5f --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2022, Andreas Kling <kling@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <LibWeb/Layout/SVGGraphicsBox.h> +#include <LibWeb/Painting/SVGPaintable.h> + +namespace Web::Painting { + +class SVGGraphicsPaintable : public SVGPaintable { +public: + static NonnullOwnPtr<SVGGraphicsPaintable> create(Layout::SVGGraphicsBox const&); + + virtual void before_children_paint(PaintContext&, PaintPhase) const override; + + Layout::SVGGraphicsBox const& layout_box() const; + +protected: + SVGGraphicsPaintable(Layout::SVGGraphicsBox const&); +}; + +} diff --git a/Userland/Libraries/LibWeb/Painting/SVGPaintable.cpp b/Userland/Libraries/LibWeb/Painting/SVGPaintable.cpp new file mode 100644 index 0000000000..febe538e58 --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/SVGPaintable.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <LibWeb/Layout/ImageBox.h> +#include <LibWeb/Painting/SVGPaintable.h> + +namespace Web::Painting { + +SVGPaintable::SVGPaintable(Layout::SVGBox const& layout_box) + : Paintable(layout_box) +{ +} + +Layout::SVGBox const& SVGPaintable::layout_box() const +{ + return static_cast<Layout::SVGBox const&>(m_layout_box); +} + +void SVGPaintable::before_children_paint(PaintContext& context, PaintPhase phase) const +{ + Paintable::before_children_paint(context, phase); + if (phase != PaintPhase::Foreground) + return; + context.svg_context().save(); +} + +void SVGPaintable::after_children_paint(PaintContext& context, PaintPhase phase) const +{ + Paintable::after_children_paint(context, phase); + if (phase != PaintPhase::Foreground) + return; + context.svg_context().restore(); +} + +} diff --git a/Userland/Libraries/LibWeb/Painting/SVGPaintable.h b/Userland/Libraries/LibWeb/Painting/SVGPaintable.h new file mode 100644 index 0000000000..d429586485 --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/SVGPaintable.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2022, Andreas Kling <kling@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <LibWeb/Layout/SVGBox.h> +#include <LibWeb/Painting/Paintable.h> + +namespace Web::Painting { + +class SVGPaintable : public Paintable { +public: + static NonnullOwnPtr<SVGPaintable> create(Layout::SVGBox const&); + + virtual void before_children_paint(PaintContext&, PaintPhase) const override; + virtual void after_children_paint(PaintContext&, PaintPhase) const override; + + Layout::SVGBox const& layout_box() const; + +protected: + SVGPaintable(Layout::SVGBox const&); +}; + +} diff --git a/Userland/Libraries/LibWeb/Painting/SVGSVGPaintable.cpp b/Userland/Libraries/LibWeb/Painting/SVGSVGPaintable.cpp new file mode 100644 index 0000000000..8871d1311d --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/SVGSVGPaintable.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <LibWeb/Layout/ImageBox.h> +#include <LibWeb/Painting/SVGSVGPaintable.h> + +namespace Web::Painting { + +SVGSVGPaintable::SVGSVGPaintable(Layout::SVGSVGBox const& layout_box) + : SVGGraphicsPaintable(layout_box) +{ +} + +Layout::SVGSVGBox const& SVGSVGPaintable::layout_box() const +{ + return static_cast<Layout::SVGSVGBox const&>(m_layout_box); +} + +void SVGSVGPaintable::before_children_paint(PaintContext& context, PaintPhase phase) const +{ + if (phase != PaintPhase::Foreground) + return; + + if (!context.has_svg_context()) + context.set_svg_context(SVGContext(absolute_rect())); + + SVGGraphicsPaintable::before_children_paint(context, phase); +} + +void SVGSVGPaintable::after_children_paint(PaintContext& context, PaintPhase phase) const +{ + SVGGraphicsPaintable::after_children_paint(context, phase); + if (phase != PaintPhase::Foreground) + return; + context.clear_svg_context(); +} + +} diff --git a/Userland/Libraries/LibWeb/Painting/SVGSVGPaintable.h b/Userland/Libraries/LibWeb/Painting/SVGSVGPaintable.h new file mode 100644 index 0000000000..1bd784d0ce --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/SVGSVGPaintable.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2022, Andreas Kling <kling@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <LibWeb/Layout/SVGSVGBox.h> +#include <LibWeb/Painting/SVGGraphicsPaintable.h> + +namespace Web::Painting { + +class SVGSVGPaintable : public SVGGraphicsPaintable { +public: + static NonnullOwnPtr<SVGSVGPaintable> create(Layout::SVGSVGBox const&); + + virtual void before_children_paint(PaintContext&, PaintPhase) const override; + virtual void after_children_paint(PaintContext&, PaintPhase) const override; + + Layout::SVGSVGBox const& layout_box() const; + +protected: + SVGSVGPaintable(Layout::SVGSVGBox const&); +}; + +} diff --git a/Userland/Libraries/LibWeb/Painting/StackingContext.cpp b/Userland/Libraries/LibWeb/Painting/StackingContext.cpp index ee9603e610..7be14df623 100644 --- a/Userland/Libraries/LibWeb/Painting/StackingContext.cpp +++ b/Userland/Libraries/LibWeb/Painting/StackingContext.cpp @@ -9,12 +9,22 @@ #include <LibGfx/Painter.h> #include <LibWeb/Layout/Box.h> #include <LibWeb/Layout/InitialContainingBlock.h> +#include <LibWeb/Layout/InlineNode.h> #include <LibWeb/Layout/ReplacedBox.h> #include <LibWeb/Painting/Paintable.h> #include <LibWeb/Painting/StackingContext.h> namespace Web::Painting { +static void paint_node(Layout::Node const& layout_node, PaintContext& context, PaintPhase phase) +{ + // FIXME: This whole thing is hairy. Find a nicer solution for painting InlineNode. + if (layout_node.is_box()) + static_cast<Layout::Box const&>(layout_node).paint_box()->paint(context, phase); + else if (is<Layout::InlineNode>(layout_node)) + static_cast<Layout::InlineNode const&>(layout_node).paint_inline(context, phase); +} + StackingContext::StackingContext(Layout::Box& box, StackingContext* parent) : m_box(box) , m_parent(parent) @@ -36,8 +46,10 @@ StackingContext::StackingContext(Layout::Box& box, StackingContext* parent) void StackingContext::paint_descendants(PaintContext& context, Layout::Node& box, StackingContextPaintPhase phase) const { - if (phase == StackingContextPaintPhase::Foreground) - box.before_children_paint(context, PaintPhase::Foreground); + if (phase == StackingContextPaintPhase::Foreground) { + if (box.is_box()) + static_cast<Layout::Box const&>(box).m_paint_box->before_children_paint(context, PaintPhase::Foreground); + } box.for_each_child([&](auto& child) { if (child.establishes_stacking_context()) @@ -46,16 +58,16 @@ void StackingContext::paint_descendants(PaintContext& context, Layout::Node& box switch (phase) { case StackingContextPaintPhase::BackgroundAndBorders: if (!child_is_inline_or_replaced && !child.is_floating() && !child.is_positioned()) { - child.paint(context, PaintPhase::Background); - child.paint(context, PaintPhase::Border); + paint_node(child, context, PaintPhase::Background); + paint_node(child, context, PaintPhase::Border); paint_descendants(context, child, phase); } break; case StackingContextPaintPhase::Floats: if (!child.is_positioned()) { if (child.is_floating()) { - child.paint(context, PaintPhase::Background); - child.paint(context, PaintPhase::Border); + paint_node(child, context, PaintPhase::Background); + paint_node(child, context, PaintPhase::Border); paint_descendants(context, child, StackingContextPaintPhase::BackgroundAndBorders); } paint_descendants(context, child, phase); @@ -64,8 +76,8 @@ void StackingContext::paint_descendants(PaintContext& context, Layout::Node& box case StackingContextPaintPhase::BackgroundAndBordersForInlineLevelAndReplaced: if (!child.is_positioned()) { if (child_is_inline_or_replaced) { - child.paint(context, PaintPhase::Background); - child.paint(context, PaintPhase::Border); + paint_node(child, context, PaintPhase::Background); + paint_node(child, context, PaintPhase::Border); paint_descendants(context, child, StackingContextPaintPhase::BackgroundAndBorders); } paint_descendants(context, child, phase); @@ -73,30 +85,32 @@ void StackingContext::paint_descendants(PaintContext& context, Layout::Node& box break; case StackingContextPaintPhase::Foreground: if (!child.is_positioned()) { - child.paint(context, PaintPhase::Foreground); + paint_node(child, context, PaintPhase::Foreground); paint_descendants(context, child, phase); } break; case StackingContextPaintPhase::FocusAndOverlay: if (context.has_focus()) { - child.paint(context, PaintPhase::FocusOutline); + paint_node(child, context, PaintPhase::FocusOutline); } - child.paint(context, PaintPhase::Overlay); + paint_node(child, context, PaintPhase::Overlay); paint_descendants(context, child, phase); break; } }); - if (phase == StackingContextPaintPhase::Foreground) - box.after_children_paint(context, PaintPhase::Foreground); + if (phase == StackingContextPaintPhase::Foreground) { + if (box.is_box()) + static_cast<Layout::Box const&>(box).m_paint_box->after_children_paint(context, PaintPhase::Foreground); + } } void StackingContext::paint_internal(PaintContext& context) const { // For a more elaborate description of the algorithm, see CSS 2.1 Appendix E // Draw the background and borders for the context root (steps 1, 2) - m_box.paint(context, PaintPhase::Background); - m_box.paint(context, PaintPhase::Border); + paint_node(m_box, context, PaintPhase::Background); + paint_node(m_box, context, PaintPhase::Border); // Draw positioned descendants with negative z-indices (step 3) for (auto* child : m_children) { if (child->m_box.computed_values().z_index().has_value() && child->m_box.computed_values().z_index().value() < 0) @@ -108,7 +122,7 @@ void StackingContext::paint_internal(PaintContext& context) const paint_descendants(context, m_box, StackingContextPaintPhase::Floats); // Draw inline content, replaced content, etc. (steps 6, 7) paint_descendants(context, m_box, StackingContextPaintPhase::BackgroundAndBordersForInlineLevelAndReplaced); - m_box.paint(context, PaintPhase::Foreground); + paint_node(m_box, context, PaintPhase::Foreground); paint_descendants(context, m_box, StackingContextPaintPhase::Foreground); // Draw other positioned descendants (steps 8, 9) for (auto* child : m_children) { @@ -117,8 +131,8 @@ void StackingContext::paint_internal(PaintContext& context) const child->paint(context); } - m_box.paint(context, PaintPhase::FocusOutline); - m_box.paint(context, PaintPhase::Overlay); + paint_node(m_box, context, PaintPhase::FocusOutline); + paint_node(m_box, context, PaintPhase::Overlay); paint_descendants(context, m_box, StackingContextPaintPhase::FocusAndOverlay); } |