mirror of
https://github.com/RGBCube/serenity
synced 2025-07-28 13:07:46 +00:00
LibWeb: Move hit testing to the painting tree
This commit is contained in:
parent
ba606d9057
commit
5779a910e5
18 changed files with 196 additions and 172 deletions
|
@ -29,31 +29,6 @@ BlockContainer::~BlockContainer()
|
|||
{
|
||||
}
|
||||
|
||||
HitTestResult BlockContainer::hit_test(const Gfx::IntPoint& position, HitTestType type) const
|
||||
{
|
||||
if (!children_are_inline())
|
||||
return Box::hit_test(position, type);
|
||||
|
||||
HitTestResult last_good_candidate;
|
||||
for (auto& line_box : paint_box()->line_boxes()) {
|
||||
for (auto& fragment : line_box.fragments()) {
|
||||
if (is<Box>(fragment.layout_node()) && verify_cast<Box>(fragment.layout_node()).paint_box()->stacking_context())
|
||||
continue;
|
||||
if (enclosing_int_rect(fragment.absolute_rect()).contains(position)) {
|
||||
if (is<BlockContainer>(fragment.layout_node()))
|
||||
return verify_cast<BlockContainer>(fragment.layout_node()).hit_test(position, type);
|
||||
return { fragment.layout_node().paintable(), fragment.text_index_at(position.x()) };
|
||||
}
|
||||
if (fragment.absolute_rect().top() <= position.y())
|
||||
last_good_candidate = { fragment.layout_node().paintable(), fragment.text_index_at(position.x()) };
|
||||
}
|
||||
}
|
||||
|
||||
if (type == HitTestType::TextCursor && last_good_candidate.paintable)
|
||||
return last_good_candidate;
|
||||
return { paint_box()->absolute_border_box_rect().contains(position.x(), position.y()) ? paintable() : nullptr };
|
||||
}
|
||||
|
||||
bool BlockContainer::is_scrollable() const
|
||||
{
|
||||
// FIXME: Support horizontal scroll as well (overflow-x)
|
||||
|
|
|
@ -18,8 +18,6 @@ public:
|
|||
BlockContainer(DOM::Document&, DOM::Node*, CSS::ComputedValues);
|
||||
virtual ~BlockContainer() override;
|
||||
|
||||
virtual HitTestResult hit_test(const Gfx::IntPoint&, HitTestType) const override;
|
||||
|
||||
BlockContainer* previous_sibling() { return verify_cast<BlockContainer>(Node::previous_sibling()); }
|
||||
const BlockContainer* previous_sibling() const { return verify_cast<BlockContainer>(Node::previous_sibling()); }
|
||||
BlockContainer* next_sibling() { return verify_cast<BlockContainer>(Node::next_sibling()); }
|
||||
|
|
|
@ -57,20 +57,6 @@ bool Box::is_out_of_flow(FormattingContext const& formatting_context) const
|
|||
return false;
|
||||
}
|
||||
|
||||
HitTestResult Box::hit_test(const Gfx::IntPoint& position, HitTestType type) const
|
||||
{
|
||||
// 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 { paint_box()->absolute_border_box_rect().contains(position.x(), position.y()) ? paint_box() : nullptr };
|
||||
for_each_child_in_paint_order([&](auto& child) {
|
||||
auto child_result = child.hit_test(position, type);
|
||||
if (child_result.paintable)
|
||||
result = child_result;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
void Box::set_needs_display()
|
||||
{
|
||||
if (!is_inline()) {
|
||||
|
|
|
@ -24,7 +24,6 @@ public:
|
|||
|
||||
bool is_out_of_flow(FormattingContext const&) const;
|
||||
|
||||
virtual HitTestResult hit_test(const Gfx::IntPoint&, HitTestType) const override;
|
||||
virtual void set_needs_display() override;
|
||||
|
||||
bool is_body() const;
|
||||
|
|
|
@ -47,11 +47,6 @@ void InitialContainingBlock::paint_all_phases(PaintContext& context)
|
|||
paint_box()->stacking_context()->paint(context);
|
||||
}
|
||||
|
||||
HitTestResult InitialContainingBlock::hit_test(const Gfx::IntPoint& position, HitTestType type) const
|
||||
{
|
||||
return paint_box()->stacking_context()->hit_test(position, type);
|
||||
}
|
||||
|
||||
void InitialContainingBlock::recompute_selection_states()
|
||||
{
|
||||
SelectionState state = SelectionState::None;
|
||||
|
|
|
@ -20,8 +20,6 @@ public:
|
|||
|
||||
void paint_all_phases(PaintContext&);
|
||||
|
||||
virtual HitTestResult hit_test(const Gfx::IntPoint&, HitTestType) const override;
|
||||
|
||||
const LayoutRange& selection() const { return m_selection; }
|
||||
void set_selection(const LayoutRange&);
|
||||
void set_selection_end(const LayoutPosition&);
|
||||
|
|
|
@ -80,11 +80,6 @@ bool Node::establishes_stacking_context() const
|
|||
return computed_values().opacity() < 1.0f;
|
||||
}
|
||||
|
||||
HitTestResult Node::hit_test(Gfx::IntPoint const&, HitTestType) const
|
||||
{
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
HTML::BrowsingContext const& Node::browsing_context() const
|
||||
{
|
||||
VERIFY(document().browsing_context());
|
||||
|
|
|
@ -26,30 +26,10 @@ enum class LayoutMode {
|
|||
OnlyRequiredLineBreaks,
|
||||
};
|
||||
|
||||
struct HitTestResult {
|
||||
RefPtr<Painting::Paintable> paintable;
|
||||
int index_in_node { 0 };
|
||||
|
||||
enum InternalPosition {
|
||||
None,
|
||||
Before,
|
||||
Inside,
|
||||
After,
|
||||
};
|
||||
InternalPosition internal_position { None };
|
||||
};
|
||||
|
||||
enum class HitTestType {
|
||||
Exact, // Exact matches only
|
||||
TextCursor, // Clicking past the right/bottom edge of text will still hit the text
|
||||
};
|
||||
|
||||
class Node : public TreeNode<Node> {
|
||||
public:
|
||||
virtual ~Node();
|
||||
|
||||
virtual HitTestResult hit_test(const Gfx::IntPoint&, HitTestType) const;
|
||||
|
||||
bool is_anonymous() const { return !m_dom_node; }
|
||||
const DOM::Node* dom_node() const { return m_dom_node; }
|
||||
DOM::Node* dom_node() { return m_dom_node; }
|
||||
|
@ -144,72 +124,6 @@ public:
|
|||
SelectionState selection_state() const { return m_selection_state; }
|
||||
void set_selection_state(SelectionState state) { m_selection_state = state; }
|
||||
|
||||
template<typename Callback>
|
||||
void for_each_child_in_paint_order(Callback callback) const
|
||||
{
|
||||
// Element traversal using the order defined in https://www.w3.org/TR/CSS2/zindex.html#painting-order.
|
||||
// Note: Some steps are skipped because they are not relevant to node traversal.
|
||||
|
||||
// 3. Stacking contexts formed by positioned descendants with negative z-indices (excluding 0) in z-index order
|
||||
// (most negative first) then tree order.
|
||||
// FIXME: This does not retrieve elements in the z-index order.
|
||||
for_each_child([&](auto& child) {
|
||||
if (!child.is_positioned() || !is<Box>(child))
|
||||
return;
|
||||
|
||||
auto& box_child = verify_cast<Box>(child);
|
||||
auto* stacking_context = box_child.paint_box()->stacking_context();
|
||||
if (stacking_context && box_child.computed_values().z_index().has_value() && box_child.computed_values().z_index().value() < 0)
|
||||
callback(child);
|
||||
});
|
||||
|
||||
// 4. For all its in-flow, non-positioned, block-level descendants in tree order: If the element is a block, list-item,
|
||||
// or other block equivalent:
|
||||
for_each_child([&](auto& child) {
|
||||
if (is<Box>(child) && verify_cast<Box>(child).paint_box()->stacking_context())
|
||||
return;
|
||||
if (!child.is_positioned())
|
||||
callback(child);
|
||||
});
|
||||
|
||||
// 5. All non-positioned floating descendants, in tree order. For each one of these, treat the element as if it created
|
||||
// a new stacking context, but any positioned descendants and descendants which actually create a new stacking context
|
||||
// should be considered part of the parent stacking context, not this new one.
|
||||
for_each_child([&](auto& child) {
|
||||
if (is<Box>(child) && verify_cast<Box>(child).paint_box()->stacking_context())
|
||||
return;
|
||||
if (child.is_positioned())
|
||||
callback(child);
|
||||
});
|
||||
|
||||
// 8. All positioned descendants with 'z-index: auto' or 'z-index: 0', in tree order. For those with 'z-index: auto', treat
|
||||
// the element as if it created a new stacking context, but any positioned descendants and descendants which actually
|
||||
// create a new stacking context should be considered part of the parent stacking context, not this new one. For those
|
||||
// with 'z-index: 0', treat the stacking context generated atomically.
|
||||
for_each_child([&](auto& child) {
|
||||
if (!child.is_positioned() || !is<Box>(child))
|
||||
return;
|
||||
|
||||
auto& box_child = verify_cast<Box>(child);
|
||||
auto* stacking_context = box_child.paint_box()->stacking_context();
|
||||
if (stacking_context && box_child.computed_values().z_index().has_value() && box_child.computed_values().z_index().value() == 0)
|
||||
callback(child);
|
||||
});
|
||||
|
||||
// 9. Stacking contexts formed by positioned descendants with z-indices greater than or equal to 1 in z-index order
|
||||
// (smallest first) then tree order.
|
||||
// FIXME: This does not retrieve elements in the z-index order.
|
||||
for_each_child([&](auto& child) {
|
||||
if (!child.is_positioned() || !is<Box>(child))
|
||||
return;
|
||||
|
||||
auto& box_child = verify_cast<Box>(child);
|
||||
auto* stacking_context = box_child.paint_box()->stacking_context();
|
||||
if (stacking_context && box_child.computed_values().z_index().has_value() && box_child.computed_values().z_index().value() > 0)
|
||||
callback(child);
|
||||
});
|
||||
}
|
||||
|
||||
protected:
|
||||
Node(DOM::Document&, DOM::Node*);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue