mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-21 12:05:15 +00:00
LibWeb: Separate layout tree rendering into phases
CSS defines a very specific paint order. This patch starts steering us towards respecting that by introducing the PaintPhase enum with values: - Background - Border - Foreground - Overlay (internal overlays used by inspector) Basically, to get the right visual result, we have to render the page multiple times, going one phase at a time.
This commit is contained in:
parent
abe811104f
commit
cfab53903f
Notes:
sideshowbarker
2024-07-19 05:34:13 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/cfab53903f0
22 changed files with 109 additions and 95 deletions
|
@ -600,19 +600,22 @@ void LayoutBlock::compute_height()
|
|||
}
|
||||
}
|
||||
|
||||
void LayoutBlock::render(RenderingContext& context)
|
||||
void LayoutBlock::render(RenderingContext& context, PaintPhase phase)
|
||||
{
|
||||
if (!is_visible())
|
||||
return;
|
||||
|
||||
LayoutBox::render(context);
|
||||
LayoutBox::render(context, phase);
|
||||
|
||||
if (children_are_inline()) {
|
||||
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);
|
||||
fragment.render(context);
|
||||
// FIXME: Inline backgrounds etc.
|
||||
if (phase == PaintPhase::Foreground) {
|
||||
if (children_are_inline()) {
|
||||
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);
|
||||
fragment.render(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ public:
|
|||
virtual const char* class_name() const override { return "LayoutBlock"; }
|
||||
|
||||
virtual void layout(LayoutMode = LayoutMode::Default) override;
|
||||
virtual void render(RenderingContext&) override;
|
||||
virtual void render(RenderingContext&, PaintPhase) override;
|
||||
|
||||
virtual LayoutNode& inline_wrapper() override;
|
||||
|
||||
|
|
|
@ -31,9 +31,6 @@
|
|||
#include <LibWeb/Layout/LayoutBlock.h>
|
||||
#include <LibWeb/Layout/LayoutBox.h>
|
||||
|
||||
//#define DRAW_BOXES_AROUND_LAYOUT_NODES
|
||||
//#define DRAW_BOXES_AROUND_HOVERED_NODES
|
||||
|
||||
namespace Web {
|
||||
|
||||
void LayoutBox::paint_border(RenderingContext& context, Edge edge, const Gfx::FloatRect& rect, CSS::PropertyID style_property_id, CSS::PropertyID color_property_id, CSS::PropertyID width_property_id)
|
||||
|
@ -189,7 +186,7 @@ void LayoutBox::paint_border(RenderingContext& context, Edge edge, const Gfx::Fl
|
|||
}
|
||||
}
|
||||
|
||||
void LayoutBox::render(RenderingContext& context)
|
||||
void LayoutBox::render(RenderingContext& context, PaintPhase phase)
|
||||
{
|
||||
if (!is_visible())
|
||||
return;
|
||||
|
@ -198,21 +195,14 @@ void LayoutBox::render(RenderingContext& context)
|
|||
if (is_fixed_position())
|
||||
context.painter().translate(context.scroll_offset());
|
||||
|
||||
#ifdef DRAW_BOXES_AROUND_LAYOUT_NODES
|
||||
context.painter().draw_rect(m_rect, Color::Blue);
|
||||
#endif
|
||||
#ifdef DRAW_BOXES_AROUND_HOVERED_NODES
|
||||
if (!is_anonymous() && node() == document().hovered_node())
|
||||
context.painter().draw_rect(m_rect, Color::Red);
|
||||
#endif
|
||||
|
||||
Gfx::FloatRect padded_rect;
|
||||
padded_rect.set_x(absolute_x() - box_model().padding().left.to_px(*this));
|
||||
padded_rect.set_width(width() + box_model().padding().left.to_px(*this) + box_model().padding().right.to_px(*this));
|
||||
padded_rect.set_y(absolute_y() - box_model().padding().top.to_px(*this));
|
||||
padded_rect.set_height(height() + box_model().padding().top.to_px(*this) + box_model().padding().bottom.to_px(*this));
|
||||
|
||||
if (!is_body()) {
|
||||
if (phase == PaintPhase::Background && !is_body()) {
|
||||
// FIXME: We should paint the body here too, but that currently happens at the view layer.
|
||||
auto bgcolor = style().property(CSS::PropertyID::BackgroundColor);
|
||||
if (bgcolor.has_value() && bgcolor.value()->is_color()) {
|
||||
context.painter().fill_rect(enclosing_int_rect(padded_rect), bgcolor.value()->to_color(document()));
|
||||
|
@ -227,20 +217,22 @@ void LayoutBox::render(RenderingContext& context)
|
|||
}
|
||||
}
|
||||
|
||||
Gfx::FloatRect bordered_rect;
|
||||
bordered_rect.set_x(padded_rect.x() - box_model().border().left.to_px(*this));
|
||||
bordered_rect.set_width(padded_rect.width() + box_model().border().left.to_px(*this) + box_model().border().right.to_px(*this));
|
||||
bordered_rect.set_y(padded_rect.y() - box_model().border().top.to_px(*this));
|
||||
bordered_rect.set_height(padded_rect.height() + box_model().border().top.to_px(*this) + box_model().border().bottom.to_px(*this));
|
||||
if (phase == PaintPhase::Border) {
|
||||
Gfx::FloatRect bordered_rect;
|
||||
bordered_rect.set_x(padded_rect.x() - box_model().border().left.to_px(*this));
|
||||
bordered_rect.set_width(padded_rect.width() + box_model().border().left.to_px(*this) + box_model().border().right.to_px(*this));
|
||||
bordered_rect.set_y(padded_rect.y() - box_model().border().top.to_px(*this));
|
||||
bordered_rect.set_height(padded_rect.height() + box_model().border().top.to_px(*this) + box_model().border().bottom.to_px(*this));
|
||||
|
||||
paint_border(context, Edge::Left, bordered_rect, CSS::PropertyID::BorderLeftStyle, CSS::PropertyID::BorderLeftColor, CSS::PropertyID::BorderLeftWidth);
|
||||
paint_border(context, Edge::Right, bordered_rect, CSS::PropertyID::BorderRightStyle, CSS::PropertyID::BorderRightColor, CSS::PropertyID::BorderRightWidth);
|
||||
paint_border(context, Edge::Top, bordered_rect, CSS::PropertyID::BorderTopStyle, CSS::PropertyID::BorderTopColor, CSS::PropertyID::BorderTopWidth);
|
||||
paint_border(context, Edge::Bottom, bordered_rect, CSS::PropertyID::BorderBottomStyle, CSS::PropertyID::BorderBottomColor, CSS::PropertyID::BorderBottomWidth);
|
||||
paint_border(context, Edge::Left, bordered_rect, CSS::PropertyID::BorderLeftStyle, CSS::PropertyID::BorderLeftColor, CSS::PropertyID::BorderLeftWidth);
|
||||
paint_border(context, Edge::Right, bordered_rect, CSS::PropertyID::BorderRightStyle, CSS::PropertyID::BorderRightColor, CSS::PropertyID::BorderRightWidth);
|
||||
paint_border(context, Edge::Top, bordered_rect, CSS::PropertyID::BorderTopStyle, CSS::PropertyID::BorderTopColor, CSS::PropertyID::BorderTopWidth);
|
||||
paint_border(context, Edge::Bottom, bordered_rect, CSS::PropertyID::BorderBottomStyle, CSS::PropertyID::BorderBottomColor, CSS::PropertyID::BorderBottomWidth);
|
||||
}
|
||||
|
||||
LayoutNodeWithStyleAndBoxModelMetrics::render(context);
|
||||
LayoutNodeWithStyleAndBoxModelMetrics::render(context, phase);
|
||||
|
||||
if (node() && document().inspected_node() == node())
|
||||
if (phase == PaintPhase::Overlay && node() && document().inspected_node() == node())
|
||||
context.painter().draw_rect(enclosing_int_rect(absolute_rect()), Color::Magenta);
|
||||
}
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ public:
|
|||
void set_stacking_context(NonnullOwnPtr<StackingContext> context) { m_stacking_context = move(context); }
|
||||
StackingContext* enclosing_stacking_context();
|
||||
|
||||
virtual void render(RenderingContext&) override;
|
||||
virtual void render(RenderingContext&, PaintPhase) override;
|
||||
|
||||
protected:
|
||||
LayoutBox(const Node* node, NonnullRefPtr<StyleProperties> style)
|
||||
|
|
|
@ -49,18 +49,21 @@ void LayoutCanvas::layout(LayoutMode layout_mode)
|
|||
LayoutReplaced::layout(layout_mode);
|
||||
}
|
||||
|
||||
void LayoutCanvas::render(RenderingContext& context)
|
||||
void LayoutCanvas::render(RenderingContext& context, 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(absolute_rect())))
|
||||
return;
|
||||
LayoutReplaced::render(context, phase);
|
||||
|
||||
if (node().bitmap())
|
||||
context.painter().draw_scaled_bitmap(enclosing_int_rect(absolute_rect()), *node().bitmap(), node().bitmap()->rect());
|
||||
LayoutReplaced::render(context);
|
||||
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 (node().bitmap())
|
||||
context.painter().draw_scaled_bitmap(enclosing_int_rect(absolute_rect()), *node().bitmap(), node().bitmap()->rect());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ public:
|
|||
virtual ~LayoutCanvas() override;
|
||||
|
||||
virtual void layout(LayoutMode = LayoutMode::Default) override;
|
||||
virtual void render(RenderingContext&) override;
|
||||
virtual void render(RenderingContext&, PaintPhase) override;
|
||||
|
||||
const HTMLCanvasElement& node() const { return static_cast<const HTMLCanvasElement&>(LayoutReplaced::node()); }
|
||||
|
||||
|
|
|
@ -100,9 +100,17 @@ void LayoutDocument::did_set_viewport_rect(Badge<Frame>, const Gfx::IntRect& a_v
|
|||
});
|
||||
}
|
||||
|
||||
void LayoutDocument::render(RenderingContext& context)
|
||||
void LayoutDocument::paint_all_phases(RenderingContext& context)
|
||||
{
|
||||
stacking_context()->render(context);
|
||||
render(context, PaintPhase::Background);
|
||||
render(context, PaintPhase::Border);
|
||||
render(context, PaintPhase::Foreground);
|
||||
render(context, PaintPhase::Overlay);
|
||||
}
|
||||
|
||||
void LayoutDocument::render(RenderingContext& context, PaintPhase phase)
|
||||
{
|
||||
stacking_context()->render(context, phase);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -40,7 +40,9 @@ public:
|
|||
virtual const char* class_name() const override { return "LayoutDocument"; }
|
||||
virtual void layout(LayoutMode = LayoutMode::Default) override;
|
||||
|
||||
virtual void render(RenderingContext&) override;
|
||||
void paint_all_phases(RenderingContext&);
|
||||
|
||||
virtual void render(RenderingContext&, PaintPhase) override;
|
||||
|
||||
const LayoutRange& selection() const { return m_selection; }
|
||||
LayoutRange& selection() { return m_selection; }
|
||||
|
|
|
@ -59,28 +59,30 @@ void LayoutFrame::layout(LayoutMode layout_mode)
|
|||
LayoutReplaced::layout(layout_mode);
|
||||
}
|
||||
|
||||
void LayoutFrame::render(RenderingContext& context)
|
||||
void LayoutFrame::render(RenderingContext& context, PaintPhase phase)
|
||||
{
|
||||
LayoutReplaced::render(context);
|
||||
LayoutReplaced::render(context, phase);
|
||||
|
||||
auto* hosted_document = node().hosted_document();
|
||||
if (!hosted_document)
|
||||
return;
|
||||
auto* hosted_layout_tree = hosted_document->layout_node();
|
||||
if (!hosted_layout_tree)
|
||||
return;
|
||||
if (phase == PaintPhase::Foreground) {
|
||||
auto* hosted_document = node().hosted_document();
|
||||
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().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.painter().add_clip_rect(enclosing_int_rect(absolute_rect()));
|
||||
context.painter().translate(absolute_x(), absolute_y());
|
||||
|
||||
context.set_viewport_rect({ {}, node().hosted_frame()->size() });
|
||||
const_cast<LayoutDocument*>(hosted_layout_tree)->render(context);
|
||||
context.set_viewport_rect({ {}, node().hosted_frame()->size() });
|
||||
const_cast<LayoutDocument*>(hosted_layout_tree)->paint_all_phases(context);
|
||||
|
||||
context.set_viewport_rect(old_viewport_rect);
|
||||
context.painter().restore();
|
||||
context.set_viewport_rect(old_viewport_rect);
|
||||
context.painter().restore();
|
||||
}
|
||||
}
|
||||
|
||||
void LayoutFrame::did_set_rect()
|
||||
|
|
|
@ -36,7 +36,7 @@ public:
|
|||
LayoutFrame(const Element&, NonnullRefPtr<StyleProperties>);
|
||||
virtual ~LayoutFrame() override;
|
||||
|
||||
virtual void render(RenderingContext&) override;
|
||||
virtual void render(RenderingContext&, PaintPhase) override;
|
||||
virtual void layout(LayoutMode) override;
|
||||
|
||||
const HTMLIFrameElement& node() const { return static_cast<const HTMLIFrameElement&>(LayoutReplaced::node()); }
|
||||
|
|
|
@ -77,7 +77,7 @@ void LayoutImage::layout(LayoutMode layout_mode)
|
|||
LayoutReplaced::layout(layout_mode);
|
||||
}
|
||||
|
||||
void LayoutImage::render(RenderingContext& context)
|
||||
void LayoutImage::render(RenderingContext& context, PaintPhase phase)
|
||||
{
|
||||
if (!is_visible())
|
||||
return;
|
||||
|
@ -86,18 +86,20 @@ void LayoutImage::render(RenderingContext& context)
|
|||
if (!context.viewport_rect().intersects(enclosing_int_rect(absolute_rect())))
|
||||
return;
|
||||
|
||||
LayoutReplaced::render(context);
|
||||
LayoutReplaced::render(context, phase);
|
||||
|
||||
if (renders_as_alt_text()) {
|
||||
auto& image_element = to<HTMLImageElement>(node());
|
||||
context.painter().set_font(Gfx::Font::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, style().color_or_fallback(CSS::PropertyID::Color, document(), Color::Black), Gfx::TextElision::Right);
|
||||
} else if (m_image_loader.bitmap()) {
|
||||
context.painter().draw_scaled_bitmap(enclosing_int_rect(absolute_rect()), *m_image_loader.bitmap(), m_image_loader.bitmap()->rect());
|
||||
if (phase == PaintPhase::Foreground) {
|
||||
if (renders_as_alt_text()) {
|
||||
auto& image_element = to<HTMLImageElement>(node());
|
||||
context.painter().set_font(Gfx::Font::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, style().color_or_fallback(CSS::PropertyID::Color, document(), Color::Black), Gfx::TextElision::Right);
|
||||
} else if (m_image_loader.bitmap()) {
|
||||
context.painter().draw_scaled_bitmap(enclosing_int_rect(absolute_rect()), *m_image_loader.bitmap(), m_image_loader.bitmap()->rect());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ public:
|
|||
virtual ~LayoutImage() override;
|
||||
|
||||
virtual void layout(LayoutMode = LayoutMode::Default) override;
|
||||
virtual void render(RenderingContext&) override;
|
||||
virtual void render(RenderingContext&, PaintPhase) override;
|
||||
|
||||
const Element& node() const { return static_cast<const Element&>(LayoutReplaced::node()); }
|
||||
|
||||
|
|
|
@ -38,8 +38,10 @@ LayoutListItemMarker::~LayoutListItemMarker()
|
|||
{
|
||||
}
|
||||
|
||||
void LayoutListItemMarker::render(RenderingContext& context)
|
||||
void LayoutListItemMarker::render(RenderingContext& context, PaintPhase phase)
|
||||
{
|
||||
if (phase != PaintPhase::Foreground)
|
||||
return;
|
||||
Gfx::IntRect bullet_rect { 0, 0, 4, 4 };
|
||||
bullet_rect.center_within(enclosing_int_rect(absolute_rect()));
|
||||
// FIXME: It would be nicer to not have to go via the parent here to get our inherited style.
|
||||
|
|
|
@ -35,7 +35,7 @@ public:
|
|||
LayoutListItemMarker();
|
||||
virtual ~LayoutListItemMarker() override;
|
||||
|
||||
virtual void render(RenderingContext&) override;
|
||||
virtual void render(RenderingContext&, PaintPhase) override;
|
||||
|
||||
private:
|
||||
virtual const char* class_name() const override { return "LayoutListItemMarker"; }
|
||||
|
|
|
@ -89,7 +89,7 @@ const LayoutBlock* LayoutNode::containing_block() const
|
|||
return nearest_block_ancestor();
|
||||
}
|
||||
|
||||
void LayoutNode::render(RenderingContext& context)
|
||||
void LayoutNode::render(RenderingContext& context, PaintPhase phase)
|
||||
{
|
||||
if (!is_visible())
|
||||
return;
|
||||
|
@ -97,7 +97,7 @@ void LayoutNode::render(RenderingContext& context)
|
|||
for_each_child([&](auto& child) {
|
||||
if (child.is_box() && to<LayoutBox>(child).stacking_context())
|
||||
return;
|
||||
child.render(context);
|
||||
child.render(context, phase);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -171,7 +171,14 @@ public:
|
|||
};
|
||||
|
||||
virtual void layout(LayoutMode);
|
||||
virtual void render(RenderingContext&);
|
||||
|
||||
enum class PaintPhase {
|
||||
Background,
|
||||
Border,
|
||||
Foreground,
|
||||
Overlay,
|
||||
};
|
||||
virtual void render(RenderingContext&, PaintPhase);
|
||||
|
||||
bool is_absolutely_positioned() const;
|
||||
bool is_fixed_position() const;
|
||||
|
|
|
@ -65,9 +65,4 @@ void LayoutWidget::update_widget()
|
|||
widget().move_to(adjusted_widget_position);
|
||||
}
|
||||
|
||||
void LayoutWidget::render(RenderingContext& context)
|
||||
{
|
||||
LayoutReplaced::render(context);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -35,8 +35,6 @@ public:
|
|||
LayoutWidget(const Element&, GUI::Widget&);
|
||||
virtual ~LayoutWidget() override;
|
||||
|
||||
virtual void render(RenderingContext&) override;
|
||||
|
||||
GUI::Widget& widget() { return m_widget; }
|
||||
const GUI::Widget& widget() const { return m_widget; }
|
||||
|
||||
|
|
|
@ -47,17 +47,17 @@ StackingContext::StackingContext(LayoutBox& box, StackingContext* parent)
|
|||
}
|
||||
}
|
||||
|
||||
void StackingContext::render(RenderingContext& context)
|
||||
void StackingContext::render(RenderingContext& context, LayoutNode::PaintPhase phase)
|
||||
{
|
||||
if (!m_box.is_root()) {
|
||||
m_box.render(context);
|
||||
m_box.render(context, phase);
|
||||
} else {
|
||||
// NOTE: LayoutDocument::render() merely calls StackingContext::render()
|
||||
// so we call its base class instead.
|
||||
to<LayoutDocument>(m_box).LayoutBlock::render(context);
|
||||
to<LayoutDocument>(m_box).LayoutBlock::render(context, phase);
|
||||
}
|
||||
for (auto* child : m_children) {
|
||||
child->render(context);
|
||||
child->render(context, phase);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/Vector.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
#include <LibWeb/Layout/LayoutNode.h>
|
||||
|
||||
namespace Web {
|
||||
|
||||
|
@ -40,7 +40,7 @@ public:
|
|||
StackingContext* parent() { return m_parent; }
|
||||
const StackingContext* parent() const { return m_parent; }
|
||||
|
||||
void render(RenderingContext&);
|
||||
void render(RenderingContext&, LayoutNode::PaintPhase);
|
||||
|
||||
void dump(int indent = 0) const;
|
||||
|
||||
|
|
|
@ -210,7 +210,7 @@ void PageView::paint_event(GUI::PaintEvent& event)
|
|||
RenderingContext context(painter, palette(), { horizontal_scrollbar().value(), vertical_scrollbar().value() });
|
||||
context.set_should_show_line_box_borders(m_should_show_line_box_borders);
|
||||
context.set_viewport_rect(viewport_rect_in_content_coordinates());
|
||||
layout_root()->render(context);
|
||||
layout_root()->paint_all_phases(context);
|
||||
}
|
||||
|
||||
void PageView::mousemove_event(GUI::MouseEvent& event)
|
||||
|
|
|
@ -88,7 +88,7 @@ void PageHost::paint(const Gfx::IntRect& content_rect, Gfx::Bitmap& target)
|
|||
|
||||
Web::RenderingContext context(painter, palette(), Gfx::IntPoint());
|
||||
context.set_viewport_rect(content_rect);
|
||||
layout_root->render(context);
|
||||
layout_root->paint_all_phases(context);
|
||||
}
|
||||
|
||||
void PageHost::set_viewport_rect(const Gfx::IntRect& rect)
|
||||
|
|
Loading…
Add table
Reference in a new issue