diff options
author | Andreas Kling <kling@serenityos.org> | 2022-03-10 14:02:25 +0100 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2022-03-11 00:21:49 +0100 |
commit | 02b316fd5cc2039a30cab33edee4595900cb35aa (patch) | |
tree | 22c7ac3c6c93d97fd7548a2b1286516260545a05 | |
parent | f6497b64ac52674e99564d83c4fad6229d06b1ef (diff) | |
download | serenity-02b316fd5cc2039a30cab33edee4595900cb35aa.zip |
LibWeb: Let Paintable perform the painting
This patch adds a bunch of Paintable subclasses, each corresponding to
the Layout::Node subclasses that had a paint() override. All painting
logic is moved from layout nodes into their corresponding paintables.
Paintables are now created by asking a Layout::Box to produce one:
static NonnullOwnPtr<Paintable> Layout::Box::create_paintable()
Note that inline nodes still have their painting logic. Since they
are not boxes, and all paintables have a corresponding box, we'll need
to come up with some other solution for them.
64 files changed, 1306 insertions, 601 deletions
diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 81838b4502..27541f15d4 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -284,8 +284,20 @@ set(SOURCES Page/Page.cpp Painting/BackgroundPainting.cpp Painting/BorderPainting.cpp - Painting/Paintable.cpp + Painting/ButtonPaintable.cpp + Painting/CanvasPaintable.cpp + Painting/CheckBoxPaintable.cpp + Painting/ImagePaintable.cpp + Painting/MarkerPaintable.cpp + Painting/NestedBrowsingContextPaintable.cpp Painting/PaintContext.cpp + Painting/Paintable.cpp + Painting/ProgressPaintable.cpp + Painting/RadioButtonPaintable.cpp + Painting/SVGGeometryPaintable.cpp + Painting/SVGGraphicsPaintable.cpp + Painting/SVGPaintable.cpp + Painting/SVGSVGPaintable.cpp Painting/ShadowPainting.cpp Painting/StackingContext.cpp RequestIdleCallback/IdleDeadline.cpp diff --git a/Userland/Libraries/LibWeb/Layout/BlockContainer.cpp b/Userland/Libraries/LibWeb/Layout/BlockContainer.cpp index d3ce8d6d16..f0340e72d4 100644 --- a/Userland/Libraries/LibWeb/Layout/BlockContainer.cpp +++ b/Userland/Libraries/LibWeb/Layout/BlockContainer.cpp @@ -29,57 +29,6 @@ BlockContainer::~BlockContainer() { } -bool BlockContainer::should_clip_overflow() const -{ - return computed_values().overflow_x() != CSS::Overflow::Visible && computed_values().overflow_y() != CSS::Overflow::Visible; -} - -void BlockContainer::paint(PaintContext& context, Painting::PaintPhase phase) -{ - if (!is_visible()) - return; - - Box::paint(context, phase); - - if (!children_are_inline()) - return; - - 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(m_paint_box->absolute_padding_box_rect())); - context.painter().translate(-m_scroll_offset.to_type<int>()); - } - - for (auto& line_box : paint_box()->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<LineBoxFragment&>(fragment).paint(context, phase); - } - } - - if (should_clip_overflow()) { - context.painter().restore(); - } - - // FIXME: Merge this loop with the above somehow.. - if (phase == Painting::PaintPhase::FocusOutline) { - for (auto& line_box : paint_box()->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()); - } - } - } -} - HitTestResult BlockContainer::hit_test(const Gfx::IntPoint& position, HitTestType type) const { if (!children_are_inline()) @@ -136,4 +85,9 @@ Painting::PaintableWithLines const* BlockContainer::paint_box() const return static_cast<Painting::PaintableWithLines const*>(Box::paint_box()); } +OwnPtr<Painting::Paintable> BlockContainer::create_paintable() const +{ + return Painting::PaintableWithLines::create(*this); +} + } diff --git a/Userland/Libraries/LibWeb/Layout/BlockContainer.h b/Userland/Libraries/LibWeb/Layout/BlockContainer.h index 6b5b547a19..737fca4fb5 100644 --- a/Userland/Libraries/LibWeb/Layout/BlockContainer.h +++ b/Userland/Libraries/LibWeb/Layout/BlockContainer.h @@ -18,8 +18,6 @@ public: BlockContainer(DOM::Document&, DOM::Node*, CSS::ComputedValues); virtual ~BlockContainer() override; - virtual void paint(PaintContext&, Painting::PaintPhase) override; - virtual HitTestResult hit_test(const Gfx::IntPoint&, HitTestType) const override; BlockContainer* previous_sibling() { return verify_cast<BlockContainer>(Node::previous_sibling()); } @@ -33,13 +31,13 @@ public: Painting::PaintableWithLines const* paint_box() const; + virtual OwnPtr<Painting::Paintable> create_paintable() const override; + private: virtual bool is_block_container() const final { return true; } virtual bool wants_mouse_events() const override { return false; } virtual bool handle_mousewheel(Badge<EventHandler>, const Gfx::IntPoint&, unsigned buttons, unsigned modifiers, int wheel_delta_x, int wheel_delta_y) override; - bool should_clip_overflow() const; - Gfx::FloatPoint m_scroll_offset; }; diff --git a/Userland/Libraries/LibWeb/Layout/Box.cpp b/Userland/Libraries/LibWeb/Layout/Box.cpp index 2f7bff7904..ea8382921f 100644 --- a/Userland/Libraries/LibWeb/Layout/Box.cpp +++ b/Userland/Libraries/LibWeb/Layout/Box.cpp @@ -39,137 +39,6 @@ void Box::set_paint_box(OwnPtr<Painting::Paintable> paint_box) m_paint_box = move(paint_box); } -void Box::paint(PaintContext& context, Painting::PaintPhase phase) -{ - if (!is_visible()) - return; - - if (phase == Painting::PaintPhase::Background) { - paint_background(context); - paint_box_shadow(context); - } - - if (phase == Painting::PaintPhase::Border) { - paint_border(context); - } - - if (phase == Painting::PaintPhase::Overlay && dom_node() && document().inspected_node() == dom_node()) { - auto content_rect = m_paint_box->absolute_rect(); - - auto margin_box = box_model().margin_box(); - Gfx::FloatRect margin_rect; - margin_rect.set_x(m_paint_box->absolute_x() - margin_box.left); - margin_rect.set_width(m_paint_box->content_width() + margin_box.left + margin_box.right); - margin_rect.set_y(m_paint_box->absolute_y() - margin_box.top); - margin_rect.set_height(m_paint_box->content_height() + margin_box.top + margin_box.bottom); - - auto border_rect = m_paint_box->absolute_border_box_rect(); - auto padding_rect = m_paint_box->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 (dom_node()) - builder.append(dom_node()->debug_description()); - else - builder.append(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 == Painting::PaintPhase::FocusOutline && dom_node() && dom_node()->is_element() && verify_cast<DOM::Element>(*dom_node()).is_focused()) { - context.painter().draw_rect(enclosing_int_rect(m_paint_box->absolute_rect()), context.palette().focus_outline()); - } -} - -void Box::paint_border(PaintContext& context) -{ - auto borders_data = Painting::BordersData { - .top = computed_values().border_top(), - .right = computed_values().border_right(), - .bottom = computed_values().border_bottom(), - .left = computed_values().border_left(), - }; - Painting::paint_all_borders(context, m_paint_box->absolute_border_box_rect(), normalized_border_radius_data(), borders_data); -} - -void Box::paint_background(PaintContext& context) -{ - // If the body's background properties were propagated to the root element, do no re-paint the body's background. - if (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 (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(m_paint_box->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(m_paint_box->absolute_border_box_rect()); - - Painting::paint_background(context, *this, background_rect, background_color, background_layers, normalized_border_radius_data()); -} - -void Box::paint_box_shadow(PaintContext& context) -{ - auto box_shadow_data = computed_values().box_shadow(); - if (box_shadow_data.is_empty()) - return; - - Vector<Painting::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(*this)), - static_cast<int>(layer.offset_y.to_px(*this)), - static_cast<int>(layer.blur_radius.to_px(*this)), - static_cast<int>(layer.spread_distance.to_px(*this)), - layer.placement == CSS::BoxShadowPlacement::Outer ? Painting::BoxShadowPlacement::Outer : Painting::BoxShadowPlacement::Inner); - } - Painting::paint_box_shadow(context, enclosing_int_rect(m_paint_box->absolute_border_box_rect()), resolved_box_shadow_data); -} - -Painting::BorderRadiusData Box::normalized_border_radius_data() -{ - return Painting::normalized_border_radius_data(*this, m_paint_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()); -} - // https://www.w3.org/TR/css-display-3/#out-of-flow bool Box::is_out_of_flow(FormattingContext const& formatting_context) const { @@ -222,22 +91,9 @@ bool Box::is_body() const return dom_node() && dom_node() == document().body(); } -void Box::before_children_paint(PaintContext& context, Painting::PaintPhase phase) -{ - NodeWithStyleAndBoxModelMetrics::before_children_paint(context, phase); - // 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(m_paint_box->absolute_border_box_rect())); - } -} - -void Box::after_children_paint(PaintContext& context, Painting::PaintPhase phase) +OwnPtr<Painting::Paintable> Box::create_paintable() const { - NodeWithStyleAndBoxModelMetrics::after_children_paint(context, phase); - // FIXME: Support more overflow variations. - if (computed_values().overflow_x() == CSS::Overflow::Hidden && computed_values().overflow_y() == CSS::Overflow::Hidden) - context.painter().restore(); + return Painting::Paintable::create(*this); } } diff --git a/Userland/Libraries/LibWeb/Layout/Box.h b/Userland/Libraries/LibWeb/Layout/Box.h index 03c4f13565..730bebcbf4 100644 --- a/Userland/Libraries/LibWeb/Layout/Box.h +++ b/Userland/Libraries/LibWeb/Layout/Box.h @@ -32,13 +32,6 @@ public: bool is_body() const; - virtual void paint(PaintContext&, Painting::PaintPhase) override; - virtual void paint_border(PaintContext& context); - virtual void paint_box_shadow(PaintContext& context); - virtual void paint_background(PaintContext& context); - - Painting::BorderRadiusData normalized_border_radius_data(); - virtual Optional<float> intrinsic_width() const { return {}; } virtual Optional<float> intrinsic_height() const { return {}; } virtual Optional<float> intrinsic_aspect_ratio() const { return {}; } @@ -47,13 +40,12 @@ public: bool has_intrinsic_height() const { return intrinsic_height().has_value(); } bool has_intrinsic_aspect_ratio() const { return intrinsic_aspect_ratio().has_value(); } - virtual void before_children_paint(PaintContext&, Painting::PaintPhase) override; - virtual void after_children_paint(PaintContext&, Painting::PaintPhase) override; - virtual ~Box() override; virtual void did_set_rect() { } + virtual OwnPtr<Painting::Paintable> create_paintable() const; + protected: Box(DOM::Document&, DOM::Node*, NonnullRefPtr<CSS::StyleProperties>); Box(DOM::Document&, DOM::Node*, CSS::ComputedValues); diff --git a/Userland/Libraries/LibWeb/Layout/BreakNode.cpp b/Userland/Libraries/LibWeb/Layout/BreakNode.cpp index 3097e2df80..4fa194bf2a 100644 --- a/Userland/Libraries/LibWeb/Layout/BreakNode.cpp +++ b/Userland/Libraries/LibWeb/Layout/BreakNode.cpp @@ -20,8 +20,4 @@ BreakNode::~BreakNode() { } -void BreakNode::paint(PaintContext&, Painting::PaintPhase) -{ -} - } diff --git a/Userland/Libraries/LibWeb/Layout/BreakNode.h b/Userland/Libraries/LibWeb/Layout/BreakNode.h index 31672f3041..320f442722 100644 --- a/Userland/Libraries/LibWeb/Layout/BreakNode.h +++ b/Userland/Libraries/LibWeb/Layout/BreakNode.h @@ -20,7 +20,6 @@ public: private: virtual bool is_break_node() const final { return true; } - virtual void paint(PaintContext&, Painting::PaintPhase) override; }; template<> diff --git a/Userland/Libraries/LibWeb/Layout/ButtonBox.cpp b/Userland/Libraries/LibWeb/Layout/ButtonBox.cpp index 11b64eec2b..a69fbc9c84 100644 --- a/Userland/Libraries/LibWeb/Layout/ButtonBox.cpp +++ b/Userland/Libraries/LibWeb/Layout/ButtonBox.cpp @@ -12,7 +12,7 @@ #include <LibWeb/HTML/BrowsingContext.h> #include <LibWeb/Layout/ButtonBox.h> #include <LibWeb/Layout/Label.h> -#include <LibWeb/Painting/Paintable.h> +#include <LibWeb/Painting/ButtonPaintable.h> namespace Web::Layout { @@ -31,21 +31,6 @@ void ButtonBox::prepare_for_replaced_layout() set_intrinsic_height(font().glyph_height()); } -void ButtonBox::paint(PaintContext& context, Painting::PaintPhase phase) -{ - if (!is_visible()) - return; - - LabelableNode::paint(context, phase); - - if (phase == Painting::PaintPhase::Foreground) { - auto text_rect = enclosing_int_rect(m_paint_box->absolute_rect()); - if (m_being_pressed) - text_rect.translate_by(1, 1); - context.painter().draw_text(text_rect, dom_node().value(), font(), Gfx::TextAlignment::Center, computed_values().color()); - } -} - void ButtonBox::handle_mousedown(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned) { if (button != GUI::MouseButton::Primary || !dom_node().enabled()) @@ -121,4 +106,9 @@ void ButtonBox::handle_associated_label_mousemove(Badge<Label>, bool is_inside_n set_needs_display(); } +OwnPtr<Painting::Paintable> ButtonBox::create_paintable() const +{ + return Painting::ButtonPaintable::create(*this); +} + } diff --git a/Userland/Libraries/LibWeb/Layout/ButtonBox.h b/Userland/Libraries/LibWeb/Layout/ButtonBox.h index 29a73778d1..066fca3546 100644 --- a/Userland/Libraries/LibWeb/Layout/ButtonBox.h +++ b/Userland/Libraries/LibWeb/Layout/ButtonBox.h @@ -17,11 +17,14 @@ public: virtual ~ButtonBox() override; virtual void prepare_for_replaced_layout() override; - virtual void paint(PaintContext&, Painting::PaintPhase) override; const HTML::HTMLInputElement& dom_node() const { return static_cast<const HTML::HTMLInputElement&>(LabelableNode::dom_node()); } HTML::HTMLInputElement& dom_node() { return static_cast<HTML::HTMLInputElement&>(LabelableNode::dom_node()); } + bool being_pressed() const { return m_being_pressed; } + + virtual OwnPtr<Painting::Paintable> create_paintable() const override; + private: virtual bool wants_mouse_events() const override { return true; } virtual void handle_mousedown(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers) override; diff --git a/Userland/Libraries/LibWeb/Layout/CanvasBox.cpp b/Userland/Libraries/LibWeb/Layout/CanvasBox.cpp index 433e4d52cd..5e80687300 100644 --- a/Userland/Libraries/LibWeb/Layout/CanvasBox.cpp +++ b/Userland/Libraries/LibWeb/Layout/CanvasBox.cpp @@ -6,7 +6,7 @@ #include <LibGfx/Painter.h> #include <LibWeb/Layout/CanvasBox.h> -#include <LibWeb/Painting/Paintable.h> +#include <LibWeb/Painting/CanvasPaintable.h> namespace Web::Layout { @@ -25,21 +25,9 @@ void CanvasBox::prepare_for_replaced_layout() set_intrinsic_height(dom_node().height()); } -void CanvasBox::paint(PaintContext& context, Painting::PaintPhase phase) +OwnPtr<Painting::Paintable> CanvasBox::create_paintable() const { - if (!is_visible()) - return; - - ReplacedBox::paint(context, phase); - - if (phase == Painting::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(m_paint_box->absolute_rect()))) - return; - - if (dom_node().bitmap()) - context.painter().draw_scaled_bitmap(rounded_int_rect(m_paint_box->absolute_rect()), *dom_node().bitmap(), dom_node().bitmap()->rect(), 1.0f, to_gfx_scaling_mode(computed_values().image_rendering())); - } + return Painting::CanvasPaintable::create(*this); } } diff --git a/Userland/Libraries/LibWeb/Layout/CanvasBox.h b/Userland/Libraries/LibWeb/Layout/CanvasBox.h index 3e1d9a5ab5..8f0df09308 100644 --- a/Userland/Libraries/LibWeb/Layout/CanvasBox.h +++ b/Userland/Libraries/LibWeb/Layout/CanvasBox.h @@ -17,9 +17,10 @@ public: virtual ~CanvasBox() override; virtual void prepare_for_replaced_layout() override; - virtual void paint(PaintContext&, Painting::PaintPhase) override; const HTML::HTMLCanvasElement& dom_node() const { return static_cast<const HTML::HTMLCanvasElement&>(ReplacedBox::dom_node()); } + + virtual OwnPtr<Painting::Paintable> create_paintable() const override; }; } diff --git a/Userland/Libraries/LibWeb/Layout/CheckBox.cpp b/Userland/Libraries/LibWeb/Layout/CheckBox.cpp index 03c5f4eebb..8ba0638914 100644 --- a/Userland/Libraries/LibWeb/Layout/CheckBox.cpp +++ b/Userland/Libraries/LibWeb/Layout/CheckBox.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * Copyright (c) 2020-2022, Andreas Kling <kling@serenityos.org> * * SPDX-License-Identifier: BSD-2-Clause */ @@ -12,7 +12,7 @@ #include <LibWeb/HTML/HTMLInputElement.h> #include <LibWeb/Layout/CheckBox.h> #include <LibWeb/Layout/Label.h> -#include <LibWeb/Painting/Paintable.h> +#include <LibWeb/Painting/CheckBoxPaintable.h> namespace Web::Layout { @@ -27,18 +27,6 @@ CheckBox::~CheckBox() { } -void CheckBox::paint(PaintContext& context, Painting::PaintPhase phase) -{ - if (!is_visible()) - return; - - LabelableNode::paint(context, phase); - - if (phase == Painting::PaintPhase::Foreground) { - Gfx::StylePainter::paint_check_box(context.painter(), enclosing_int_rect(m_paint_box->absolute_rect()), context.palette(), dom_node().enabled(), dom_node().checked(), m_being_pressed); - } -} - void CheckBox::handle_mousedown(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned) { if (button != GUI::MouseButton::Primary || !dom_node().enabled()) @@ -120,4 +108,9 @@ void CheckBox::handle_associated_label_mousemove(Badge<Label>, bool is_inside_no set_needs_display(); } +OwnPtr<Painting::Paintable> CheckBox::create_paintable() const +{ + return Painting::CheckBoxPaintable::create(*this); +} + } diff --git a/Userland/Libraries/LibWeb/Layout/CheckBox.h b/Userland/Libraries/LibWeb/Layout/CheckBox.h index 927398ae82..6de465a725 100644 --- a/Userland/Libraries/LibWeb/Layout/CheckBox.h +++ b/Userland/Libraries/LibWeb/Layout/CheckBox.h @@ -16,11 +16,13 @@ public: CheckBox(DOM::Document&, HTML::HTMLInputElement&, NonnullRefPtr<CSS::StyleProperties>); virtual ~CheckBox() override; - virtual void paint(PaintContext&, Painting::PaintPhase) override; - const HTML::HTMLInputElement& dom_node() const { return static_cast<const HTML::HTMLInputElement&>(LabelableNode::dom_node()); } HTML::HTMLInputElement& dom_node() { return static_cast<HTML::HTMLInputElement&>(LabelableNode::dom_node()); } + bool being_pressed() const { return m_being_pressed; } + + virtual OwnPtr<Painting::Paintable> create_paintable() const override; + private: virtual bool wants_mouse_events() const override { return true; } virtual void handle_mousedown(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers) override; diff --git a/Userland/Libraries/LibWeb/Layout/FormattingState.cpp b/Userland/Libraries/LibWeb/Layout/FormattingState.cpp index 913456f19f..d5078ef030 100644 --- a/Userland/Libraries/LibWeb/Layout/FormattingState.cpp +++ b/Userland/Libraries/LibWeb/Layout/FormattingState.cpp @@ -41,11 +41,7 @@ void FormattingState::commit() // For boxes, transfer all the state needed for painting. if (is<Layout::Box>(node)) { auto& box = static_cast<Layout::Box&>(node); - box.set_paint_box([](Layout::Box const& layout_box) -> OwnPtr<Painting::Paintable> { - if (is<Layout::BlockContainer>(layout_box)) - return Painting::PaintableWithLines::create(static_cast<Layout::BlockContainer const&>(layout_box)); - return Painting::Paintable::create(static_cast<Layout::Box const&>(layout_box)); - }(box)); + box.set_paint_box(box.create_paintable()); box.m_paint_box->set_offset(node_state.offset); box.m_paint_box->set_content_size(node_state.content_width, node_state.content_height); box.m_paint_box->set_overflow_data(move(node_state.overflow_data)); diff --git a/Userland/Libraries/LibWeb/Layout/FrameBox.cpp b/Userland/Libraries/LibWeb/Layout/FrameBox.cpp index 55184564bf..538236fb2e 100644 --- a/Userland/Libraries/LibWeb/Layout/FrameBox.cpp +++ b/Userland/Libraries/LibWeb/Layout/FrameBox.cpp @@ -10,7 +10,7 @@ #include <LibWeb/HTML/BrowsingContext.h> #include <LibWeb/Layout/FrameBox.h> #include <LibWeb/Layout/InitialContainingBlock.h> -#include <LibWeb/Painting/Paintable.h> +#include <LibWeb/Painting/NestedBrowsingContextPaintable.h> namespace Web::Layout { @@ -32,38 +32,6 @@ void FrameBox::prepare_for_replaced_layout() set_intrinsic_height(dom_node().attribute(HTML::AttributeNames::height).to_int().value_or(150)); } -void FrameBox::paint(PaintContext& context, Painting::PaintPhase phase) -{ - ReplacedBox::paint(context, phase); - - if (phase == Painting::PaintPhase::Foreground) { - auto* hosted_document = 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(m_paint_box->absolute_rect())); - context.painter().translate(m_paint_box->absolute_x(), m_paint_box->absolute_y()); - - context.set_viewport_rect({ {}, 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 (dom_node().nested_browsing_context()->is_focused_context()) { - context.painter().draw_rect(m_paint_box->absolute_rect().to_type<int>(), Color::Cyan); - } - } - } -} - void FrameBox::did_set_rect() { ReplacedBox::did_set_rect(); @@ -72,4 +40,9 @@ void FrameBox::did_set_rect() dom_node().nested_browsing_context()->set_size(m_paint_box->content_size().to_type<int>()); } +OwnPtr<Painting::Paintable> FrameBox::create_paintable() const +{ + return Painting::NestedBrowsingContextPaintable::create(*this); +} + } diff --git a/Userland/Libraries/LibWeb/Layout/FrameBox.h b/Userland/Libraries/LibWeb/Layout/FrameBox.h index 43b08d55d1..632b9908cc 100644 --- a/Userland/Libraries/LibWeb/Layout/FrameBox.h +++ b/Userland/Libraries/LibWeb/Layout/FrameBox.h @@ -16,12 +16,13 @@ public: FrameBox(DOM::Document&, DOM::Element&, NonnullRefPtr<CSS::StyleProperties>); virtual ~FrameBox() override; - virtual void paint(PaintContext&, Painting::PaintPhase) override; virtual void prepare_for_replaced_layout() override; const HTML::HTMLIFrameElement& dom_node() const { return verify_cast<HTML::HTMLIFrameElement>(ReplacedBox::dom_node()); } HTML::HTMLIFrameElement& dom_node() { return verify_cast<HTML::HTMLIFrameElement>(ReplacedBox::dom_node()); } + virtual OwnPtr<Painting::Paintable> create_paintable() const override; + private: virtual void did_set_rect() override; }; diff --git a/Userland/Libraries/LibWeb/Layout/ImageBox.cpp b/Userland/Libraries/LibWeb/Layout/ImageBox.cpp index f74b995a02..1c351b64f3 100644 --- a/Userland/Libraries/LibWeb/Layout/ImageBox.cpp +++ b/Userland/Libraries/LibWeb/Layout/ImageBox.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> + * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org> * * SPDX-License-Identifier: BSD-2-Clause */ @@ -11,7 +11,7 @@ #include <LibWeb/CSS/ValueID.h> #include <LibWeb/HTML/BrowsingContext.h> #include <LibWeb/Layout/ImageBox.h> -#include <LibWeb/Painting/Paintable.h> +#include <LibWeb/Painting/ImagePaintable.h> namespace Web::Layout { @@ -72,32 +72,6 @@ void ImageBox::prepare_for_replaced_layout() } } -void ImageBox::paint(PaintContext& context, Painting::PaintPhase phase) -{ - 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(m_paint_box->absolute_rect()))) - return; - - ReplacedBox::paint(context, phase); - - if (phase == Painting::PaintPhase::Foreground) { - if (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(m_paint_box->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(m_paint_box->absolute_rect()), alt, Gfx::TextAlignment::Center, computed_values().color(), Gfx::TextElision::Right); - } else if (auto bitmap = m_image_loader.bitmap(m_image_loader.current_frame_index())) { - context.painter().draw_scaled_bitmap(rounded_int_rect(m_paint_box->absolute_rect()), *bitmap, bitmap->rect(), 1.0f, to_gfx_scaling_mode(computed_values().image_rendering())); - } - } -} - bool ImageBox::renders_as_alt_text() const { if (is<HTML::HTMLImageElement>(dom_node())) @@ -110,4 +84,9 @@ void ImageBox::browsing_context_did_set_viewport_rect(Gfx::IntRect const& viewpo m_image_loader.set_visible_in_viewport(viewport_rect.to_type<float>().intersects(m_paint_box->absolute_rect())); } +OwnPtr<Painting::Paintable> ImageBox::create_paintable() const +{ + return Painting::ImagePaintable::create(*this); +} + } diff --git a/Userland/Libraries/LibWeb/Layout/ImageBox.h b/Userland/Libraries/LibWeb/Layout/ImageBox.h index 1bc4591c9a..85568ab4fc 100644 --- a/Userland/Libraries/LibWeb/Layout/ImageBox.h +++ b/Userland/Libraries/LibWeb/Layout/ImageBox.h @@ -20,12 +20,15 @@ public: virtual ~ImageBox() override; virtual void prepare_for_replaced_layout() override; - virtual void paint(PaintContext&, Painting::PaintPhase) override; const DOM::Element& dom_node() const { return static_cast<const DOM::Element&>(ReplacedBox::dom_node()); } bool renders_as_alt_text() const; + virtual OwnPtr<Painting::Paintable> create_paintable() const override; + + auto const& image_loader() const { return m_image_loader; } + private: // ^BrowsingContext::ViewportClient virtual void browsing_context_did_set_viewport_rect(Gfx::IntRect const&) final; diff --git a/Userland/Libraries/LibWeb/Layout/InlineNode.cpp b/Userland/Libraries/LibWeb/Layout/InlineNode.cpp index 0234d410f1..38391cf211 100644 --- a/Userland/Libraries/LibWeb/Layout/InlineNode.cpp +++ b/Userland/Libraries/LibWeb/Layout/InlineNode.cpp @@ -27,7 +27,7 @@ InlineNode::~InlineNode() { } -void InlineNode::paint(PaintContext& context, Painting::PaintPhase phase) +void InlineNode::paint_inline(PaintContext& context, Painting::PaintPhase phase) const { auto& painter = context.painter(); @@ -126,7 +126,7 @@ void InlineNode::paint(PaintContext& context, Painting::PaintPhase phase) } template<typename Callback> -void InlineNode::for_each_fragment(Callback callback) +void InlineNode::for_each_fragment(Callback callback) const { // FIXME: This will be slow if the containing block has a lot of fragments! Vector<LineBoxFragment const&> fragments; diff --git a/Userland/Libraries/LibWeb/Layout/InlineNode.h b/Userland/Libraries/LibWeb/Layout/InlineNode.h index 898e8c3a5c..6bbba32737 100644 --- a/Userland/Libraries/LibWeb/Layout/InlineNode.h +++ b/Userland/Libraries/LibWeb/Layout/InlineNode.h @@ -15,11 +15,11 @@ public: InlineNode(DOM::Document&, DOM::Element*, NonnullRefPtr<CSS::StyleProperties>); virtual ~InlineNode() override; - virtual void paint(PaintContext&, Painting::PaintPhase) override; + void paint_inline(PaintContext&, Painting::PaintPhase) const; private: template<typename Callback> - void for_each_fragment(Callback); + void for_each_fragment(Callback) const; }; } diff --git a/Userland/Libraries/LibWeb/Layout/ListItemMarkerBox.cpp b/Userland/Libraries/LibWeb/Layout/ListItemMarkerBox.cpp index 73aa694952..672f550cc9 100644 --- a/Userland/Libraries/LibWeb/Layout/ListItemMarkerBox.cpp +++ b/Userland/Libraries/LibWeb/Layout/ListItemMarkerBox.cpp @@ -8,7 +8,7 @@ #include <AK/StringBuilder.h> #include <LibGfx/Painter.h> #include <LibWeb/Layout/ListItemMarkerBox.h> -#include <LibWeb/Painting/Paintable.h> +#include <LibWeb/Painting/MarkerPaintable.h> namespace Web::Layout { @@ -55,62 +55,14 @@ ListItemMarkerBox::~ListItemMarkerBox() { } -void ListItemMarkerBox::paint(PaintContext& context, Painting::PaintPhase phase) +Gfx::Bitmap const* ListItemMarkerBox::list_style_image_bitmap() const { - if (phase != Painting::PaintPhase::Foreground) - return; - - auto enclosing = enclosing_int_rect(m_paint_box->absolute_rect()); - - if (auto const* list_style_image = 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 (m_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 (m_text.is_null()) - break; - context.painter().draw_text(enclosing, m_text, Gfx::TextAlignment::Center); - break; - case CSS::ListStyleType::None: - return; - - default: - VERIFY_NOT_REACHED(); - } + return list_style_image() ? list_style_image()->bitmap() : nullptr; } -Gfx::Bitmap const* ListItemMarkerBox::list_style_image_bitmap() const +OwnPtr<Painting::Paintable> ListItemMarkerBox::create_paintable() const { - return list_style_image() ? list_style_image()->bitmap() : nullptr; + return Painting::MarkerPaintable::create(*this); } } diff --git a/Userland/Libraries/LibWeb/Layout/ListItemMarkerBox.h b/Userland/Libraries/LibWeb/Layout/ListItemMarkerBox.h index 2ba5892802..127f4874dd 100644 --- a/Userland/Libraries/LibWeb/Layout/ListItemMarkerBox.h +++ b/Userland/Libraries/LibWeb/Layout/ListItemMarkerBox.h @@ -16,11 +16,13 @@ public: explicit ListItemMarkerBox(DOM::Document&, CSS::ListStyleType, size_t index, NonnullRefPtr<CSS::StyleProperties>); virtual ~ListItemMarkerBox() override; - virtual void paint(PaintContext&, Painting::PaintPhase) override; - Gfx::Bitmap const* list_style_image_bitmap() const; String const& text() const { return m_text; } + virtual OwnPtr<Painting::Paintable> create_paintable() const override; + + CSS::ListStyleType list_style_type() const { return m_list_style_type; } + private: virtual bool can_have_children() const override { return false; } diff --git a/Userland/Libraries/LibWeb/Layout/Node.h b/Userland/Libraries/LibWeb/Layout/Node.h index 3789cd7a38..78726450f0 100644 --- a/Userland/Libraries/LibWeb/Layout/Node.h +++ b/Userland/Libraries/LibWeb/Layout/Node.h @@ -84,10 +84,7 @@ public: virtual void handle_mousemove(Badge<EventHandler>, const Gfx::IntPoint&, unsigned buttons, unsigned modifiers); virtual bool handle_mousewheel(Badge<EventHandler>, const Gfx::IntPoint&, unsigned buttons, unsigned modifiers, int wheel_delta_x, int wheel_delta_y); - virtual void before_children_paint(PaintContext&, Painting::PaintPhase) {}; - virtual void paint(PaintContext&, Painting::PaintPhase) = 0; virtual void paint_fragment(PaintContext&, const LineBoxFragment&, Painting::PaintPhase) const { } - virtual void after_children_paint(PaintContext&, Painting::PaintPhase) {}; // These are used to optimize hot is<T> variants for some classes where dynamic_cast is too slow. virtual bool is_box() const { return false; } diff --git a/Userland/Libraries/LibWeb/Layout/Progress.cpp b/Userland/Libraries/LibWeb/Layout/Progress.cpp index ec737a49ad..e131fda718 100644 --- a/Userland/Libraries/LibWeb/Layout/Progress.cpp +++ b/Userland/Libraries/LibWeb/Layout/Progress.cpp @@ -7,7 +7,7 @@ #include <LibGfx/Painter.h> #include <LibGfx/StylePainter.h> #include <LibWeb/Layout/Progress.h> -#include <LibWeb/Painting/Paintable.h> +#include <LibWeb/Painting/ProgressPaintable.h> namespace Web::Layout { @@ -21,15 +21,9 @@ Progress::~Progress() { } -void Progress::paint(PaintContext& context, Painting::PaintPhase phase) +OwnPtr<Painting::Paintable> Progress::create_paintable() const { - if (!is_visible()) - return; - - if (phase == Painting::PaintPhase::Foreground) { - // FIXME: This does not support floating point value() and max() - Gfx::StylePainter::paint_progressbar(context.painter(), enclosing_int_rect(m_paint_box->absolute_rect()), context.palette(), 0, dom_node().max(), dom_node().value(), ""); - } + return Painting::ProgressPaintable::create(*this); } } diff --git a/Userland/Libraries/LibWeb/Layout/Progress.h b/Userland/Libraries/LibWeb/Layout/Progress.h index 48398f49df..4431bacc55 100644 --- a/Userland/Libraries/LibWeb/Layout/Progress.h +++ b/Userland/Libraries/LibWeb/Layout/Progress.h @@ -16,10 +16,10 @@ public: Progress(DOM::Document&, HTML::HTMLProgressElement&, NonnullRefPtr<CSS::StyleProperties>); virtual ~Progress() override; - virtual void paint(PaintContext&, Painting::PaintPhase) override; - const HTML::HTMLProgressElement& dom_node() const { return static_cast<const HTML::HTMLProgressElement&>(LabelableNode::dom_node()); } HTML::HTMLProgressElement& dom_node() { return static_cast<HTML::HTMLProgressElement&>(LabelableNode::dom_node()); } + + virtual OwnPtr<Painting::Paintable> create_paintable() const override; }; } diff --git a/Userland/Libraries/LibWeb/Layout/RadioButton.cpp b/Userland/Libraries/LibWeb/Layout/RadioButton.cpp index 36447c2c20..d439063e43 100644 --- a/Userland/Libraries/LibWeb/Layout/RadioButton.cpp +++ b/Userland/Libraries/LibWeb/Layout/RadioButton.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2021, Tim Flynn <trflynn89@serenityos.org> + * Copyright (c) 2022, Andreas Kling <kling@serenityos.org> * * SPDX-License-Identifier: BSD-2-Clause */ @@ -11,7 +12,7 @@ #include <LibWeb/HTML/BrowsingContext.h> #include <LibWeb/Layout/Label.h> #include <LibWeb/Layout/RadioButton.h> -#include <LibWeb/Painting/Paintable.h> +#include <LibWeb/Painting/RadioButtonPaintable.h> namespace Web::Layout { @@ -26,18 +27,6 @@ RadioButton::~RadioButton() { } -void RadioButton::paint(PaintContext& context, Painting::PaintPhase phase) -{ - if (!is_visible()) - return; - - LabelableNode::paint(context, phase); - - if (phase == Painting::PaintPhase::Foreground) { - Gfx::StylePainter::paint_radio_button(context.painter(), enclosing_int_rect(m_paint_box->absolute_rect()), context.palette(), dom_node().checked(), m_being_pressed); - } -} - void RadioButton::handle_mousedown(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned) { if (button != GUI::MouseButton::Primary || !dom_node().enabled()) @@ -125,4 +114,9 @@ void RadioButton::set_checked_within_group() }); } +OwnPtr<Painting::Paintable> RadioButton::create_paintable() const +{ + return Painting::RadioButtonPaintable::create(*this); +} + } diff --git a/Userland/Libraries/LibWeb/Layout/RadioButton.h b/Userland/Libraries/LibWeb/Layout/RadioButton.h index 7d8fce50a8..ec5b3563f2 100644 --- a/Userland/Libraries/LibWeb/Layout/RadioButton.h +++ b/Userland/Libraries/LibWeb/Layout/RadioButton.h @@ -11,16 +11,18 @@ namespace Web::Layout { -class RadioButton : public LabelableNode { +class RadioButton final : public LabelableNode { public: RadioButton(DOM::Document&, HTML::HTMLInputElement&, NonnullRefPtr<CSS::StyleProperties>); virtual ~RadioButton() override; - virtual void paint(PaintContext&, Painting::PaintPhase) override; - const HTML::HTMLInputElement& dom_node() const { return static_cast<const HTML::HTMLInputElement&>(LabelableNode::dom_node()); } HTML::HTMLInputElement& dom_node() { return static_cast<HTML::HTMLInputElement&>(LabelableNode::dom_node()); } + virtual OwnPtr<Painting::Paintable> create_paintable() const override; + + bool being_pressed() const { return m_being_pressed; } + private: virtual bool wants_mouse_events() const override { return true; } virtual void handle_mousedown(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers) override; diff --git a/Userland/Libraries/LibWeb/Layout/SVGBox.cpp b/Userland/Libraries/LibWeb/Layout/SVGBox.cpp index 9c0c7a8299..5f30689bce 100644 --- a/Userland/Libraries/LibWeb/Layout/SVGBox.cpp +++ b/Userland/Libraries/LibWeb/Layout/SVGBox.cpp @@ -15,20 +15,4 @@ SVGBox::SVGBox(DOM::Document& document, SVG::SVGElement& element, NonnullRefPtr< { } -void SVGBox::before_children_paint(PaintContext& context, Painting::PaintPhase phase) -{ - Node::before_children_paint(context, phase); - if (phase != Painting::PaintPhase::Foreground) - return; - context.svg_context().save(); -} - -void SVGBox::after_children_paint(PaintContext& context, Painting::PaintPhase phase) -{ - Node::after_children_paint(context, phase); - if (phase != Painting::PaintPhase::Foreground) - return; - context.svg_context().restore(); -} - } diff --git a/Userland/Libraries/LibWeb/Layout/SVGBox.h b/Userland/Libraries/LibWeb/Layout/SVGBox.h index 998be6dd4a..ff38f9c3bc 100644 --- a/Userland/Libraries/LibWeb/Layout/SVGBox.h +++ b/Userland/Libraries/LibWeb/Layout/SVGBox.h @@ -20,9 +20,6 @@ public: SVG::SVGElement& dom_node() { return verify_cast<SVG::SVGElement>(*Box::dom_node()); } SVG::SVGElement const& dom_node() const { return verify_cast<SVG::SVGElement>(*Box::dom_node()); } - virtual void before_children_paint(PaintContext& context, Painting::PaintPhase phase) override; - virtual void after_children_paint(PaintContext& context, Painting::PaintPhase phase) override; - private: virtual bool is_svg_box() const final { return true; } }; diff --git a/Userland/Libraries/LibWeb/Layout/SVGGeometryBox.cpp b/Userland/Libraries/LibWeb/Layout/SVGGeometryBox.cpp index c3166b82bc..2b992b4431 100644 --- a/Userland/Libraries/LibWeb/Layout/SVGGeometryBox.cpp +++ b/Userland/Libraries/LibWeb/Layout/SVGGeometryBox.cpp @@ -19,99 +19,6 @@ SVGGeometryBox::SVGGeometryBox(DOM::Document& document, SVG::SVGGeometryElement& { } -void SVGGeometryBox::paint(PaintContext& context, Painting::PaintPhase phase) -{ - if (!is_visible()) - return; - - SVGGraphicsBox::paint(context, phase); - - if (phase != Painting::PaintPhase::Foreground) - return; - - auto& geometry_element = dom_node(); - - Gfx::AntiAliasingPainter painter { context.painter() }; - auto& svg_context = context.svg_context(); - - auto offset = svg_context.svg_element_position(); - painter.translate(offset); - - SVG::SVGSVGElement* 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(m_paint_box->absolute_rect())); - - Gfx::Path path = geometry_element.get_path(); - - if (maybe_view_box.has_value()) { - Gfx::Path new_path; - auto scaling = viewbox_scaling(); - auto origin = 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(); -} - float SVGGeometryBox::viewbox_scaling() const { auto* svg_box = dom_node().first_ancestor_of_type<SVG::SVGSVGElement>(); diff --git a/Userland/Libraries/LibWeb/Layout/SVGGeometryBox.h b/Userland/Libraries/LibWeb/Layout/SVGGeometryBox.h index 234b8ccb27..4a073ee042 100644 --- a/Userland/Libraries/LibWeb/Layout/SVGGeometryBox.h +++ b/Userland/Libraries/LibWeb/Layout/SVGGeometryBox.h @@ -19,8 +19,6 @@ public: SVG::SVGGeometryElement& dom_node() { return verify_cast<SVG::SVGGeometryElement>(SVGGraphicsBox::dom_node()); } SVG::SVGGeometryElement const& dom_node() const { return verify_cast<SVG::SVGGeometryElement>(SVGGraphicsBox::dom_node()); } - virtual void paint(PaintContext& context, Painting::PaintPhase phase) override; - float viewbox_scaling() const; Gfx::FloatPoint viewbox_origin() const; diff --git a/Userland/Libraries/LibWeb/Layout/SVGGraphicsBox.cpp b/Userland/Libraries/LibWeb/Layout/SVGGraphicsBox.cpp index ae0f4b421b..723fca2fb8 100644 --- a/Userland/Libraries/LibWeb/Layout/SVGGraphicsBox.cpp +++ b/Userland/Libraries/LibWeb/Layout/SVGGraphicsBox.cpp @@ -14,20 +14,4 @@ SVGGraphicsBox::SVGGraphicsBox(DOM::Document& document, SVG::SVGGraphicsElement& { } -void SVGGraphicsBox::before_children_paint(PaintContext& context, Painting::PaintPhase phase) -{ - SVGBox::before_children_paint(context, phase); - if (phase != Painting::PaintPhase::Foreground) - return; - - auto& graphics_element = verify_cast<SVG::SVGGraphicsElement>(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/Layout/SVGGraphicsBox.h b/Userland/Libraries/LibWeb/Layout/SVGGraphicsBox.h index 928a85d20f..efa4333239 100644 --- a/Userland/Libraries/LibWeb/Layout/SVGGraphicsBox.h +++ b/Userland/Libraries/LibWeb/Layout/SVGGraphicsBox.h @@ -19,8 +19,6 @@ public: SVG::SVGGraphicsElement& dom_node() { return verify_cast<SVG::SVGGraphicsElement>(SVGBox::dom_node()); } SVG::SVGGraphicsElement const& dom_node() const { return verify_cast<SVG::SVGGraphicsElement>(SVGBox::dom_node()); } - - virtual void before_children_paint(PaintContext& context, Painting::PaintPhase phase) override; }; } diff --git a/Userland/Libraries/LibWeb/Layout/SVGSVGBox.cpp b/Userland/Libraries/LibWeb/Layout/SVGSVGBox.cpp index 599be692f9..1179e5bbb4 100644 --- a/Userland/Libraries/LibWeb/Layout/SVGSVGBox.cpp +++ b/Userland/Libraries/LibWeb/Layout/SVGSVGBox.cpp @@ -14,23 +14,4 @@ SVGSVGBox::SVGSVGBox(DOM::Document& document, SVG::SVGSVGElement& element, Nonnu { } -void SVGSVGBox::before_children_paint(PaintContext& context, Painting::PaintPhase phase) -{ - if (phase != Painting::PaintPhase::Foreground) - return; - - if (!context.has_svg_context()) - context.set_svg_context(SVGContext(m_paint_box->absolute_rect())); - - SVGGraphicsBox::before_children_paint(context, phase); -} - -void SVGSVGBox::after_children_paint(PaintContext& context, Painting::PaintPhase phase) -{ - SVGGraphicsBox::after_children_paint(context, phase); - if (phase != Painting::PaintPhase::Foreground) - return; - context.clear_svg_context(); -} - } diff --git a/Userland/Libraries/LibWeb/Layout/SVGSVGBox.h b/Userland/Libraries/LibWeb/Layout/SVGSVGBox.h index b02e595186..4501ab5f14 100644 --- a/Userland/Libraries/LibWeb/Layout/SVGSVGBox.h +++ b/Userland/Libraries/LibWeb/Layout/SVGSVGBox.h @@ -18,9 +18,6 @@ public: SVG::SVGSVGElement& dom_node() { return verify_cast<SVG::SVGSVGElement>(SVGGraphicsBox::dom_node()); } - virtual void before_children_paint(PaintContext& context, Painting::PaintPhase phase) override; - virtual void after_children_paint(PaintContext& context, Painting::PaintPhase phase) override; - virtual bool can_have_children() const override { return true; } }; diff --git a/Userland/Libraries/LibWeb/Layout/TextNode.cpp b/Userland/Libraries/LibWeb/Layout/TextNode.cpp index 90772aa773..2eb20df53b 100644 --- a/Userland/Libraries/LibWeb/Layout/TextNode.cpp +++ b/Userland/Libraries/LibWeb/Layout/TextNode.cpp @@ -366,8 +366,4 @@ Optional<TextNode::Chunk> TextNode::ChunkIterator::try_commit_chunk(Utf8View::It return {}; } -void TextNode::paint(PaintContext&, Painting::PaintPhase) -{ -} - } diff --git a/Userland/Libraries/LibWeb/Layout/TextNode.h b/Userland/Libraries/LibWeb/Layout/TextNode.h index 3cc072a184..384cd8fbe1 100644 --- a/Userland/Libraries/LibWeb/Layout/TextNode.h +++ b/Userland/Libraries/LibWeb/Layout/TextNode.h @@ -60,7 +60,6 @@ private: virtual void handle_mousemove(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers) override; void paint_cursor_if_needed(PaintContext&, const LineBoxFragment&) const; void paint_text_decoration(Gfx::Painter&, LineBoxFragment const&) const; - virtual void paint(PaintContext&, Painting::PaintPhase) override; String m_text_for_rendering; }; 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); } |