mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 22:02:44 +00:00 
			
		
		
		
	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.
This commit is contained in:
		
							parent
							
								
									cf4870c93e
								
							
						
					
					
						commit
						d47f77169f
					
				
					 6 changed files with 60 additions and 15 deletions
				
			
		|  | @ -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(); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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; | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -54,6 +54,8 @@ public: | |||
| 
 | ||||
|     void build_stacking_context_tree(); | ||||
| 
 | ||||
|     void recompute_selection_states(); | ||||
| 
 | ||||
| private: | ||||
|     LayoutRange m_selection; | ||||
| }; | ||||
|  |  | |||
|  | @ -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 { | ||||
|  |  | |||
|  | @ -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 {}; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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(); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Andreas Kling
						Andreas Kling