1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 05:37:43 +00:00

LibWeb: Add Painting::Box and move things from Layout::Box into it

The "paintable" state in Layout::Box was actually not safe to access
until after layout had been performed.

As a first step towards making this harder to mess up accidentally,
this patch moves painting information from Layout::Box to a new class:
Painting::Box. Every layout can have a corresponding paint box, and
it holds the final used metrics determined by layout.

The paint box is created and populated by FormattingState::commit().

I've also added DOM::Node::paint_box() as a convenient way to access
the paint box (if available) of a given DOM node.

Going forward, I believe this will allow us to better separate data
that belongs to layout vs painting, and also open up opportunities
for naturally invalidating caches in the paint box (since it's
reconstituted by every layout.)
This commit is contained in:
Andreas Kling 2022-03-09 23:53:41 +01:00
parent db404af6df
commit a4d51b3dc2
35 changed files with 365 additions and 293 deletions

View file

@ -284,6 +284,7 @@ set(SOURCES
Page/Page.cpp
Painting/BackgroundPainting.cpp
Painting/BorderPainting.cpp
Painting/Box.cpp
Painting/PaintContext.cpp
Painting/ShadowPainting.cpp
Painting/StackingContext.cpp

View file

@ -35,6 +35,7 @@
#include <LibWeb/Layout/TableRowGroupBox.h>
#include <LibWeb/Layout/TreeBuilder.h>
#include <LibWeb/Namespace.h>
#include <LibWeb/Painting/Box.h>
namespace Web::DOM {
@ -436,7 +437,7 @@ NonnullRefPtr<Geometry::DOMRect> Element::get_bounding_client_rect() const
auto viewport_offset = document().browsing_context()->viewport_scroll_offset();
auto& box = static_cast<Layout::Box const&>(*layout_node());
return Geometry::DOMRect::create(box.absolute_rect().translated(-viewport_offset.x(), -viewport_offset.y()));
return Geometry::DOMRect::create(box.m_paint_box->absolute_rect().translated(-viewport_offset.x(), -viewport_offset.y()));
}
// https://drafts.csswg.org/cssom-view/#dom-element-getclientrects
@ -465,34 +466,30 @@ NonnullRefPtr<Geometry::DOMRectList> Element::get_client_rects() const
int Element::client_top() const
{
if (!layout_node() || !layout_node()->is_box())
return 0;
auto& box = static_cast<Layout::Box const&>(*layout_node());
return box.absolute_rect().top();
if (auto* paint_box = this->paint_box())
return paint_box->absolute_rect().top();
return 0;
}
int Element::client_left() const
{
if (!layout_node() || !layout_node()->is_box())
return 0;
auto& box = static_cast<Layout::Box const&>(*layout_node());
return box.absolute_rect().left();
if (auto* paint_box = this->paint_box())
return paint_box->absolute_rect().left();
return 0;
}
int Element::client_width() const
{
if (!layout_node() || !layout_node()->is_box())
return 0;
auto& box = static_cast<Layout::Box const&>(*layout_node());
return box.absolute_rect().width();
if (auto* paint_box = this->paint_box())
return paint_box->absolute_rect().width();
return 0;
}
int Element::client_height() const
{
if (!layout_node() || !layout_node()->is_box())
return 0;
auto& box = static_cast<Layout::Box const&>(*layout_node());
return box.absolute_rect().height();
if (auto* paint_box = this->paint_box())
return paint_box->absolute_rect().height();
return 0;
}
void Element::children_changed()

View file

@ -1010,4 +1010,13 @@ size_t Node::length() const
return child_count();
}
Painting::Box const* Node::paint_box() const
{
if (!layout_node())
return nullptr;
if (!layout_node()->is_box())
return nullptr;
return static_cast<Layout::Box const&>(*layout_node()).m_paint_box;
}
}

View file

