mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 13:17:35 +00:00
LibWeb: Make StackingContext point to paint tree instead of layout tree
Eventually we should not need the layout tree for anything when painting and this code will only look at the paint tree. For now, this is just another step in that direction.
This commit is contained in:
parent
1e0ea45fe5
commit
d296992fb3
6 changed files with 86 additions and 100 deletions
|
@ -24,15 +24,14 @@
|
|||
|
||||
namespace Web::Painting {
|
||||
|
||||
static void paint_node(Layout::Node const& layout_node, PaintContext& context, PaintPhase phase)
|
||||
static void paint_node(Paintable const& paintable, PaintContext& context, PaintPhase phase)
|
||||
{
|
||||
if (auto const* paintable = layout_node.paintable())
|
||||
paintable->paint(context, phase);
|
||||
paintable.paint(context, phase);
|
||||
}
|
||||
|
||||
StackingContext::StackingContext(Layout::Box& box, StackingContext* parent, size_t index_in_tree_order)
|
||||
: m_box(box)
|
||||
, m_transform(combine_transformations(m_box->computed_values().transformations()))
|
||||
StackingContext::StackingContext(PaintableBox& paintable_box, StackingContext* parent, size_t index_in_tree_order)
|
||||
: m_paintable_box(paintable_box)
|
||||
, m_transform(combine_transformations(paintable_box.computed_values().transformations()))
|
||||
, m_transform_origin(compute_transform_origin())
|
||||
, m_parent(parent)
|
||||
, m_index_in_tree_order(index_in_tree_order)
|
||||
|
@ -45,8 +44,8 @@ StackingContext::StackingContext(Layout::Box& box, StackingContext* parent, size
|
|||
void StackingContext::sort()
|
||||
{
|
||||
quick_sort(m_children, [](auto& a, auto& b) {
|
||||
auto a_z_index = a->m_box->computed_values().z_index().value_or(0);
|
||||
auto b_z_index = b->m_box->computed_values().z_index().value_or(0);
|
||||
auto a_z_index = a->paintable_box().computed_values().z_index().value_or(0);
|
||||
auto b_z_index = b->paintable_box().computed_values().z_index().value_or(0);
|
||||
if (a_z_index == b_z_index)
|
||||
return a->m_index_in_tree_order < b->m_index_in_tree_order;
|
||||
return a_z_index < b_z_index;
|
||||
|
@ -73,23 +72,13 @@ static PaintPhase to_paint_phase(StackingContext::StackingContextPaintPhase phas
|
|||
}
|
||||
}
|
||||
|
||||
void StackingContext::paint_descendants(PaintContext& context, Layout::Node const& box, StackingContextPaintPhase phase) const
|
||||
void StackingContext::paint_descendants(PaintContext& context, Paintable const& paintable, StackingContextPaintPhase phase) const
|
||||
{
|
||||
if (auto* paintable = box.paintable()) {
|
||||
paintable->before_children_paint(context, to_paint_phase(phase));
|
||||
paintable->apply_clip_overflow_rect(context, to_paint_phase(phase));
|
||||
}
|
||||
paintable.before_children_paint(context, to_paint_phase(phase));
|
||||
paintable.apply_clip_overflow_rect(context, to_paint_phase(phase));
|
||||
|
||||
auto child_stacking_context = [&](auto& child) -> StackingContext const* {
|
||||
if (auto* paintable = child.paintable()) {
|
||||
if (auto* stacking_context = paintable->stacking_context_rooted_here())
|
||||
return stacking_context;
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
box.for_each_child([&](auto& child) {
|
||||
auto* stacking_context = child_stacking_context(child);
|
||||
paintable.for_each_child([this, &context, phase](auto& child) {
|
||||
auto* stacking_context = child.stacking_context_rooted_here();
|
||||
|
||||
if (child.is_positioned()) {
|
||||
// If `child` is positioned with a z-index of `0` or `auto`, skip over it.
|
||||
|
@ -120,7 +109,7 @@ void StackingContext::paint_descendants(PaintContext& context, Layout::Node cons
|
|||
paint_node(child, context, PaintPhase::Border);
|
||||
paint_descendants(context, child, phase);
|
||||
if (child.display().is_table_inside() || child.computed_values().border_collapse() == CSS::BorderCollapse::Collapse) {
|
||||
paint_table_borders(context, child);
|
||||
paint_table_borders(context, verify_cast<PaintableBox>(child));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -137,7 +126,7 @@ void StackingContext::paint_descendants(PaintContext& context, Layout::Node cons
|
|||
paint_node(child, context, PaintPhase::Background);
|
||||
paint_node(child, context, PaintPhase::Border);
|
||||
if (child.display().is_table_inside() && child.computed_values().border_collapse() == CSS::BorderCollapse::Separate)
|
||||
paint_table_borders(context, child);
|
||||
paint_table_borders(context, verify_cast<PaintableBox>(child));
|
||||
paint_descendants(context, child, StackingContextPaintPhase::BackgroundAndBorders);
|
||||
}
|
||||
paint_descendants(context, child, phase);
|
||||
|
@ -154,19 +143,16 @@ void StackingContext::paint_descendants(PaintContext& context, Layout::Node cons
|
|||
}
|
||||
});
|
||||
|
||||
if (auto* paintable = box.paintable()) {
|
||||
paintable->clear_clip_overflow_rect(context, to_paint_phase(phase));
|
||||
paintable->after_children_paint(context, to_paint_phase(phase));
|
||||
}
|
||||
paintable.clear_clip_overflow_rect(context, to_paint_phase(phase));
|
||||
paintable.after_children_paint(context, to_paint_phase(phase));
|
||||
}
|
||||
|
||||
void StackingContext::paint_child(PaintContext& context, StackingContext const& child) const
|
||||
{
|
||||
auto parent = child.m_box->parent();
|
||||
auto* parent_paintable = parent ? parent->paintable() : nullptr;
|
||||
auto parent_paintable = child.paintable_box().parent();
|
||||
if (parent_paintable)
|
||||
parent_paintable->before_children_paint(context, PaintPhase::Foreground);
|
||||
auto containing_block = child.m_box->containing_block();
|
||||
auto containing_block = child.paintable_box().containing_block();
|
||||
auto* containing_block_paintable = containing_block ? containing_block->paintable() : nullptr;
|
||||
if (containing_block_paintable)
|
||||
containing_block_paintable->apply_clip_overflow_rect(context, PaintPhase::Foreground);
|
||||
|
@ -183,34 +169,33 @@ 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)
|
||||
paint_node(m_box, context, PaintPhase::Background);
|
||||
paint_node(m_box, context, PaintPhase::Border);
|
||||
paint_node(paintable_box(), context, PaintPhase::Background);
|
||||
paint_node(paintable_box(), context, PaintPhase::Border);
|
||||
|
||||
// Stacking contexts formed by positioned descendants with negative z-indices (excluding 0) in z-index order
|
||||
// (most negative first) then tree order. (step 3)
|
||||
for (auto* child : m_children) {
|
||||
if (!child->paintable_box().layout_box().is_positioned())
|
||||
if (!child->paintable_box().is_positioned())
|
||||
continue;
|
||||
if (child->paintable_box().computed_values().z_index().has_value() && child->m_box->computed_values().z_index().value() < 0)
|
||||
if (child->paintable_box().computed_values().z_index().has_value() && child->paintable_box().computed_values().z_index().value() < 0)
|
||||
paint_child(context, *child);
|
||||
}
|
||||
|
||||
// Draw the background and borders for block-level children (step 4)
|
||||
paint_descendants(context, m_box, StackingContextPaintPhase::BackgroundAndBorders);
|
||||
paint_descendants(context, paintable_box(), StackingContextPaintPhase::BackgroundAndBorders);
|
||||
// Draw the non-positioned floats (step 5)
|
||||
paint_descendants(context, m_box, StackingContextPaintPhase::Floats);
|
||||
paint_descendants(context, paintable_box(), StackingContextPaintPhase::Floats);
|
||||
// Draw inline content, replaced content, etc. (steps 6, 7)
|
||||
paint_descendants(context, m_box, StackingContextPaintPhase::BackgroundAndBordersForInlineLevelAndReplaced);
|
||||
paint_node(m_box, context, PaintPhase::Foreground);
|
||||
paint_descendants(context, m_box, StackingContextPaintPhase::Foreground);
|
||||
paint_descendants(context, paintable_box(), StackingContextPaintPhase::BackgroundAndBordersForInlineLevelAndReplaced);
|
||||
paint_node(paintable_box(), context, PaintPhase::Foreground);
|
||||
paint_descendants(context, paintable_box(), StackingContextPaintPhase::Foreground);
|
||||
|
||||
// Draw positioned descendants with z-index `0` or `auto` in tree order. (step 8)
|
||||
// FIXME: There's more to this step that we have yet to understand and implement.
|
||||
m_box->paintable_box()->for_each_in_subtree([&](Paintable const& paintable) {
|
||||
auto const& layout_node = paintable.layout_node();
|
||||
paintable_box().for_each_in_subtree([this, &context](Paintable const& paintable) {
|
||||
auto const& z_index = paintable.computed_values().z_index();
|
||||
|
||||
if (!layout_node.is_positioned() || (z_index.has_value() && z_index.value() != 0)) {
|
||||
if (!paintable.is_positioned() || (z_index.has_value() && z_index.value() != 0)) {
|
||||
return paintable.stacking_context_rooted_here()
|
||||
? TraversalDecision::SkipChildrenAndContinue
|
||||
: TraversalDecision::Continue;
|
||||
|
@ -218,11 +203,10 @@ void StackingContext::paint_internal(PaintContext& context) const
|
|||
|
||||
// At this point, `paintable_box` is a positioned descendant with z-index: auto.
|
||||
// FIXME: This is basically duplicating logic found elsewhere in this same function. Find a way to make this more elegant.
|
||||
auto parent = layout_node.parent();
|
||||
auto* parent_paintable = parent ? parent->paintable() : nullptr;
|
||||
auto* parent_paintable = paintable.parent();
|
||||
if (parent_paintable)
|
||||
parent_paintable->before_children_paint(context, PaintPhase::Foreground);
|
||||
auto containing_block = layout_node.containing_block();
|
||||
auto containing_block = paintable.containing_block();
|
||||
auto* containing_block_paintable = containing_block ? containing_block->paintable() : nullptr;
|
||||
if (containing_block_paintable)
|
||||
containing_block_paintable->apply_clip_overflow_rect(context, PaintPhase::Foreground);
|
||||
|
@ -230,16 +214,16 @@ void StackingContext::paint_internal(PaintContext& context) const
|
|||
paint_child(context, *child);
|
||||
return TraversalDecision::SkipChildrenAndContinue;
|
||||
} else {
|
||||
paint_node(layout_node, context, PaintPhase::Background);
|
||||
paint_node(layout_node, context, PaintPhase::Border);
|
||||
paint_descendants(context, layout_node, StackingContextPaintPhase::BackgroundAndBorders);
|
||||
paint_descendants(context, layout_node, StackingContextPaintPhase::Floats);
|
||||
paint_descendants(context, layout_node, StackingContextPaintPhase::BackgroundAndBordersForInlineLevelAndReplaced);
|
||||
paint_node(layout_node, context, PaintPhase::Foreground);
|
||||
paint_descendants(context, layout_node, StackingContextPaintPhase::Foreground);
|
||||
paint_node(layout_node, context, PaintPhase::Outline);
|
||||
paint_node(layout_node, context, PaintPhase::Overlay);
|
||||
paint_descendants(context, layout_node, StackingContextPaintPhase::FocusAndOverlay);
|
||||
paint_node(paintable, context, PaintPhase::Background);
|
||||
paint_node(paintable, context, PaintPhase::Border);
|
||||
paint_descendants(context, paintable, StackingContextPaintPhase::BackgroundAndBorders);
|
||||
paint_descendants(context, paintable, StackingContextPaintPhase::Floats);
|
||||
paint_descendants(context, paintable, StackingContextPaintPhase::BackgroundAndBordersForInlineLevelAndReplaced);
|
||||
paint_node(paintable, context, PaintPhase::Foreground);
|
||||
paint_descendants(context, paintable, StackingContextPaintPhase::Foreground);
|
||||
paint_node(paintable, context, PaintPhase::Outline);
|
||||
paint_node(paintable, context, PaintPhase::Overlay);
|
||||
paint_descendants(context, paintable, StackingContextPaintPhase::FocusAndOverlay);
|
||||
}
|
||||
if (parent_paintable)
|
||||
parent_paintable->after_children_paint(context, PaintPhase::Foreground);
|
||||
|
@ -252,15 +236,15 @@ void StackingContext::paint_internal(PaintContext& context) const
|
|||
// Stacking contexts formed by positioned descendants with z-indices greater than or equal to 1 in z-index order
|
||||
// (smallest first) then tree order. (Step 9)
|
||||
for (auto* child : m_children) {
|
||||
if (!child->paintable_box().layout_box().is_positioned())
|
||||
if (!child->paintable_box().is_positioned())
|
||||
continue;
|
||||
if (child->paintable_box().computed_values().z_index().has_value() && child->m_box->computed_values().z_index().value() >= 1)
|
||||
if (child->paintable_box().computed_values().z_index().has_value() && child->paintable_box().computed_values().z_index().value() >= 1)
|
||||
paint_child(context, *child);
|
||||
}
|
||||
|
||||
paint_node(m_box, context, PaintPhase::Outline);
|
||||
paint_node(m_box, context, PaintPhase::Overlay);
|
||||
paint_descendants(context, m_box, StackingContextPaintPhase::FocusAndOverlay);
|
||||
paint_node(paintable_box(), context, PaintPhase::Outline);
|
||||
paint_node(paintable_box(), context, PaintPhase::Overlay);
|
||||
paint_descendants(context, paintable_box(), StackingContextPaintPhase::FocusAndOverlay);
|
||||
}
|
||||
|
||||
Gfx::FloatMatrix4x4 StackingContext::get_transformation_matrix(CSS::Transformation const& transformation) const
|
||||
|
@ -269,10 +253,10 @@ Gfx::FloatMatrix4x4 StackingContext::get_transformation_matrix(CSS::Transformati
|
|||
auto value = [this, transformation](size_t index, CSS::Length const& reference_length = CSS::Length::make_px(0)) -> float {
|
||||
return transformation.values[index].visit(
|
||||
[this, reference_length](CSS::LengthPercentage const& value) -> double {
|
||||
return value.resolved(m_box, reference_length).to_px(m_box).to_float();
|
||||
return value.resolved(paintable_box().layout_node(), reference_length).to_px(paintable_box().layout_box()).to_float();
|
||||
},
|
||||
[this](CSS::AngleOrCalculated const& value) {
|
||||
return value.resolved(m_box).to_degrees() * M_DEG2RAD;
|
||||
return value.resolved(paintable_box().layout_node()).to_degrees() * M_DEG2RAD;
|
||||
},
|
||||
[](double value) {
|
||||
return value;
|
||||
|
@ -422,11 +406,11 @@ Gfx::AffineTransform StackingContext::affine_transform_matrix() const
|
|||
void StackingContext::paint(PaintContext& context) const
|
||||
{
|
||||
Gfx::PainterStateSaver saver(context.painter());
|
||||
if (m_box->is_fixed_position()) {
|
||||
if (paintable_box().is_fixed_position()) {
|
||||
context.painter().translate(-context.painter().translation());
|
||||
}
|
||||
|
||||
auto opacity = m_box->computed_values().opacity();
|
||||
auto opacity = paintable_box().computed_values().opacity();
|
||||
if (opacity == 0.0f)
|
||||
return;
|
||||
|
||||
|
@ -473,7 +457,7 @@ void StackingContext::paint(PaintContext& context) const
|
|||
if (destination_rect.size() == bitmap->size()) {
|
||||
context.painter().blit(destination_rect.location(), *bitmap, bitmap->rect(), opacity);
|
||||
} else {
|
||||
auto scaling_mode = CSS::to_gfx_scaling_mode(m_box->computed_values().image_rendering(), bitmap->rect(), destination_rect);
|
||||
auto scaling_mode = CSS::to_gfx_scaling_mode(paintable_box().computed_values().image_rendering(), bitmap->rect(), destination_rect);
|
||||
context.painter().draw_scaled_bitmap(destination_rect, *bitmap, bitmap->rect(), opacity, scaling_mode);
|
||||
}
|
||||
} else {
|
||||
|
@ -485,11 +469,11 @@ void StackingContext::paint(PaintContext& context) const
|
|||
|
||||
Gfx::FloatPoint StackingContext::compute_transform_origin() const
|
||||
{
|
||||
auto style_value = m_box->computed_values().transform_origin();
|
||||
auto style_value = paintable_box().computed_values().transform_origin();
|
||||
// FIXME: respect transform-box property
|
||||
auto reference_box = paintable_box().absolute_border_box_rect();
|
||||
auto x = reference_box.left() + style_value.x.to_px(m_box, reference_box.width());
|
||||
auto y = reference_box.top() + style_value.y.to_px(m_box, reference_box.height());
|
||||
auto x = reference_box.left() + style_value.x.to_px(paintable_box().layout_node(), reference_box.width());
|
||||
auto y = reference_box.top() + style_value.y.to_px(paintable_box().layout_node(), reference_box.height());
|
||||
return { x.to_float(), y.to_float() };
|
||||
}
|
||||
|
||||
|
@ -525,7 +509,7 @@ static TraversalDecision for_each_in_subtree_of_type_within_same_stacking_contex
|
|||
|
||||
Optional<HitTestResult> StackingContext::hit_test(CSSPixelPoint position, HitTestType type) const
|
||||
{
|
||||
if (!m_box->is_visible())
|
||||
if (!paintable_box().is_visible())
|
||||
return {};
|
||||
|
||||
auto transform_origin = this->transform_origin().to_type<CSSPixels>();
|
||||
|
@ -536,7 +520,7 @@ Optional<HitTestResult> StackingContext::hit_test(CSSPixelPoint position, HitTes
|
|||
};
|
||||
auto transformed_position = affine_transform_matrix().inverse().value_or({}).map(offset_position).to_type<CSSPixels>() + transform_origin;
|
||||
|
||||
if (paintable_box().layout_box().is_fixed_position()) {
|
||||
if (paintable_box().is_fixed_position()) {
|
||||
auto scroll_offset = paintable_box().document().browsing_context()->viewport_scroll_offset();
|
||||
transformed_position.translate_by(-scroll_offset);
|
||||
}
|
||||
|
@ -554,7 +538,7 @@ Optional<HitTestResult> StackingContext::hit_test(CSSPixelPoint position, HitTes
|
|||
// NOTE: Hit testing follows reverse painting order, that's why the conditions here are reversed.
|
||||
for (ssize_t i = m_children.size() - 1; i >= 0; --i) {
|
||||
auto const& child = *m_children[i];
|
||||
if (child.m_box->computed_values().z_index().value_or(0) <= 0)
|
||||
if (child.paintable_box().computed_values().z_index().value_or(0) <= 0)
|
||||
break;
|
||||
auto result = child.hit_test(transformed_position, type);
|
||||
if (result.has_value() && result->paintable->visible_for_hit_testing())
|
||||
|
@ -571,8 +555,7 @@ Optional<HitTestResult> StackingContext::hit_test(CSSPixelPoint position, HitTes
|
|||
}
|
||||
|
||||
auto const& z_index = paintable_box.computed_values().z_index();
|
||||
auto& layout_box = paintable_box.layout_box();
|
||||
if (z_index.value_or(0) == 0 && layout_box.is_positioned() && !paintable_box.stacking_context()) {
|
||||
if (z_index.value_or(0) == 0 && paintable_box.is_positioned() && !paintable_box.stacking_context()) {
|
||||
auto candidate = paintable_box.hit_test(transformed_position, type);
|
||||
if (candidate.has_value() && candidate->paintable->visible_for_hit_testing()) {
|
||||
result = move(candidate);
|
||||
|
@ -596,7 +579,7 @@ Optional<HitTestResult> StackingContext::hit_test(CSSPixelPoint position, HitTes
|
|||
return result;
|
||||
|
||||
// 5. the in-flow, inline-level, non-positioned descendants, including inline tables and inline blocks.
|
||||
if (m_box->children_are_inline() && is<Layout::BlockContainer>(*m_box)) {
|
||||
if (paintable_box().layout_box().children_are_inline() && is<Layout::BlockContainer>(paintable_box().layout_box())) {
|
||||
auto result = paintable_box().hit_test(transformed_position, type);
|
||||
if (result.has_value() && result->paintable->visible_for_hit_testing())
|
||||
return result;
|
||||
|
@ -610,8 +593,7 @@ Optional<HitTestResult> StackingContext::hit_test(CSSPixelPoint position, HitTes
|
|||
return TraversalDecision::SkipChildrenAndContinue;
|
||||
}
|
||||
|
||||
auto& layout_box = paintable_box.layout_box();
|
||||
if (layout_box.is_floating()) {
|
||||
if (paintable_box.is_floating()) {
|
||||
if (auto candidate = paintable_box.hit_test(transformed_position, type); candidate.has_value()) {
|
||||
result = move(candidate);
|
||||
return TraversalDecision::Break;
|
||||
|
@ -623,7 +605,7 @@ Optional<HitTestResult> StackingContext::hit_test(CSSPixelPoint position, HitTes
|
|||
return result;
|
||||
|
||||
// 3. the in-flow, non-inline-level, non-positioned descendants.
|
||||
if (!m_box->children_are_inline()) {
|
||||
if (!paintable_box().layout_box().children_are_inline()) {
|
||||
for_each_in_subtree_of_type_within_same_stacking_context_in_reverse<PaintableBox>(paintable_box(), [&](PaintableBox const& paintable_box) {
|
||||
// FIXME: Support more overflow variations.
|
||||
if (paintable_box.computed_values().overflow_x() == CSS::Overflow::Hidden && paintable_box.computed_values().overflow_y() == CSS::Overflow::Hidden) {
|
||||
|
@ -631,8 +613,7 @@ Optional<HitTestResult> StackingContext::hit_test(CSSPixelPoint position, HitTes
|
|||
return TraversalDecision::SkipChildrenAndContinue;
|
||||
}
|
||||
|
||||
auto& layout_box = paintable_box.layout_box();
|
||||
if (!layout_box.is_absolutely_positioned() && !layout_box.is_floating()) {
|
||||
if (!paintable_box.is_absolutely_positioned() && !paintable_box.is_floating()) {
|
||||
if (auto candidate = paintable_box.hit_test(transformed_position, type); candidate.has_value()) {
|
||||
result = move(candidate);
|
||||
return TraversalDecision::Break;
|
||||
|
@ -648,7 +629,7 @@ Optional<HitTestResult> StackingContext::hit_test(CSSPixelPoint position, HitTes
|
|||
// NOTE: Hit testing follows reverse painting order, that's why the conditions here are reversed.
|
||||
for (ssize_t i = m_children.size() - 1; i >= 0; --i) {
|
||||
auto const& child = *m_children[i];
|
||||
if (child.m_box->computed_values().z_index().value_or(0) >= 0)
|
||||
if (child.paintable_box().computed_values().z_index().value_or(0) >= 0)
|
||||
break;
|
||||
auto result = child.hit_test(transformed_position, type);
|
||||
if (result.has_value() && result->paintable->visible_for_hit_testing())
|
||||
|
@ -670,9 +651,9 @@ 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(), paintable_box().absolute_rect(), m_children.size());
|
||||
if (m_box->computed_values().z_index().has_value())
|
||||
builder.appendff("{}", m_box->computed_values().z_index().value());
|
||||
builder.appendff("SC for {} {} [children: {}] (z-index: ", paintable_box().layout_box().debug_description(), paintable_box().absolute_rect(), m_children.size());
|
||||
if (paintable_box().computed_values().z_index().has_value())
|
||||
builder.appendff("{}", paintable_box().computed_values().z_index().value());
|
||||
else
|
||||
builder.append("auto"sv);
|
||||
builder.append(')');
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue