Browse Source

LibWeb: Remember the selection state of each LayoutNode

Instead of computing it on the fly while painting each layout node,
they now remember their selection state. This avoids a whole bunch
of tree traversal while painting with anything selected.
Andreas Kling 4 years ago
parent
commit
d47f77169f

+ 1 - 0
Libraries/LibWeb/InProcessWebView.cpp

@@ -110,6 +110,7 @@ void InProcessWebView::select_all()
         last_layout_node_index_in_node = downcast<LayoutText>(*last_layout_node).text_for_rendering().length() - 1;
 
     layout_root->selection().set({ first_layout_node, 0 }, { last_layout_node, last_layout_node_index_in_node });
+    layout_root->recompute_selection_states();
     update();
 }
 

+ 26 - 0
Libraries/LibWeb/Layout/LayoutDocument.cpp

@@ -120,4 +120,30 @@ HitTestResult LayoutDocument::hit_test(const Gfx::IntPoint& position, HitTestTyp
     return stacking_context()->hit_test(position, type);
 }
 
+void LayoutDocument::recompute_selection_states()
+{
+    SelectionState state = SelectionState::None;
+
+    auto selection = this->selection().normalized();
+
+    for_each_in_subtree([&](auto& layout_node) {
+        if (!selection.is_valid()) {
+            // Everything gets SelectionState::None.
+        } else if (&layout_node == selection.start().layout_node && &layout_node == selection.end().layout_node) {
+            state = SelectionState::StartAndEnd;
+        } else if (&layout_node == selection.start().layout_node) {
+            state = SelectionState::Start;
+        } else if (&layout_node == selection.end().layout_node) {
+            state = SelectionState::End;
+        } else {
+            if (state == SelectionState::Start)
+                state = SelectionState::Full;
+            else if (state == SelectionState::End || state == SelectionState::StartAndEnd)
+                state = SelectionState::None;
+        }
+        layout_node.set_selection_state(state);
+        return IterationDecision::Continue;
+    });
+}
+
 }

+ 2 - 0
Libraries/LibWeb/Layout/LayoutDocument.h

@@ -54,6 +54,8 @@ public:
 
     void build_stacking_context_tree();
 
+    void recompute_selection_states();
+
 private:
     LayoutRange m_selection;
 };

+ 20 - 0
Libraries/LibWeb/Layout/LayoutNode.h

@@ -43,6 +43,14 @@ namespace Web {
 struct HitTestResult {
     RefPtr<LayoutNode> layout_node;
     int index_in_node { 0 };
+
+    enum InternalPosition {
+        None,
+        Before,
+        Inside,
+        After,
+    };
+    InternalPosition internal_position { None };
 };
 
 enum class HitTestType {
@@ -142,6 +150,17 @@ public:
 
     float font_size() const;
 
+    enum class SelectionState {
+        None,        // No selection
+        Start,       // Selection starts in this LayoutNode
+        End,         // Selection ends in this LayoutNode
+        StartAndEnd, // Selection starts and ends in this LayoutNode
+        Full,        // Selection starts before and ends after this LayoutNode
+    };
+
+    SelectionState selection_state() const { return m_selection_state; }
+    void set_selection_state(SelectionState state) { m_selection_state = state; }
+
 protected:
     LayoutNode(DOM::Document&, DOM::Node*);
 
@@ -155,6 +174,7 @@ private:
     bool m_has_style { false };
     bool m_visible { true };
     bool m_children_are_inline { false };
+    SelectionState m_selection_state { SelectionState::None };
 };
 
 class LayoutNodeWithStyle : public LayoutNode {

+ 9 - 15
Libraries/LibWeb/Layout/LineBoxFragment.cpp

@@ -100,6 +100,12 @@ int LineBoxFragment::text_index_at(float x) const
 
 Gfx::FloatRect LineBoxFragment::selection_rect(const Gfx::Font& font) const
 {
+    if (layout_node().selection_state() == LayoutNode::SelectionState::None)
+        return {};
+
+    if (layout_node().selection_state() == LayoutNode::SelectionState::Full)
+        return absolute_rect();
+
     auto selection = layout_node().root().selection().normalized();
     if (!selection.is_valid())
         return {};
@@ -110,7 +116,7 @@ Gfx::FloatRect LineBoxFragment::selection_rect(const Gfx::Font& font) const
     const auto end_index = m_start + m_length;
     auto text = this->text();
 
-    if (&layout_node() == selection.start().layout_node && &layout_node() == selection.end().layout_node) {
+    if (layout_node().selection_state() == LayoutNode::SelectionState::StartAndEnd) {
         // we are in the start/end node (both the same)
         if (start_index > selection.end().index_in_node)
             return {};
@@ -128,7 +134,7 @@ Gfx::FloatRect LineBoxFragment::selection_rect(const Gfx::Font& font) const
 
         return rect;
     }
-    if (&layout_node() == selection.start().layout_node) {
+    if (layout_node().selection_state() == LayoutNode::SelectionState::Start) {
         // we are in the start node
         if (end_index < selection.start().index_in_node)
             return {};
@@ -144,7 +150,7 @@ Gfx::FloatRect LineBoxFragment::selection_rect(const Gfx::Font& font) const
 
         return rect;
     }
-    if (&layout_node() == selection.end().layout_node) {
+    if (layout_node().selection_state() == LayoutNode::SelectionState::End) {
         // we are in the end node
         if (start_index > selection.end().index_in_node)
             return {};
@@ -160,18 +166,6 @@ Gfx::FloatRect LineBoxFragment::selection_rect(const Gfx::Font& font) const
 
         return rect;
     }
-
-    // are we in between start and end?
-    auto* node = selection.start().layout_node.ptr();
-    bool is_fully_selected = false;
-    for (; node && node != selection.end().layout_node.ptr(); node = node->next_in_pre_order()) {
-        if (node == &layout_node()) {
-            is_fully_selected = true;
-            break;
-        }
-    }
-    if (is_fully_selected)
-        return absolute_rect();
     return {};
 }
 

+ 2 - 0
Libraries/LibWeb/Page/EventHandler.cpp

@@ -157,6 +157,7 @@ bool EventHandler::handle_mousedown(const Gfx::IntPoint& position, unsigned butt
             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 }, {});
+                layout_root()->recompute_selection_states();
                 dump_selection("MouseDown");
                 m_in_mouse_selection = true;
             }
@@ -209,6 +210,7 @@ bool EventHandler::handle_mousemove(const Gfx::IntPoint& position, unsigned butt
             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 });
+                layout_root()->recompute_selection_states();
             }
             dump_selection("MouseMove");
             page_client.page_did_change_selection();