@ -158,6 +158,8 @@ public:
const Layout::Node* layout_node() const { return m_layout_node; }
Layout::Node* layout_node() { return m_layout_node; }
Painting::Box const* paint_box() const;
void set_layout_node(Badge<Layout::Node>, Layout::Node*) const;
virtual bool is_child_allowed(const Node&) const { return true; }

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2021, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
@ -26,6 +26,7 @@
#include <LibWeb/Layout/Node.h>
#include <LibWeb/Layout/SVGBox.h>
#include <LibWeb/Layout/TextNode.h>
#include <LibWeb/Painting/Box.h>
#include <stdio.h>
namespace Web {
@ -166,10 +167,10 @@ void dump_tree(StringBuilder& builder, Layout::Node const& layout_node, bool sho
builder.appendff("@{:p} ", &layout_node);
builder.appendff("at ({},{}) content-size {}x{}",
box.absolute_x(),
box.absolute_y(),
box.content_width(),
box.content_height());
box.m_paint_box->absolute_x(),
box.m_paint_box->absolute_y(),
box.m_paint_box->content_width(),
box.m_paint_box->content_height());
if (box.is_positioned())
builder.appendff(" {}positioned{}", positioned_color_on, color_off);
@ -204,7 +205,7 @@ void dump_tree(StringBuilder& builder, Layout::Node const& layout_node, bool sho
box.box_model().margin.left,
box.box_model().border.left,
box.box_model().padding.left,
box.content_width(),
box.m_paint_box->content_width(),
box.box_model().padding.right,
box.box_model().border.right,
box.box_model().margin.right);
@ -214,7 +215,7 @@ void dump_tree(StringBuilder& builder, Layout::Node const& layout_node, bool sho
box.box_model().margin.top,
box.box_model().border.top,
box.box_model().padding.top,
box.content_height(),
box.m_paint_box->content_height(),
box.box_model().padding.bottom,
box.box_model().border.bottom,
box.box_model().margin.bottom);
@ -225,8 +226,8 @@ void dump_tree(StringBuilder& builder, Layout::Node const& layout_node, bool sho
if (is<Layout::BlockContainer>(layout_node) && static_cast<Layout::BlockContainer const&>(layout_node).children_are_inline()) {
auto& block = static_cast<Layout::BlockContainer const&>(layout_node);
for (size_t line_box_index = 0; line_box_index < block.line_boxes().size(); ++line_box_index) {
auto& line_box = block.line_boxes()[line_box_index];
for (size_t line_box_index = 0; line_box_index < block.m_paint_box->line_boxes().size(); ++line_box_index) {
auto& line_box = block.m_paint_box->line_boxes()[line_box_index];
for (size_t i = 0; i < indent; ++i)
builder.append(" ");
builder.appendff(" {}line {}{} width: {}, bottom: {}, baseline: {}\n",

View file

@ -263,6 +263,10 @@ namespace Web::NavigationTiming {
class PerformanceTiming;
}
namespace Web::Painting {
class Box;
}
namespace Web::RequestIdleCallback {
class IdleDeadline;
}

View file

@ -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
*/
@ -22,6 +22,7 @@
#include <LibWeb/Layout/Box.h>
#include <LibWeb/Layout/BreakNode.h>
#include <LibWeb/Layout/TextNode.h>
#include <LibWeb/Painting/Box.h>
#include <LibWeb/UIEvents/EventNames.h>
#include <LibWeb/UIEvents/FocusEvent.h>
@ -150,17 +151,17 @@ int HTMLElement::offset_left() const
// https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsetwidth
int HTMLElement::offset_width() const
{
if (!layout_node() || !layout_node()->is_box())
return 0;
return static_cast<Layout::Box const&>(*layout_node()).border_box_width();
if (auto* paint_box = this->paint_box())
return paint_box->border_box_width();
return 0;
}
// https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsetheight
int HTMLElement::offset_height() const
{
if (!layout_node() || !layout_node()->is_box())
return 0;
return static_cast<Layout::Box const&>(*layout_node()).border_box_height();
if (auto* paint_box = this->paint_box())
return paint_box->border_box_height();
return 0;
}
bool HTMLElement::cannot_navigate() const

View file

@ -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
*/
@ -13,6 +13,7 @@
#include <LibWeb/HTML/HTMLImageElement.h>
#include <LibWeb/Layout/ImageBox.h>
#include <LibWeb/Loader/ResourceLoader.h>
#include <LibWeb/Painting/Box.h>
namespace Web::HTML {
@ -84,8 +85,8 @@ unsigned HTMLImageElement::width() const
const_cast<DOM::Document&>(document()).update_layout();
// Return the rendered width of the image, in CSS pixels, if the image is being rendered.
if (layout_node() && is<Layout::Box>(*layout_node()))
return static_cast<Layout::Box const&>(*layout_node()).content_width();
if (auto* paint_box = this->paint_box())
return paint_box->content_width();
// ...or else the density-corrected intrinsic width and height of the image, in CSS pixels,
// if the image has intrinsic dimensions and is available but not being rendered.
@ -107,8 +108,8 @@ unsigned HTMLImageElement::height() const
const_cast<DOM::Document&>(document()).update_layout();
// Return the rendered height of the image, in CSS pixels, if the image is being rendered.
if (layout_node() && is<Layout::Box>(*layout_node()))
return static_cast<Layout::Box const&>(*layout_node()).content_height();
if (auto* paint_box = this->paint_box())
return paint_box->content_height();
// ...or else the density-corrected intrinsic height and height of the image, in CSS pixels,
// if the image has intrinsic dimensions and is available but not being rendered.

View file

@ -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
*/
@ -21,6 +21,7 @@
#include <LibWeb/Layout/TextNode.h>
#include <LibWeb/Loader/ResourceLoader.h>
#include <LibWeb/Page/EventHandler.h>
#include <LibWeb/Painting/Box.h>
#include <LibWeb/Painting/PaintContext.h>
#include <LibWeb/UIEvents/MouseEvent.h>
@ -61,7 +62,8 @@ void InProcessWebView::set_preferred_color_scheme(CSS::PreferredColorScheme colo
void InProcessWebView::page_did_layout()
{
VERIFY(layout_root());
set_content_size(layout_root()->content_size().to_type<int>());
VERIFY(layout_root()->m_paint_box);
set_content_size(layout_root()->m_paint_box->content_size().to_type<int>());
}
void InProcessWebView::page_did_change_title(const String& title)
@ -177,13 +179,13 @@ void InProcessWebView::layout_and_sync_size()
bool had_horizontal_scrollbar = horizontal_scrollbar().is_visible();
page().top_level_browsing_context().set_size(available_size());
set_content_size(layout_root()->content_size().to_type<int>());
set_content_size(layout_root()->m_paint_box->content_size().to_type<int>());
// NOTE: If layout caused us to gain or lose scrollbars, we have to lay out again
// since the scrollbars now take up some of the available space.
if (had_vertical_scrollbar != vertical_scrollbar().is_visible() || had_horizontal_scrollbar != horizontal_scrollbar().is_visible()) {
page().top_level_browsing_context().set_size(available_size());
set_content_size(layout_root()->content_size().to_type<int>());
set_content_size(layout_root()->m_paint_box->content_size().to_type<int>());
}
page().top_level_browsing_context().set_viewport_scroll_offset({ horizontal_scrollbar().value(), vertical_scrollbar().value() });

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -11,6 +11,7 @@
#include <LibWeb/Layout/InlineFormattingContext.h>
#include <LibWeb/Layout/ReplacedBox.h>
#include <LibWeb/Layout/TextNode.h>
#include <LibWeb/Painting/Box.h>
namespace Web::Layout {
@ -46,11 +47,11 @@ void BlockContainer::paint(PaintContext& context, PaintPhase phase)
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()));
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 : m_line_boxes) {
for (auto& line_box : m_paint_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);
@ -64,7 +65,7 @@ void BlockContainer::paint(PaintContext& context, PaintPhase phase)
// FIXME: Merge this loop with the above somehow..
if (phase == PaintPhase::FocusOutline) {
for (auto& line_box : m_line_boxes) {
for (auto& line_box : m_paint_box->m_line_boxes) {
for (auto& fragment : line_box.fragments()) {
auto* node = fragment.layout_node().dom_node();
if (!node)
@ -85,7 +86,7 @@ HitTestResult BlockContainer::hit_test(const Gfx::IntPoint& position, HitTestTyp
return Box::hit_test(position, type);
HitTestResult last_good_candidate;
for (auto& line_box : m_line_boxes) {
for (auto& line_box : m_paint_box->m_line_boxes) {
for (auto& fragment : line_box.fragments()) {
if (is<Box>(fragment.layout_node()) && verify_cast<Box>(fragment.layout_node()).stacking_context())
continue;
@ -101,7 +102,7 @@ HitTestResult BlockContainer::hit_test(const Gfx::IntPoint& position, HitTestTyp
if (type == HitTestType::TextCursor && last_good_candidate.layout_node)
return last_good_candidate;
return { absolute_border_box_rect().contains(position.x(), position.y()) ? this : nullptr };
return { m_paint_box->absolute_border_box_rect().contains(position.x(), position.y()) ? this : nullptr };
}
bool BlockContainer::is_scrollable() const

View file

@ -27,22 +27,10 @@ public:
BlockContainer* next_sibling() { return verify_cast<BlockContainer>(Node::next_sibling()); }
const BlockContainer* next_sibling() const { return verify_cast<BlockContainer>(Node::next_sibling()); }
template<typename Callback>
void for_each_fragment(Callback);
template<typename Callback>
void for_each_fragment(Callback) const;
bool is_scrollable() const;
const Gfx::FloatPoint& scroll_offset() const { return m_scroll_offset; }
void set_scroll_offset(const Gfx::FloatPoint&);
const Vector<LineBox>& line_boxes() const { return m_line_boxes; }
void set_line_boxes(Vector<LineBox>&& line_boxes) { m_line_boxes = move(line_boxes); }
protected:
Vector<LineBox> m_line_boxes;
private:
virtual bool is_block_container() const final { return true; }
virtual bool wants_mouse_events() const override { return false; }
@ -56,26 +44,4 @@ private:
template<>
inline bool Node::fast_is<BlockContainer>() const { return is_block_container(); }
template<typename Callback>
void BlockContainer::for_each_fragment(Callback callback)
{
for (auto& line_box : line_boxes()) {
for (auto& fragment : line_box.fragments()) {
if (callback(fragment) == IterationDecision::Break)
return;
}
}
}
template<typename Callback>
void BlockContainer::for_each_fragment(Callback callback) const
{
for (auto& line_box : line_boxes()) {
for (auto& fragment : line_box.fragments()) {
if (callback(fragment) == IterationDecision::Break)
return;
}
}
}
}

View file

@ -15,10 +15,25 @@
#include <LibWeb/Layout/FormattingContext.h>
#include <LibWeb/Painting/BackgroundPainting.h>
#include <LibWeb/Painting/BorderPainting.h>
#include <LibWeb/Painting/Box.h>
#include <LibWeb/Painting/ShadowPainting.h>
namespace Web::Layout {
Box::Box(DOM::Document& document, DOM::Node* node, NonnullRefPtr<CSS::StyleProperties> style)
: NodeWithStyleAndBoxModelMetrics(document, node, move(style))
{
}
Box::Box(DOM::Document& document, DOM::Node* node, CSS::ComputedValues computed_values)
: NodeWithStyleAndBoxModelMetrics(document, node, move(computed_values))
{
}
Box::~Box()
{
}
void Box::paint(PaintContext& context, PaintPhase phase)
{
if (!is_visible())
@ -34,17 +49,17 @@ void Box::paint(PaintContext& context, PaintPhase phase)
}
if (phase == PaintPhase::Overlay && dom_node() && document().inspected_node() == dom_node()) {
auto content_rect = absolute_rect();
auto content_rect = m_paint_box->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);
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 = absolute_border_box_rect();
auto padding_rect = absolute_padding_box_rect();
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));
@ -74,7 +89,7 @@ void Box::paint(PaintContext& context, PaintPhase phase)
}
if (phase == PaintPhase::FocusOutline && dom_node() && dom_node()->is_element() && verify_cast<DOM::Element>(*dom_node()).is_focused()) {
context.painter().draw_rect(enclosing_int_rect(absolute_rect()), context.palette().focus_outline());
context.painter().draw_rect(enclosing_int_rect(m_paint_box->absolute_rect()), context.palette().focus_outline());
}
}
@ -86,7 +101,7 @@ void Box::paint_border(PaintContext& context)
.bottom = computed_values().border_bottom(),
.left = computed_values().border_left(),
};
Painting::paint_all_borders(context, absolute_border_box_rect(), normalized_border_radius_data(), borders_data);
Painting::paint_all_borders(context, m_paint_box->absolute_border_box_rect(), normalized_border_radius_data(), borders_data);
}
void Box::paint_background(PaintContext& context)
@ -110,13 +125,13 @@ void Box::paint_background(PaintContext& context)
background_color = document().background_color(context.palette());
}
} else {
background_rect = enclosing_int_rect(absolute_padding_box_rect());
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(absolute_border_box_rect());
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());
}
@ -138,12 +153,12 @@ void Box::paint_box_shadow(PaintContext& context)
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(absolute_border_box_rect()), resolved_box_shadow_data);
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, absolute_border_box_rect(),
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(),
@ -178,7 +193,7 @@ HitTestResult Box::hit_test(const Gfx::IntPoint& position, HitTestType type) con
// FIXME: It would be nice if we could confidently skip over hit testing
// parts of the layout tree, but currently we can't just check
// m_rect.contains() since inline text rects can't be trusted..
HitTestResult result { absolute_border_box_rect().contains(position.x(), position.y()) ? this : nullptr };
HitTestResult result { m_paint_box->absolute_border_box_rect().contains(position.x(), position.y()) ? this : nullptr };
for_each_child_in_paint_order([&](auto& child) {
auto child_result = child.hit_test(position, type);
if (child_result.layout_node)
@ -190,7 +205,7 @@ HitTestResult Box::hit_test(const Gfx::IntPoint& position, HitTestType type) con
void Box::set_needs_display()
{
if (!is_inline()) {
browsing_context().set_needs_display(enclosing_int_rect(absolute_rect()));
browsing_context().set_needs_display(enclosing_int_rect(m_paint_box->absolute_rect()));
return;
}
@ -202,45 +217,6 @@ bool Box::is_body() const
return dom_node() && dom_node() == document().body();
}
void Box::set_offset(const Gfx::FloatPoint& offset)
{
if (m_offset == offset)
return;
m_offset = offset;
did_set_rect();
}
void Box::set_content_size(Gfx::FloatSize const& size)
{
if (m_content_size == size)
return;
m_content_size = size;
did_set_rect();
}
Gfx::FloatPoint Box::effective_offset() const
{
if (m_containing_line_box_fragment.has_value()) {
auto const& fragment = containing_block()->line_boxes()[m_containing_line_box_fragment->line_box_index].fragments()[m_containing_line_box_fragment->fragment_index];
return fragment.offset();
}
return m_offset;
}
const Gfx::FloatRect Box::absolute_rect() const
{
Gfx::FloatRect rect { effective_offset(), content_size() };
for (auto* block = containing_block(); block; block = block->containing_block()) {
rect.translate_by(block->effective_offset());
}
return rect;
}
void Box::set_containing_line_box_fragment(Optional<LineBoxFragmentCoordinate> fragment_coordinate)
{
m_containing_line_box_fragment = fragment_coordinate;
}
StackingContext* Box::enclosing_stacking_context()
{
for (auto* ancestor = parent(); ancestor; ancestor = ancestor->parent()) {
@ -262,7 +238,7 @@ void Box::before_children_paint(PaintContext& context, PaintPhase 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(absolute_border_box_rect()));
context.painter().add_clip_rect(enclosing_int_rect(m_paint_box->absolute_border_box_rect()));
}
}

View file

@ -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
*/
@ -21,64 +21,7 @@ struct LineBoxFragmentCoordinate {
class Box : public NodeWithStyleAndBoxModelMetrics {
public:
struct OverflowData {
Gfx::FloatRect scrollable_overflow_rect;
Gfx::FloatPoint scroll_offset;
};
const Gfx::FloatRect absolute_rect() const;
Gfx::FloatPoint effective_offset() const;
void set_offset(const Gfx::FloatPoint& offset);
void set_offset(float x, float y) { set_offset({ x, y }); }
Gfx::FloatSize const& content_size() const { return m_content_size; }
void set_content_size(Gfx::FloatSize const&);
void set_content_size(float width, float height) { set_content_size({ width, height }); }
void set_content_width(float width) { set_content_size(width, content_height()); }
void set_content_height(float height) { set_content_size(content_width(), height); }
float content_width() const { return m_content_size.width(); }
float content_height() const { return m_content_size.height(); }
Gfx::FloatRect absolute_padding_box_rect() const
{
auto absolute_rect = this->absolute_rect();
Gfx::FloatRect rect;
rect.set_x(absolute_rect.x() - box_model().padding.left);
rect.set_width(content_width() + box_model().padding.left + box_model().padding.right);
rect.set_y(absolute_rect.y() - box_model().padding.top);
rect.set_height(content_height() + box_model().padding.top + box_model().padding.bottom);
return rect;
}
Gfx::FloatRect absolute_border_box_rect() const
{
auto padded_rect = this->absolute_padding_box_rect();
Gfx::FloatRect rect;
rect.set_x(padded_rect.x() - box_model().border.left);
rect.set_width(padded_rect.width() + box_model().border.left + box_model().border.right);
rect.set_y(padded_rect.y() - box_model().border.top);
rect.set_height(padded_rect.height() + box_model().border.top + box_model().border.bottom);
return rect;
}
float border_box_width() const
{
auto border_box = box_model().border_box();
return content_width() + border_box.left + border_box.right;
}
float border_box_height() const
{
auto border_box = box_model().border_box();
return content_height() + border_box.top + border_box.bottom;
}
float absolute_x() const { return absolute_rect().x(); }
float absolute_y() const { return absolute_rect().y(); }
Gfx::FloatPoint absolute_position() const { return absolute_rect().location(); }
OwnPtr<Painting::Box> m_paint_box;
bool is_out_of_flow(FormattingContext const&) const;
@ -87,8 +30,6 @@ public:
bool is_body() const;
void set_containing_line_box_fragment(Optional<LineBoxFragmentCoordinate>);
StackingContext* stacking_context() { return m_stacking_context; }
const StackingContext* stacking_context() const { return m_stacking_context; }
void set_stacking_context(NonnullOwnPtr<StackingContext> context) { m_stacking_context = move(context); }
@ -109,45 +50,21 @@ public:
bool has_intrinsic_height() const { return intrinsic_height().has_value(); }
bool has_intrinsic_aspect_ratio() const { return intrinsic_aspect_ratio().has_value(); }
bool has_overflow() const { return m_overflow_data.has_value(); }
Optional<Gfx::FloatRect> scrollable_overflow_rect() const
{
if (!m_overflow_data.has_value())
return {};
return m_overflow_data->scrollable_overflow_rect;
}
void set_overflow_data(Optional<OverflowData> data) { m_overflow_data = move(data); }
virtual void before_children_paint(PaintContext&, PaintPhase) override;
virtual void after_children_paint(PaintContext&, PaintPhase) override;
protected:
Box(DOM::Document& document, DOM::Node* node, NonnullRefPtr<CSS::StyleProperties> style)
: NodeWithStyleAndBoxModelMetrics(document, node, move(style))
{
}
Box(DOM::Document& document, DOM::Node* node, CSS::ComputedValues computed_values)
: NodeWithStyleAndBoxModelMetrics(document, node, move(computed_values))
{
}
virtual ~Box() override;
virtual void did_set_rect() { }
protected:
Box(DOM::Document&, DOM::Node*, NonnullRefPtr<CSS::StyleProperties>);
Box(DOM::Document&, DOM::Node*, CSS::ComputedValues);
private:
virtual bool is_box() const final { return true; }
Gfx::FloatPoint m_offset;
Gfx::FloatSize m_content_size;
// Some boxes hang off of line box fragments. (inline-block, inline-table, replaced, etc)
Optional<LineBoxFragmentCoordinate> m_containing_line_box_fragment;
OwnPtr<StackingContext> m_stacking_context;
Optional<OverflowData> m_overflow_data;
};
template<>

View file

@ -12,6 +12,7 @@
#include <LibWeb/HTML/BrowsingContext.h>
#include <LibWeb/Layout/ButtonBox.h>
#include <LibWeb/Layout/Label.h>
#include <LibWeb/Painting/Box.h>
namespace Web::Layout {
@ -38,7 +39,7 @@ void ButtonBox::paint(PaintContext& context, PaintPhase phase)
LabelableNode::paint(context, phase);
if (phase == PaintPhase::Foreground) {
auto text_rect = enclosing_int_rect(absolute_rect());
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());
@ -66,7 +67,7 @@ void ButtonBox::handle_mouseup(Badge<EventHandler>, const Gfx::IntPoint& positio
NonnullRefPtr protected_this = *this;
NonnullRefPtr protected_browsing_context = browsing_context();
bool is_inside_node_or_label = enclosing_int_rect(absolute_rect()).contains(position);
bool is_inside_node_or_label = enclosing_int_rect(m_paint_box->absolute_rect()).contains(position);
if (!is_inside_node_or_label)
is_inside_node_or_label = Label::is_inside_associated_label(*this, position);
@ -84,7 +85,7 @@ void ButtonBox::handle_mousemove(Badge<EventHandler>, const Gfx::IntPoint& posit
if (!m_tracking_mouse || !dom_node().enabled())
return;
bool is_inside_node_or_label = enclosing_int_rect(absolute_rect()).contains(position);
bool is_inside_node_or_label = enclosing_int_rect(m_paint_box->absolute_rect()).contains(position);
if (!is_inside_node_or_label)
is_inside_node_or_label = Label::is_inside_associated_label(*this, position);

View file

@ -1,11 +1,12 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2020-2022, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibGfx/Painter.h>
#include <LibWeb/Layout/CanvasBox.h>
#include <LibWeb/Painting/Box.h>
namespace Web::Layout {
@ -33,11 +34,11 @@ void CanvasBox::paint(PaintContext& context, PaintPhase 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())))
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(absolute_rect()), *dom_node().bitmap(), dom_node().bitmap()->rect(), 1.0f, to_gfx_scaling_mode(computed_values().image_rendering()));
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()));
}
}

View file

@ -12,6 +12,7 @@
#include <LibWeb/HTML/HTMLInputElement.h>
#include <LibWeb/Layout/CheckBox.h>
#include <LibWeb/Layout/Label.h>
#include <LibWeb/Painting/Box.h>
namespace Web::Layout {
@ -34,7 +35,7 @@ void CheckBox::paint(PaintContext& context, PaintPhase phase)
LabelableNode::paint(context, phase);
if (phase == PaintPhase::Foreground) {
Gfx::StylePainter::paint_check_box(context.painter(), enclosing_int_rect(absolute_rect()), context.palette(), dom_node().enabled(), dom_node().checked(), m_being_pressed);
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);
}
}
@ -58,7 +59,7 @@ void CheckBox::handle_mouseup(Badge<EventHandler>, const Gfx::IntPoint& position
// NOTE: Changing the checked state of the DOM node may run arbitrary JS, which could disappear this node.
NonnullRefPtr protect = *this;
bool is_inside_node_or_label = enclosing_int_rect(absolute_rect()).contains(position);
bool is_inside_node_or_label = enclosing_int_rect(m_paint_box->absolute_rect()).contains(position);
if (!is_inside_node_or_label)
is_inside_node_or_label = Label::is_inside_associated_label(*this, position);
@ -77,7 +78,7 @@ void CheckBox::handle_mousemove(Badge<EventHandler>, const Gfx::IntPoint& positi
if (!m_tracking_mouse || !dom_node().enabled())
return;
bool is_inside_node_or_label = enclosing_int_rect(absolute_rect()).contains(position);
bool is_inside_node_or_label = enclosing_int_rect(m_paint_box->absolute_rect()).contains(position);
if (!is_inside_node_or_label)
is_inside_node_or_label = Label::is_inside_associated_label(*this, position);

View file

@ -38,19 +38,15 @@ void FormattingState::commit()
node.box_model().border = { node_state.border_top, node_state.border_right, node_state.border_bottom, node_state.border_left };
node.box_model().margin = { node_state.margin_top, node_state.margin_right, node_state.margin_bottom, node_state.margin_left };
// For boxes, transfer relative offset, size, and overflow data.
// For boxes, transfer all the state needed for painting.
if (is<Layout::Box>(node)) {
auto& box = static_cast<Layout::Box&>(node);
box.set_offset(node_state.offset);
box.set_content_size(node_state.content_width, node_state.content_height);
box.set_overflow_data(move(node_state.overflow_data));
box.set_containing_line_box_fragment(node_state.containing_line_box_fragment);
}
// For block containers, transfer line boxes.
if (is<Layout::BlockContainer>(node)) {
auto& block_container = static_cast<Layout::BlockContainer&>(node);
block_container.set_line_boxes(move(node_state.line_boxes));
box.m_paint_box = Painting::Box::create(box);
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));
box.m_paint_box->set_containing_line_box_fragment(node_state.containing_line_box_fragment);
box.m_paint_box->set_line_boxes(move(node_state.line_boxes));
}
}
}

View file

@ -10,6 +10,7 @@
#include <LibGfx/Point.h>
#include <LibWeb/Layout/Box.h>
#include <LibWeb/Layout/LineBox.h>
#include <LibWeb/Painting/Box.h>
namespace Web::Layout {
@ -54,12 +55,12 @@ struct FormattingState {
float border_box_width() const { return border_box_left() + content_width + border_box_right(); }
float border_box_height() const { return border_box_top() + content_height + border_box_bottom(); }
Optional<Layout::Box::OverflowData> overflow_data;
Optional<Painting::Box::OverflowData> overflow_data;
Layout::Box::OverflowData& ensure_overflow_data()
Painting::Box::OverflowData& ensure_overflow_data()
{
if (!overflow_data.has_value())
overflow_data = Layout::Box::OverflowData {};
overflow_data = Painting::Box::OverflowData {};
return *overflow_data;
}

View file

@ -10,6 +10,7 @@
#include <LibWeb/HTML/BrowsingContext.h>
#include <LibWeb/Layout/FrameBox.h>
#include <LibWeb/Layout/InitialContainingBlock.h>
#include <LibWeb/Painting/Box.h>
namespace Web::Layout {
@ -46,8 +47,8 @@ void FrameBox::paint(PaintContext& context, PaintPhase phase)
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(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);
@ -57,7 +58,7 @@ void FrameBox::paint(PaintContext& context, PaintPhase phase)
if constexpr (HIGHLIGHT_FOCUSED_FRAME_DEBUG) {
if (dom_node().nested_browsing_context()->is_focused_context()) {
context.painter().draw_rect(absolute_rect().to_type<int>(), Color::Cyan);
context.painter().draw_rect(m_paint_box->absolute_rect().to_type<int>(), Color::Cyan);
}
}
}
@ -68,7 +69,7 @@ void FrameBox::did_set_rect()
ReplacedBox::did_set_rect();
VERIFY(dom_node().nested_browsing_context());
dom_node().nested_browsing_context()->set_size(content_size().to_type<int>());
dom_node().nested_browsing_context()->set_size(m_paint_box->content_size().to_type<int>());
}
}

View file

@ -11,6 +11,7 @@
#include <LibWeb/CSS/ValueID.h>
#include <LibWeb/HTML/BrowsingContext.h>
#include <LibWeb/Layout/ImageBox.h>
#include <LibWeb/Painting/Box.h>
namespace Web::Layout {
@ -67,8 +68,7 @@ void ImageBox::prepare_for_replaced_layout()
}
if (!has_intrinsic_width() && !has_intrinsic_height()) {
set_content_width(16);
set_content_height(16);
// FIXME: Do something.
}
}
@ -78,7 +78,7 @@ void ImageBox::paint(PaintContext& context, PaintPhase phase)
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())))
if (!context.viewport_rect().intersects(enclosing_int_rect(m_paint_box->absolute_rect())))
return;
ReplacedBox::paint(context, phase);
@ -87,13 +87,13 @@ void ImageBox::paint(PaintContext& context, PaintPhase phase)
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(absolute_rect()), context.palette(), Gfx::FrameShape::Container, Gfx::FrameShadow::Sunken, 2);
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(absolute_rect()), alt, Gfx::TextAlignment::Center, computed_values().color(), Gfx::TextElision::Right);
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(absolute_rect()), *bitmap, bitmap->rect(), 1.0f, to_gfx_scaling_mode(computed_values().image_rendering()));
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()));
}
}
}
@ -107,7 +107,7 @@ bool ImageBox::renders_as_alt_text() const
void ImageBox::browsing_context_did_set_viewport_rect(Gfx::IntRect const& viewport_rect)
{
m_image_loader.set_visible_in_viewport(viewport_rect.to_type<float>().intersects(absolute_rect()));
m_image_loader.set_visible_in_viewport(viewport_rect.to_type<float>().intersects(m_paint_box->absolute_rect()));
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -8,6 +8,7 @@
#include <LibWeb/Dump.h>
#include <LibWeb/HTML/BrowsingContext.h>
#include <LibWeb/Layout/InitialContainingBlock.h>
#include <LibWeb/Painting/Box.h>
#include <LibWeb/Painting/StackingContext.h>
namespace Web::Layout {
@ -41,7 +42,7 @@ void InitialContainingBlock::build_stacking_context_tree()
void InitialContainingBlock::paint_all_phases(PaintContext& context)
{
context.painter().fill_rect(enclosing_int_rect(absolute_rect()), context.palette().base());
context.painter().fill_rect(enclosing_int_rect(m_paint_box->absolute_rect()), context.palette().base());
context.painter().translate(-context.viewport_rect().location());
stacking_context()->paint(context);
}

View file

@ -36,7 +36,7 @@ void InlineNode::paint(PaintContext& context, PaintPhase phase)
auto top_right_border_radius = computed_values().border_top_right_radius();
auto bottom_right_border_radius = computed_values().border_bottom_right_radius();
auto bottom_left_border_radius = computed_values().border_bottom_left_radius();
auto containing_block_position_in_absolute_coordinates = containing_block()->absolute_position();
auto containing_block_position_in_absolute_coordinates = containing_block()->m_paint_box->absolute_position();
for_each_fragment([&](auto const& fragment, bool is_first_fragment, bool is_last_fragment) {
Gfx::FloatRect absolute_fragment_rect { containing_block_position_in_absolute_coordinates.translated(fragment.offset()), fragment.size() };
@ -87,7 +87,7 @@ void InlineNode::paint(PaintContext& context, PaintPhase phase)
.left = computed_values().border_left(),
};
auto containing_block_position_in_absolute_coordinates = containing_block()->absolute_position();
auto containing_block_position_in_absolute_coordinates = containing_block()->m_paint_box->absolute_position();
for_each_fragment([&](auto const& fragment, bool is_first_fragment, bool is_last_fragment) {
Gfx::FloatRect absolute_fragment_rect { containing_block_position_in_absolute_coordinates.translated(fragment.offset()), fragment.size() };
@ -130,7 +130,7 @@ void InlineNode::for_each_fragment(Callback callback)
{
// FIXME: This will be slow if the containing block has a lot of fragments!
Vector<LineBoxFragment const&> fragments;
containing_block()->for_each_fragment([&](auto& fragment) {
containing_block()->m_paint_box->for_each_fragment([&](auto& fragment) {
if (is_inclusive_ancestor_of(fragment.layout_node()))
fragments.append(fragment);
return IterationDecision::Continue;

View file

@ -14,6 +14,7 @@
#include <LibWeb/Layout/Label.h>
#include <LibWeb/Layout/LabelableNode.h>
#include <LibWeb/Layout/TextNode.h>
#include <LibWeb/Painting/Box.h>
namespace Web::Layout {
@ -46,8 +47,8 @@ void Label::handle_mouseup_on_label(Badge<TextNode>, const Gfx::IntPoint& positi
NonnullRefPtr protect = *this;
if (auto* control = labeled_control(); control) {
bool is_inside_control = enclosing_int_rect(control->absolute_rect()).contains(position);
bool is_inside_label = enclosing_int_rect(absolute_rect()).contains(position);
bool is_inside_control = enclosing_int_rect(control->m_paint_box->absolute_rect()).contains(position);
bool is_inside_label = enclosing_int_rect(m_paint_box->absolute_rect()).contains(position);
if (is_inside_control || is_inside_label)
control->handle_associated_label_mouseup({});
@ -62,8 +63,8 @@ void Label::handle_mousemove_on_label(Badge<TextNode>, const Gfx::IntPoint& posi
return;
if (auto* control = labeled_control(); control) {
bool is_inside_control = enclosing_int_rect(control->absolute_rect()).contains(position);
bool is_inside_label = enclosing_int_rect(absolute_rect()).contains(position);
bool is_inside_control = enclosing_int_rect(control->m_paint_box->absolute_rect()).contains(position);
bool is_inside_label = enclosing_int_rect(m_paint_box->absolute_rect()).contains(position);
control->handle_associated_label_mousemove({}, is_inside_control || is_inside_label);
}
@ -72,7 +73,7 @@ void Label::handle_mousemove_on_label(Badge<TextNode>, const Gfx::IntPoint& posi
bool Label::is_inside_associated_label(LabelableNode& control, const Gfx::IntPoint& position)
{
if (auto* label = label_for_control_node(control); label)
return enclosing_int_rect(label->absolute_rect()).contains(position);
return enclosing_int_rect(label->m_paint_box->absolute_rect()).contains(position);
return false;
}

View file

@ -48,7 +48,7 @@ StringView LineBoxFragment::text() const
const Gfx::FloatRect LineBoxFragment::absolute_rect() const
{
Gfx::FloatRect rect { {}, size() };
rect.set_location(m_layout_node.containing_block()->absolute_position());
rect.set_location(m_layout_node.containing_block()->m_paint_box->absolute_position());
rect.translate_by(offset());
return rect;
}

View file

@ -8,6 +8,7 @@
#include <AK/StringBuilder.h>
#include <LibGfx/Painter.h>
#include <LibWeb/Layout/ListItemMarkerBox.h>
#include <LibWeb/Painting/Box.h>
namespace Web::Layout {
@ -59,7 +60,7 @@ void ListItemMarkerBox::paint(PaintContext& context, PaintPhase phase)
if (phase != PaintPhase::Foreground)
return;
auto enclosing = enclosing_int_rect(absolute_rect());
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());

View file

@ -112,7 +112,7 @@ InitialContainingBlock& Node::root()
void Node::set_needs_display()
{
if (auto* block = containing_block()) {
block->for_each_fragment([&](auto& fragment) {
block->m_paint_box->for_each_fragment([&](auto& fragment) {
if (&fragment.layout_node() == this || is_ancestor_of(fragment.layout_node())) {
browsing_context().set_needs_display(enclosing_int_rect(fragment.absolute_rect()));
}
@ -124,11 +124,11 @@ void Node::set_needs_display()
Gfx::FloatPoint Node::box_type_agnostic_position() const
{
if (is<Box>(*this))
return verify_cast<Box>(*this).absolute_position();
return verify_cast<Box>(*this).m_paint_box->absolute_position();
VERIFY(is_inline());
Gfx::FloatPoint position;
if (auto* block = containing_block()) {
block->for_each_fragment([&](auto& fragment) {
block->m_paint_box->for_each_fragment([&](auto& fragment) {
if (&fragment.layout_node() == this || is_ancestor_of(fragment.layout_node())) {
position = fragment.absolute_rect().location();
return IterationDecision::Break;

View file

@ -7,6 +7,7 @@
#include <LibGfx/Painter.h>
#include <LibGfx/StylePainter.h>
#include <LibWeb/Layout/Progress.h>
#include <LibWeb/Painting/Box.h>
namespace Web::Layout {
@ -27,7 +28,7 @@ void Progress::paint(PaintContext& context, PaintPhase phase)
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, dom_node().max(), dom_node().value(), "");
Gfx::StylePainter::paint_progressbar(context.painter(), enclosing_int_rect(m_paint_box->absolute_rect()), context.palette(), 0, dom_node().max(), dom_node().value(), "");
}
}

View file

@ -11,6 +11,7 @@
#include <LibWeb/HTML/BrowsingContext.h>
#include <LibWeb/Layout/Label.h>
#include <LibWeb/Layout/RadioButton.h>
#include <LibWeb/Painting/Box.h>
namespace Web::Layout {
@ -33,7 +34,7 @@ void RadioButton::paint(PaintContext& context, PaintPhase phase)
LabelableNode::paint(context, phase);
if (phase == PaintPhase::Foreground) {
Gfx::StylePainter::paint_radio_button(context.painter(), enclosing_int_rect(absolute_rect()), context.palette(), dom_node().checked(), m_being_pressed);
Gfx::StylePainter::paint_radio_button(context.painter(), enclosing_int_rect(m_paint_box->absolute_rect()), context.palette(), dom_node().checked(), m_being_pressed);
}
}
@ -57,7 +58,7 @@ void RadioButton::handle_mouseup(Badge<EventHandler>, const Gfx::IntPoint& posit
// NOTE: Changing the checked state of the DOM node may run arbitrary JS, which could disappear this node.
NonnullRefPtr protect = *this;
bool is_inside_node_or_label = enclosing_int_rect(absolute_rect()).contains(position);
bool is_inside_node_or_label = enclosing_int_rect(m_paint_box->absolute_rect()).contains(position);
if (!is_inside_node_or_label)
is_inside_node_or_label = Label::is_inside_associated_label(*this, position);
@ -74,7 +75,7 @@ void RadioButton::handle_mousemove(Badge<EventHandler>, const Gfx::IntPoint& pos
if (!m_tracking_mouse || !dom_node().enabled())
return;
bool is_inside_node_or_label = enclosing_int_rect(absolute_rect()).contains(position);
bool is_inside_node_or_label = enclosing_int_rect(m_paint_box->absolute_rect()).contains(position);
if (!is_inside_node_or_label)
is_inside_node_or_label = Label::is_inside_associated_label(*this, position);

View file

@ -8,6 +8,7 @@
#include <LibGfx/AntiAliasingPainter.h>
#include <LibGfx/Painter.h>
#include <LibWeb/Layout/SVGGeometryBox.h>
#include <LibWeb/Painting/Box.h>
#include <LibWeb/SVG/SVGPathElement.h>
#include <LibWeb/SVG/SVGSVGElement.h>
@ -39,7 +40,7 @@ void SVGGeometryBox::paint(PaintContext& context, PaintPhase phase)
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((Gfx::Rect<int>)absolute_rect());
context.painter().add_clip_rect(enclosing_int_rect(m_paint_box->absolute_rect()));
Gfx::Path path = geometry_element.get_path();
@ -121,10 +122,10 @@ float SVGGeometryBox::viewbox_scaling() const
auto view_box = svg_box->view_box().value();
bool has_specified_width = svg_box->has_attribute(HTML::AttributeNames::width);
auto specified_width = content_width();
auto specified_width = m_paint_box->content_width();
bool has_specified_height = svg_box->has_attribute(HTML::AttributeNames::height);
auto specified_height = content_height();
auto specified_height = m_paint_box->content_height();
auto scale_width = has_specified_width ? specified_width / view_box.width : 1;
auto scale_height = has_specified_height ? specified_height / view_box.height : 1;

View file

@ -5,6 +5,7 @@
*/
#include <LibWeb/Layout/SVGSVGBox.h>
#include <LibWeb/Painting/Box.h>
namespace Web::Layout {
@ -19,7 +20,7 @@ void SVGSVGBox::before_children_paint(PaintContext& context, PaintPhase phase)
return;
if (!context.has_svg_context())
context.set_svg_context(SVGContext(absolute_rect()));
context.set_svg_context(SVGContext(m_paint_box->absolute_rect()));
SVGGraphicsBox::before_children_paint(context, phase);
}

View file

@ -0,0 +1,52 @@
/*
* Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/Layout/BlockContainer.h>
#include <LibWeb/Painting/Box.h>
namespace Web::Painting {
void Box::set_offset(const Gfx::FloatPoint& offset)
{
if (m_offset == offset)
return;
m_offset = offset;
// FIXME: This const_cast is gross.
const_cast<Layout::Box&>(m_layout_box).did_set_rect();
}
void Box::set_content_size(Gfx::FloatSize const& size)
{
if (m_content_size == size)
return;
m_content_size = size;
// FIXME: This const_cast is gross.
const_cast<Layout::Box&>(m_layout_box).did_set_rect();
}
Gfx::FloatPoint Box::effective_offset() const
{
if (m_containing_line_box_fragment.has_value()) {
auto const& fragment = m_layout_box.containing_block()->m_paint_box->line_boxes()[m_containing_line_box_fragment->line_box_index].fragments()[m_containing_line_box_fragment->fragment_index];
return fragment.offset();
}
return m_offset;
}
Gfx::FloatRect Box::absolute_rect() const
{
Gfx::FloatRect rect { effective_offset(), content_size() };
for (auto* block = m_layout_box.containing_block(); block; block = block->containing_block())
rect.translate_by(block->m_paint_box->effective_offset());
return rect;
}
void Box::set_containing_line_box_fragment(Optional<Layout::LineBoxFragmentCoordinate> fragment_coordinate)
{
m_containing_line_box_fragment = fragment_coordinate;
}
}

View file

@ -0,0 +1,126 @@
/*
* Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/NonnullOwnPtr.h>
#include <LibWeb/Layout/Box.h>
#include <LibWeb/Layout/LineBox.h>
namespace Web::Painting {
class Box {
public:
static NonnullOwnPtr<Box> create(Layout::Box const& layout_box)
{
return adopt_own(*new Box(layout_box));
}
explicit Box(Layout::Box const& layout_box)
: m_layout_box(layout_box)
{
}
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(); }
struct OverflowData {
Gfx::FloatRect scrollable_overflow_rect;
Gfx::FloatPoint scroll_offset;
};
Optional<OverflowData> m_overflow_data;
Gfx::FloatPoint m_offset;
Gfx::FloatSize m_content_size;
Vector<Layout::LineBox> const& line_boxes() const { return m_line_boxes; }
void set_line_boxes(Vector<Layout::LineBox>&& line_boxes) { m_line_boxes = move(line_boxes); }
Vector<Layout::LineBox> m_line_boxes;
// Some boxes hang off of line box fragments. (inline-block, inline-table, replaced, etc)
Optional<Layout::LineBoxFragmentCoordinate> m_containing_line_box_fragment;
Gfx::FloatRect absolute_rect() const;
Gfx::FloatPoint effective_offset() const;
void set_offset(Gfx::FloatPoint const&);
void set_offset(float x, float y) { set_offset({ x, y }); }
Gfx::FloatSize const& content_size() const { return m_content_size; }
void set_content_size(Gfx::FloatSize const&);
void set_content_size(float width, float height) { set_content_size({ width, height }); }
void set_content_width(float width) { set_content_size(width, content_height()); }
void set_content_height(float height) { set_content_size(content_width(), height); }
float content_width() const { return m_content_size.width(); }
float content_height() const { return m_content_size.height(); }
Gfx::FloatRect absolute_padding_box_rect() const
{
auto absolute_rect = this->absolute_rect();
Gfx::FloatRect rect;
rect.set_x(absolute_rect.x() - box_model().padding.left);
rect.set_width(content_width() + box_model().padding.left + box_model().padding.right);
rect.set_y(absolute_rect.y() - box_model().padding.top);
rect.set_height(content_height() + box_model().padding.top + box_model().padding.bottom);
return rect;
}
Gfx::FloatRect absolute_border_box_rect() const
{
auto padded_rect = this->absolute_padding_box_rect();
Gfx::FloatRect rect;
rect.set_x(padded_rect.x() - box_model().border.left);
rect.set_width(padded_rect.width() + box_model().border.left + box_model().border.right);
rect.set_y(padded_rect.y() - box_model().border.top);
rect.set_height(padded_rect.height() + box_model().border.top + box_model().border.bottom);
return rect;
}
float border_box_width() const
{
auto border_box = box_model().border_box();
return content_width() + border_box.left + border_box.right;
}
float border_box_height() const
{
auto border_box = box_model().border_box();
return content_height() + border_box.top + border_box.bottom;
}
float absolute_x() const { return absolute_rect().x(); }
float absolute_y() const { return absolute_rect().y(); }
Gfx::FloatPoint absolute_position() const { return absolute_rect().location(); }
bool has_overflow() const { return m_overflow_data.has_value(); }
Optional<Gfx::FloatRect> scrollable_overflow_rect() const
{
if (!m_overflow_data.has_value())
return {};
return m_overflow_data->scrollable_overflow_rect;
}
void set_overflow_data(Optional<OverflowData> data) { m_overflow_data = move(data); }
void set_containing_line_box_fragment(Optional<Layout::LineBoxFragmentCoordinate>);
template<typename Callback>
void for_each_fragment(Callback callback) const
{
for (auto& line_box : line_boxes()) {
for (auto& fragment : line_box.fragments()) {
if (callback(fragment) == IterationDecision::Break)
return;
}
}
}
};
}

View file

@ -10,6 +10,7 @@
#include <LibWeb/Layout/Box.h>
#include <LibWeb/Layout/InitialContainingBlock.h>
#include <LibWeb/Layout/ReplacedBox.h>
#include <LibWeb/Painting/Box.h>
#include <LibWeb/Painting/StackingContext.h>
namespace Web::Layout {
@ -140,7 +141,7 @@ void StackingContext::paint(PaintContext& context)
Gfx::Painter painter(bitmap);
PaintContext paint_context(painter, context.palette(), context.scroll_offset());
paint_internal(paint_context);
context.painter().blit(Gfx::IntPoint(m_box.absolute_position()), bitmap, Gfx::IntRect(m_box.absolute_rect()), opacity);
context.painter().blit(Gfx::IntPoint(m_box.m_paint_box->absolute_position()), bitmap, Gfx::IntRect(m_box.m_paint_box->absolute_rect()), opacity);
} else {
paint_internal(context);
}
@ -216,7 +217,7 @@ HitTestResult StackingContext::hit_test(const Gfx::IntPoint& position, HitTestTy
}
// 1. the background and borders of the element forming the stacking context.
if (m_box.absolute_border_box_rect().contains(position.to_type<float>())) {
if (m_box.m_paint_box->absolute_border_box_rect().contains(position.to_type<float>())) {
return HitTestResult {
.layout_node = m_box,
};
@ -230,7 +231,7 @@ void StackingContext::dump(int indent) const
StringBuilder builder;
for (int i = 0; i < indent; ++i)
builder.append(' ');
builder.appendff("SC for {} {} [children: {}] (z-index: ", m_box.debug_description(), m_box.absolute_rect(), m_children.size());
builder.appendff("SC for {} {} [children: {}] (z-index: ", m_box.debug_description(), m_box.m_paint_box->absolute_rect(), m_children.size());
if (m_box.computed_values().z_index().has_value())
builder.appendff("{}", m_box.computed_values().z_index().value());
else

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2020-2022, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2021, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
@ -25,6 +25,7 @@
#include <LibWeb/Layout/InitialContainingBlock.h>
#include <LibWeb/Loader/ContentFilter.h>
#include <LibWeb/Loader/ResourceLoader.h>
#include <LibWeb/Painting/Box.h>
#include <WebContent/ConnectionFromClient.h>
#include <WebContent/PageHost.h>
#include <WebContent/WebContentClientEndpoint.h>
@ -322,8 +323,13 @@ Messages::WebContentServer::InspectDomNodeResponse ConnectionFromClient::inspect
MUST(serializer.add("border_right", box_model.border.right));
MUST(serializer.add("border_bottom", box_model.border.bottom));
MUST(serializer.add("border_left", box_model.border.left));
MUST(serializer.add("content_width", box->content_width()));
MUST(serializer.add("content_height", box->content_height()));
if (auto* paint_box = box->paint_box()) {
MUST(serializer.add("content_width", paint_box->content_width()));
MUST(serializer.add("content_height", paint_box->content_height()));
} else {
MUST(serializer.add("content_width", 0));
MUST(serializer.add("content_height", 0));
}
MUST(serializer.finish());
return builder.to_string();

View file

@ -12,6 +12,7 @@
#include <LibWeb/Cookie/ParsedCookie.h>
#include <LibWeb/HTML/BrowsingContext.h>
#include <LibWeb/Layout/InitialContainingBlock.h>
#include <LibWeb/Painting/Box.h>
#include <WebContent/WebContentClientEndpoint.h>
namespace WebContent {
@ -121,10 +122,10 @@ void PageHost::page_did_layout()
auto* layout_root = this->layout_root();
VERIFY(layout_root);
Gfx::IntSize content_size;
if (layout_root->has_overflow())
content_size = enclosing_int_rect(layout_root->scrollable_overflow_rect().value()).size();
if (layout_root->m_paint_box->has_overflow())
content_size = enclosing_int_rect(layout_root->m_paint_box->scrollable_overflow_rect().value()).size();
else
content_size = enclosing_int_rect(layout_root->absolute_rect()).size();
content_size = enclosing_int_rect(layout_root->m_paint_box->absolute_rect()).size();
m_client.async_did_layout(content_size);
}