1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 14:28:12 +00:00

LibWeb: Change StackingContext::hit_test() to accept callback

This change modifies hit_test() to no longer return the first paintable
encountered at a specified position. Instead, this function accepts a
callback that is invoked for each paintable located at a position, in
hit-testing order.

This modification will allow us to reuse this call for
`Document.elementsFromPoint()` in upcoming changes.
This commit is contained in:
Aliaksandr Kalenik 2024-02-13 21:34:07 +01:00 committed by Andreas Kling
parent 15d151ee66
commit 9c99182b1e
11 changed files with 120 additions and 104 deletions

View file

@ -719,17 +719,17 @@ Layout::BlockContainer& PaintableWithLines::layout_box()
return static_cast<Layout::BlockContainer&>(PaintableBox::layout_box());
}
Optional<HitTestResult> PaintableBox::hit_test(CSSPixelPoint position, HitTestType type) const
TraversalDecision PaintableBox::hit_test(CSSPixelPoint position, HitTestType type, Function<TraversalDecision(HitTestResult)> const& callback) const
{
if (clip_rect().has_value() && !clip_rect()->contains(position))
return {};
return TraversalDecision::Continue;
auto position_adjusted_by_scroll_offset = position;
if (enclosing_scroll_frame_offset().has_value())
position_adjusted_by_scroll_offset.translate_by(-enclosing_scroll_frame_offset().value());
if (!is_visible())
return {};
return TraversalDecision::Continue;
if (layout_box().is_viewport()) {
auto& viewport_paintable = const_cast<ViewportPaintable&>(static_cast<ViewportPaintable const&>(*this));
@ -737,46 +737,55 @@ Optional<HitTestResult> PaintableBox::hit_test(CSSPixelPoint position, HitTestTy
viewport_paintable.document().update_paint_and_hit_testing_properties_if_needed();
viewport_paintable.refresh_scroll_state();
viewport_paintable.refresh_clip_state();
return stacking_context()->hit_test(position, type);
return stacking_context()->hit_test(position, type, callback);
}
if (!absolute_border_box_rect().contains(position_adjusted_by_scroll_offset.x(), position_adjusted_by_scroll_offset.y()))
return {};
return TraversalDecision::Continue;
for (auto* child = first_child(); child; child = child->next_sibling()) {
auto result = child->hit_test(position, type);
if (!result.has_value())
for (auto const* child = last_child(); child; child = child->previous_sibling()) {
auto z_index = child->computed_values().z_index();
if (child->layout_node().is_positioned() && z_index.value_or(0) == 0)
continue;
if (!result->paintable->visible_for_hit_testing())
continue;
return result;
if (child->hit_test(position, type, callback) == TraversalDecision::Break)
return TraversalDecision::Break;
}
if (!visible_for_hit_testing())
return {};
return TraversalDecision::Continue;
return HitTestResult { const_cast<PaintableBox&>(*this) };
return callback(HitTestResult { const_cast<PaintableBox&>(*this) });
}
Optional<HitTestResult> PaintableWithLines::hit_test(CSSPixelPoint position, HitTestType type) const
Optional<HitTestResult> PaintableBox::hit_test(CSSPixelPoint position, HitTestType type) const
{
Optional<HitTestResult> result;
(void)PaintableBox::hit_test(position, type, [&](HitTestResult candidate) {
VERIFY(!result.has_value());
if (!candidate.paintable->visible_for_hit_testing())
return Painting::TraversalDecision::Continue;
result = move(candidate);
return Painting::TraversalDecision::Break;
});
return result;
}
TraversalDecision PaintableWithLines::hit_test(CSSPixelPoint position, HitTestType type, Function<TraversalDecision(HitTestResult)> const& callback) const
{
if (clip_rect().has_value() && !clip_rect()->contains(position))
return {};
return TraversalDecision::Continue;
auto position_adjusted_by_scroll_offset = position;
if (enclosing_scroll_frame_offset().has_value())
position_adjusted_by_scroll_offset.translate_by(-enclosing_scroll_frame_offset().value());
if (!layout_box().children_are_inline() || m_fragments.is_empty())
return PaintableBox::hit_test(position, type);
if (!layout_box().children_are_inline() || m_fragments.is_empty()) {
return PaintableBox::hit_test(position, type, callback);
}
for (auto* child = first_child(); child; child = child->next_sibling()) {
auto result = child->hit_test(position, type);
if (!result.has_value())
continue;
if (!result->paintable->visible_for_hit_testing())
continue;
return result;
for (auto const* child = last_child(); child; child = child->previous_sibling()) {
if (child->hit_test(position, type, callback) == TraversalDecision::Break)
return TraversalDecision::Break;
}
Optional<HitTestResult> last_good_candidate;
@ -785,9 +794,11 @@ Optional<HitTestResult> PaintableWithLines::hit_test(CSSPixelPoint position, Hit
continue;
auto fragment_absolute_rect = fragment.absolute_rect();
if (fragment_absolute_rect.contains(position_adjusted_by_scroll_offset)) {
if (auto result = fragment.paintable().hit_test(position, type); result.has_value())
return result;
return HitTestResult { const_cast<Paintable&>(fragment.paintable()), fragment.text_index_at(position_adjusted_by_scroll_offset.x()) };
if (fragment.paintable().hit_test(position, type, callback) == TraversalDecision::Break)
return TraversalDecision::Break;
HitTestResult hit_test_result { const_cast<Paintable&>(fragment.paintable()), fragment.text_index_at(position_adjusted_by_scroll_offset.x()) };
if (callback(hit_test_result) == TraversalDecision::Break)
return TraversalDecision::Break;
}
// If we reached this point, the position is not within the fragment. However, the fragment start or end might be the place to place the cursor.
@ -808,11 +819,16 @@ Optional<HitTestResult> PaintableWithLines::hit_test(CSSPixelPoint position, Hit
}
}
if (type == HitTestType::TextCursor && last_good_candidate.has_value())
return last_good_candidate;
if (is_visible() && absolute_border_box_rect().contains(position_adjusted_by_scroll_offset.x(), position_adjusted_by_scroll_offset.y()))
return HitTestResult { const_cast<PaintableWithLines&>(*this) };
return {};
if (type == HitTestType::TextCursor && last_good_candidate.has_value()) {
if (callback(last_good_candidate.value()) == TraversalDecision::Break)
return TraversalDecision::Break;
}
if (!stacking_context() && is_visible() && absolute_border_box_rect().contains(position_adjusted_by_scroll_offset.x(), position_adjusted_by_scroll_offset.y())) {
if (callback(HitTestResult { const_cast<PaintableWithLines&>(*this) }) == TraversalDecision::Break)
return TraversalDecision::Break;
}
return TraversalDecision::Continue;
}
void PaintableBox::set_needs_display() const