diff --git a/Libraries/LibWeb/Layout/LayoutBlock.cpp b/Libraries/LibWeb/Layout/LayoutBlock.cpp index 0a702f197f..8f04c170e7 100644 --- a/Libraries/LibWeb/Layout/LayoutBlock.cpp +++ b/Libraries/LibWeb/Layout/LayoutBlock.cpp @@ -717,10 +717,10 @@ void LayoutBlock::paint(PaintContext& context, PaintPhase phase) } } -HitTestResult LayoutBlock::hit_test(const Gfx::IntPoint& position) const +HitTestResult LayoutBlock::hit_test(const Gfx::IntPoint& position, HitTestType type) const { if (!children_are_inline()) - return LayoutBox::hit_test(position); + return LayoutBox::hit_test(position, type); HitTestResult last_good_candidate; for (auto& line_box : m_line_boxes) { @@ -729,7 +729,7 @@ HitTestResult LayoutBlock::hit_test(const Gfx::IntPoint& position) const continue; if (enclosing_int_rect(fragment.absolute_rect()).contains(position)) { if (fragment.layout_node().is_block()) - return downcast(fragment.layout_node()).hit_test(position); + return downcast(fragment.layout_node()).hit_test(position, type); return { fragment.layout_node(), fragment.text_index_at(position.x()) }; } if (fragment.absolute_rect().top() <= position.y()) @@ -737,7 +737,7 @@ HitTestResult LayoutBlock::hit_test(const Gfx::IntPoint& position) const } } - if (last_good_candidate.layout_node) + if (type == HitTestType::TextCursor && last_good_candidate.layout_node) return last_good_candidate; return { absolute_rect().contains(position.x(), position.y()) ? this : nullptr }; } diff --git a/Libraries/LibWeb/Layout/LayoutBlock.h b/Libraries/LibWeb/Layout/LayoutBlock.h index 5b31fbf585..aa663628b7 100644 --- a/Libraries/LibWeb/Layout/LayoutBlock.h +++ b/Libraries/LibWeb/Layout/LayoutBlock.h @@ -49,7 +49,7 @@ public: LineBox& ensure_last_line_box(); LineBox& add_line_box(); - virtual HitTestResult hit_test(const Gfx::IntPoint&) const override; + virtual HitTestResult hit_test(const Gfx::IntPoint&, HitTestType) const override; LayoutBlock* previous_sibling() { return downcast(LayoutNode::previous_sibling()); } const LayoutBlock* previous_sibling() const { return downcast(LayoutNode::previous_sibling()); } diff --git a/Libraries/LibWeb/Layout/LayoutBox.cpp b/Libraries/LibWeb/Layout/LayoutBox.cpp index e87ae9c4e5..343173708b 100644 --- a/Libraries/LibWeb/Layout/LayoutBox.cpp +++ b/Libraries/LibWeb/Layout/LayoutBox.cpp @@ -224,7 +224,7 @@ void LayoutBox::paint(PaintContext& context, PaintPhase phase) } } -HitTestResult LayoutBox::hit_test(const Gfx::IntPoint& position) const +HitTestResult LayoutBox::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 @@ -233,7 +233,7 @@ HitTestResult LayoutBox::hit_test(const Gfx::IntPoint& position) const for_each_child([&](auto& child) { if (is(child) && downcast(child).stacking_context()) return; - auto child_result = child.hit_test(position); + auto child_result = child.hit_test(position, type); if (child_result.layout_node) result = child_result; }); diff --git a/Libraries/LibWeb/Layout/LayoutBox.h b/Libraries/LibWeb/Layout/LayoutBox.h index 93e9fb4287..004175ee99 100644 --- a/Libraries/LibWeb/Layout/LayoutBox.h +++ b/Libraries/LibWeb/Layout/LayoutBox.h @@ -55,7 +55,7 @@ public: float absolute_y() const { return absolute_rect().y(); } Gfx::FloatPoint absolute_position() const { return absolute_rect().location(); } - virtual HitTestResult hit_test(const Gfx::IntPoint& absolute_position) const override; + virtual HitTestResult hit_test(const Gfx::IntPoint&, HitTestType) const override; virtual void set_needs_display() override; bool is_body() const; diff --git a/Libraries/LibWeb/Layout/LayoutDocument.cpp b/Libraries/LibWeb/Layout/LayoutDocument.cpp index 90c364ba49..b3255082c5 100644 --- a/Libraries/LibWeb/Layout/LayoutDocument.cpp +++ b/Libraries/LibWeb/Layout/LayoutDocument.cpp @@ -113,9 +113,9 @@ void LayoutDocument::paint(PaintContext& context, PaintPhase phase) stacking_context()->paint(context, phase); } -HitTestResult LayoutDocument::hit_test(const Gfx::IntPoint& position) const +HitTestResult LayoutDocument::hit_test(const Gfx::IntPoint& position, HitTestType type) const { - return stacking_context()->hit_test(position); + return stacking_context()->hit_test(position, type); } } diff --git a/Libraries/LibWeb/Layout/LayoutDocument.h b/Libraries/LibWeb/Layout/LayoutDocument.h index 5e6206f13e..79dbd7312a 100644 --- a/Libraries/LibWeb/Layout/LayoutDocument.h +++ b/Libraries/LibWeb/Layout/LayoutDocument.h @@ -43,7 +43,7 @@ public: void paint_all_phases(PaintContext&); virtual void paint(PaintContext&, PaintPhase) override; - virtual HitTestResult hit_test(const Gfx::IntPoint&) const override; + virtual HitTestResult hit_test(const Gfx::IntPoint&, HitTestType) const override; const LayoutRange& selection() const { return m_selection; } LayoutRange& selection() { return m_selection; } diff --git a/Libraries/LibWeb/Layout/LayoutNode.cpp b/Libraries/LibWeb/Layout/LayoutNode.cpp index 04b9f5eff8..5bd0aebd64 100644 --- a/Libraries/LibWeb/Layout/LayoutNode.cpp +++ b/Libraries/LibWeb/Layout/LayoutNode.cpp @@ -102,7 +102,7 @@ void LayoutNode::paint(PaintContext& context, PaintPhase phase) }); } -HitTestResult LayoutNode::hit_test(const Gfx::IntPoint& position) const +HitTestResult LayoutNode::hit_test(const Gfx::IntPoint& position, HitTestType type) const { HitTestResult result; for_each_child([&](auto& child) { @@ -110,7 +110,7 @@ HitTestResult LayoutNode::hit_test(const Gfx::IntPoint& position) const // The outer loop who called us will take care of those. if (is(child) && downcast(child).stacking_context()) return; - auto child_result = child.hit_test(position); + auto child_result = child.hit_test(position, type); if (child_result.layout_node) result = child_result; }); diff --git a/Libraries/LibWeb/Layout/LayoutNode.h b/Libraries/LibWeb/Layout/LayoutNode.h index 872521edc0..a3c7919b19 100644 --- a/Libraries/LibWeb/Layout/LayoutNode.h +++ b/Libraries/LibWeb/Layout/LayoutNode.h @@ -45,11 +45,16 @@ struct HitTestResult { int index_in_node { 0 }; }; +enum class HitTestType { + Exact, // Exact matches only + TextCursor, // Clicking past the right/bottom edge of text will still hit the text +}; + class LayoutNode : public TreeNode { public: virtual ~LayoutNode(); - virtual HitTestResult hit_test(const Gfx::IntPoint&) const; + virtual HitTestResult hit_test(const Gfx::IntPoint&, HitTestType) const; bool is_anonymous() const { return !m_node; } const DOM::Node* node() const { return m_node; } diff --git a/Libraries/LibWeb/Page/EventHandler.cpp b/Libraries/LibWeb/Page/EventHandler.cpp index de22ce3cf9..2ce05d9524 100644 --- a/Libraries/LibWeb/Page/EventHandler.cpp +++ b/Libraries/LibWeb/Page/EventHandler.cpp @@ -77,7 +77,7 @@ bool EventHandler::handle_mouseup(const Gfx::IntPoint& position, unsigned button return false; bool handled_event = false; - auto result = layout_root()->hit_test(position); + auto result = layout_root()->hit_test(position, HitTestType::Exact); if (result.layout_node && result.layout_node->node()) { RefPtr node = result.layout_node->node(); if (is(*node)) { @@ -104,7 +104,7 @@ bool EventHandler::handle_mousedown(const Gfx::IntPoint& position, unsigned butt NonnullRefPtr document = *m_frame.document(); auto& page_client = m_frame.page().client(); - auto result = layout_root()->hit_test(position); + auto result = layout_root()->hit_test(position, HitTestType::Exact); if (!result.layout_node) return false; @@ -151,10 +151,13 @@ bool EventHandler::handle_mousedown(const Gfx::IntPoint& position, unsigned butt } } else { if (button == GUI::MouseButton::Left) { - m_frame.set_cursor_position(DOM::Position(*node, result.index_in_node)); - layout_root()->selection().set({ result.layout_node, result.index_in_node }, {}); - dump_selection("MouseDown"); - m_in_mouse_selection = true; + auto result = layout_root()->hit_test(position, HitTestType::TextCursor); + if (result.layout_node && result.layout_node->node()) { + m_frame.set_cursor_position(DOM::Position(*node, result.index_in_node)); + layout_root()->selection().set({ result.layout_node, result.index_in_node }, {}); + dump_selection("MouseDown"); + m_in_mouse_selection = true; + } } else if (button == GUI::MouseButton::Right) { page_client.page_did_request_context_menu(m_frame.to_main_frame_position(position)); } @@ -171,7 +174,7 @@ bool EventHandler::handle_mousemove(const Gfx::IntPoint& position, unsigned butt bool hovered_node_changed = false; bool is_hovering_link = false; - auto result = layout_root()->hit_test(position); + auto result = layout_root()->hit_test(position, HitTestType::Exact); const HTML::HTMLAnchorElement* hovered_link_element = nullptr; if (result.layout_node) { RefPtr node = result.layout_node->node(); @@ -198,7 +201,10 @@ bool EventHandler::handle_mousemove(const Gfx::IntPoint& position, unsigned butt return true; } if (m_in_mouse_selection) { - layout_root()->selection().set_end({ result.layout_node, result.index_in_node }); + auto hit = layout_root()->hit_test(position, HitTestType::TextCursor); + if (hit.layout_node && hit.layout_node->node()) { + layout_root()->selection().set_end({ hit.layout_node, hit.index_in_node }); + } dump_selection("MouseMove"); page_client.page_did_change_selection(); } diff --git a/Libraries/LibWeb/Painting/StackingContext.cpp b/Libraries/LibWeb/Painting/StackingContext.cpp index ed66da46a6..f55cfe355d 100644 --- a/Libraries/LibWeb/Painting/StackingContext.cpp +++ b/Libraries/LibWeb/Painting/StackingContext.cpp @@ -61,19 +61,19 @@ void StackingContext::paint(PaintContext& context, LayoutNode::PaintPhase phase) } } -HitTestResult StackingContext::hit_test(const Gfx::IntPoint& position) const +HitTestResult StackingContext::hit_test(const Gfx::IntPoint& position, HitTestType type) const { HitTestResult result; if (!m_box.is_root()) { - result = m_box.hit_test(position); + result = m_box.hit_test(position, type); } else { // NOTE: LayoutDocument::hit_test() merely calls StackingContext::hit_test() // so we call its base class instead. - result = downcast(m_box).LayoutBlock::hit_test(position); + result = downcast(m_box).LayoutBlock::hit_test(position, type); } for (auto* child : m_children) { - auto result_here = child->hit_test(position); + auto result_here = child->hit_test(position, type); if (result_here.layout_node) result = result_here; } diff --git a/Libraries/LibWeb/Painting/StackingContext.h b/Libraries/LibWeb/Painting/StackingContext.h index f817a5ff67..d7621c3829 100644 --- a/Libraries/LibWeb/Painting/StackingContext.h +++ b/Libraries/LibWeb/Painting/StackingContext.h @@ -41,7 +41,7 @@ public: const StackingContext* parent() const { return m_parent; } void paint(PaintContext&, LayoutNode::PaintPhase); - HitTestResult hit_test(const Gfx::IntPoint&) const; + HitTestResult hit_test(const Gfx::IntPoint&, HitTestType) const; void dump(int indent = 0) const;