diff --git a/Libraries/LibHTML/HtmlView.cpp b/Libraries/LibHTML/HtmlView.cpp index a8948f8f18..b77b547f6e 100644 --- a/Libraries/LibHTML/HtmlView.cpp +++ b/Libraries/LibHTML/HtmlView.cpp @@ -64,7 +64,6 @@ void HtmlView::set_document(Document* new_document) }; } - #ifdef HTML_DEBUG if (document != nullptr) { dbgprintf("\033[33;1mLayout tree before layout:\033[0m\n"); @@ -159,6 +158,11 @@ void HtmlView::mousemove_event(GMouseEvent& event) is_hovering_link = true; } } + if (m_in_mouse_selection) { + layout_root()->selection().set_end({ result.layout_node, result.index_in_node }); + dump_selection("MouseMove"); + update(); + } } if (window()) window()->set_override_cursor(is_hovering_link ? GStandardCursor::Hand : GStandardCursor::None); @@ -196,6 +200,12 @@ void HtmlView::mousedown_event(GMouseEvent& event) dbg() << "HtmlView: clicking on a link to " << link->href(); if (on_link_click) on_link_click(link->href()); + } else { + if (event.button() == GMouseButton::Left) { + layout_root()->selection().set({ result.layout_node, result.index_in_node }, {}); + dump_selection("MouseDown"); + m_in_mouse_selection = true; + } } } } @@ -204,6 +214,17 @@ void HtmlView::mousedown_event(GMouseEvent& event) event.accept(); } +void HtmlView::mouseup_event(GMouseEvent& event) +{ + if (!layout_root()) + return GScrollableWidget::mouseup_event(event); + + if (event.button() == GMouseButton::Left) { + dump_selection("MouseUp"); + m_in_mouse_selection = false; + } +} + void HtmlView::keydown_event(GKeyEvent& event) { if (event.modifiers() == 0) { @@ -352,3 +373,10 @@ const Document* HtmlView::document() const { return main_frame().document(); } + +void HtmlView::dump_selection(const char* event_name) +{ + dbg() << event_name << " selection start: " + << layout_root()->selection().start().layout_node << ":" << layout_root()->selection().start().index_in_node << ", end: " + << layout_root()->selection().end().layout_node << ":" << layout_root()->selection().end().index_in_node; +} diff --git a/Libraries/LibHTML/HtmlView.h b/Libraries/LibHTML/HtmlView.h index 189133a79e..ea2b8b733e 100644 --- a/Libraries/LibHTML/HtmlView.h +++ b/Libraries/LibHTML/HtmlView.h @@ -43,12 +43,15 @@ protected: virtual void paint_event(GPaintEvent&) override; virtual void mousemove_event(GMouseEvent&) override; virtual void mousedown_event(GMouseEvent&) override; + virtual void mouseup_event(GMouseEvent&) override; virtual void keydown_event(GKeyEvent&) override; private: void layout_and_sync_size(); + void dump_selection(const char* event_name); RefPtr m_main_frame; bool m_should_show_line_box_borders { false }; + bool m_in_mouse_selection { false }; }; diff --git a/Libraries/LibHTML/Layout/LayoutBlock.cpp b/Libraries/LibHTML/Layout/LayoutBlock.cpp index 3ba37a9088..f5cb6f3935 100644 --- a/Libraries/LibHTML/Layout/LayoutBlock.cpp +++ b/Libraries/LibHTML/Layout/LayoutBlock.cpp @@ -293,7 +293,7 @@ HitTestResult LayoutBlock::hit_test(const Point& position) const for (auto& line_box : m_line_boxes) { for (auto& fragment : line_box.fragments()) { if (enclosing_int_rect(fragment.rect()).contains(position)) { - return { fragment.layout_node() }; + return { fragment.layout_node(), fragment.text_index_at(position.x()) }; } } } diff --git a/Libraries/LibHTML/Layout/LayoutDocument.h b/Libraries/LibHTML/Layout/LayoutDocument.h index ec1ce9d276..18b44c21be 100644 --- a/Libraries/LibHTML/Layout/LayoutDocument.h +++ b/Libraries/LibHTML/Layout/LayoutDocument.h @@ -12,5 +12,9 @@ public: virtual const char* class_name() const override { return "LayoutDocument"; } virtual void layout() override; + const LayoutRange& selection() const { return m_selection; } + LayoutRange& selection() { return m_selection; } + private: + LayoutRange m_selection; }; diff --git a/Libraries/LibHTML/Layout/LayoutNode.h b/Libraries/LibHTML/Layout/LayoutNode.h index ad504df269..f4e2181068 100644 --- a/Libraries/LibHTML/Layout/LayoutNode.h +++ b/Libraries/LibHTML/Layout/LayoutNode.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -19,6 +20,7 @@ class Node; struct HitTestResult { RefPtr layout_node; + int index_in_node { 0 }; }; class LayoutNode : public TreeNode { diff --git a/Libraries/LibHTML/Layout/LayoutPosition.h b/Libraries/LibHTML/Layout/LayoutPosition.h new file mode 100644 index 0000000000..30fac23368 --- /dev/null +++ b/Libraries/LibHTML/Layout/LayoutPosition.h @@ -0,0 +1,56 @@ +#pragma once + +#include + +class LayoutNode; + +struct LayoutPosition { + bool operator>=(const LayoutPosition& other) const + { + if (layout_node == other.layout_node) + return index_in_node >= other.index_in_node; + + // FIXME: Implement. + return true; + } + + bool operator<=(const LayoutPosition& other) const + { + if (layout_node == other.layout_node) + return index_in_node <= other.index_in_node; + + // FIXME: Implement. + return false; + } + + RefPtr layout_node; + int index_in_node { 0 }; +}; + +class LayoutRange { +public: + LayoutRange() {} + LayoutRange(const LayoutPosition& start, const LayoutPosition& end) + : m_start(start) + , m_end(end) + { + } + + bool is_valid() const { return m_start.layout_node && m_end.layout_node; } + + void set(const LayoutPosition& start, const LayoutPosition& end) + { + m_start = start; + m_end = end; + } + + void set_start(const LayoutPosition& start) { m_start = start; } + void set_end(const LayoutPosition& end) { m_end = end; } + + const LayoutPosition& start() const { return m_start; } + const LayoutPosition& end() const { return m_end; } + +private: + LayoutPosition m_start; + LayoutPosition m_end; +}; diff --git a/Libraries/LibHTML/Layout/LineBoxFragment.cpp b/Libraries/LibHTML/Layout/LineBoxFragment.cpp index c987483b2b..40c88e1764 100644 --- a/Libraries/LibHTML/Layout/LineBoxFragment.cpp +++ b/Libraries/LibHTML/Layout/LineBoxFragment.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -26,3 +27,24 @@ StringView LineBoxFragment::text() const return {}; return to(layout_node()).node().data().substring_view(m_start, m_length); } + +int LineBoxFragment::text_index_at(float x) const +{ + if (!layout_node().is_text()) + return 0; + auto& layout_text = to(layout_node()); + auto& font = layout_text.style().font(); + Utf8View view(text()); + + float relative_x = x - m_rect.location().x(); + float glyph_spacing = font.glyph_spacing(); + + float width_so_far = 0; + for (auto it = view.begin(); it != view.end(); ++it) { + float glyph_width = font.glyph_or_emoji_width(*it); + if ((width_so_far + glyph_width + glyph_spacing) > relative_x) + return m_start + view.byte_offset_of(it); + width_so_far += glyph_width + glyph_spacing; + } + return m_start + m_length - 1; +} diff --git a/Libraries/LibHTML/Layout/LineBoxFragment.h b/Libraries/LibHTML/Layout/LineBoxFragment.h index 0b12cce81f..565f960f9e 100644 --- a/Libraries/LibHTML/Layout/LineBoxFragment.h +++ b/Libraries/LibHTML/Layout/LineBoxFragment.h @@ -29,6 +29,8 @@ public: bool is_justifiable_whitespace() const; StringView text() const; + int text_index_at(float x) const; + private: const LayoutNode& m_layout_node; int m_start { 0 };