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 };