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:
Andreas Kling 2020-08-21 17:50:41 +02:00
parent cf4870c93e
commit d47f77169f
Notes: sideshowbarker 2024-07-19 03:21:04 +09:00
6 changed files with 60 additions and 15 deletions

View file

@ -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();
}

View file

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

View file

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

View file

@ -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 {

View file

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

View file

@ -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();