mirror of
https://github.com/RGBCube/serenity
synced 2025-05-16 09:44:57 +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;
|
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->selection().set({ first_layout_node, 0 }, { last_layout_node, last_layout_node_index_in_node });
|
||||||
|
layout_root->recompute_selection_states();
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -120,4 +120,30 @@ HitTestResult LayoutDocument::hit_test(const Gfx::IntPoint& position, HitTestTyp
|
||||||
return stacking_context()->hit_test(position, type);
|
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 build_stacking_context_tree();
|
||||||
|
|
||||||
|
void recompute_selection_states();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
LayoutRange m_selection;
|
LayoutRange m_selection;
|
||||||
};
|
};
|
||||||
|
|
|
@ -43,6 +43,14 @@ namespace Web {
|
||||||
struct HitTestResult {
|
struct HitTestResult {
|
||||||
RefPtr<LayoutNode> layout_node;
|
RefPtr<LayoutNode> layout_node;
|
||||||
int index_in_node { 0 };
|
int index_in_node { 0 };
|
||||||
|
|
||||||
|
enum InternalPosition {
|
||||||
|
None,
|
||||||
|
Before,
|
||||||
|
Inside,
|
||||||
|
After,
|
||||||
|
};
|
||||||
|
InternalPosition internal_position { None };
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class HitTestType {
|
enum class HitTestType {
|
||||||
|
@ -142,6 +150,17 @@ public:
|
||||||
|
|
||||||
float font_size() const;
|
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:
|
protected:
|
||||||
LayoutNode(DOM::Document&, DOM::Node*);
|
LayoutNode(DOM::Document&, DOM::Node*);
|
||||||
|
|
||||||
|
@ -155,6 +174,7 @@ private:
|
||||||
bool m_has_style { false };
|
bool m_has_style { false };
|
||||||
bool m_visible { true };
|
bool m_visible { true };
|
||||||
bool m_children_are_inline { false };
|
bool m_children_are_inline { false };
|
||||||
|
SelectionState m_selection_state { SelectionState::None };
|
||||||
};
|
};
|
||||||
|
|
||||||
class LayoutNodeWithStyle : public LayoutNode {
|
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
|
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();
|
auto selection = layout_node().root().selection().normalized();
|
||||||
if (!selection.is_valid())
|
if (!selection.is_valid())
|
||||||
return {};
|
return {};
|
||||||
|
@ -110,7 +116,7 @@ Gfx::FloatRect LineBoxFragment::selection_rect(const Gfx::Font& font) const
|
||||||
const auto end_index = m_start + m_length;
|
const auto end_index = m_start + m_length;
|
||||||
auto text = this->text();
|
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)
|
// we are in the start/end node (both the same)
|
||||||
if (start_index > selection.end().index_in_node)
|
if (start_index > selection.end().index_in_node)
|
||||||
return {};
|
return {};
|
||||||
|
@ -128,7 +134,7 @@ Gfx::FloatRect LineBoxFragment::selection_rect(const Gfx::Font& font) const
|
||||||
|
|
||||||
return rect;
|
return rect;
|
||||||
}
|
}
|
||||||
if (&layout_node() == selection.start().layout_node) {
|
if (layout_node().selection_state() == LayoutNode::SelectionState::Start) {
|
||||||
// we are in the start node
|
// we are in the start node
|
||||||
if (end_index < selection.start().index_in_node)
|
if (end_index < selection.start().index_in_node)
|
||||||
return {};
|
return {};
|
||||||
|
@ -144,7 +150,7 @@ Gfx::FloatRect LineBoxFragment::selection_rect(const Gfx::Font& font) const
|
||||||
|
|
||||||
return rect;
|
return rect;
|
||||||
}
|
}
|
||||||
if (&layout_node() == selection.end().layout_node) {
|
if (layout_node().selection_state() == LayoutNode::SelectionState::End) {
|
||||||
// we are in the end node
|
// we are in the end node
|
||||||
if (start_index > selection.end().index_in_node)
|
if (start_index > selection.end().index_in_node)
|
||||||
return {};
|
return {};
|
||||||
|
@ -160,18 +166,6 @@ Gfx::FloatRect LineBoxFragment::selection_rect(const Gfx::Font& font) const
|
||||||
|
|
||||||
return rect;
|
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 {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -157,6 +157,7 @@ bool EventHandler::handle_mousedown(const Gfx::IntPoint& position, unsigned butt
|
||||||
if (result.layout_node && result.layout_node->node()) {
|
if (result.layout_node && result.layout_node->node()) {
|
||||||
m_frame.set_cursor_position(DOM::Position(*node, result.index_in_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()->selection().set({ result.layout_node, result.index_in_node }, {});
|
||||||
|
layout_root()->recompute_selection_states();
|
||||||
dump_selection("MouseDown");
|
dump_selection("MouseDown");
|
||||||
m_in_mouse_selection = true;
|
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);
|
auto hit = layout_root()->hit_test(position, HitTestType::TextCursor);
|
||||||
if (hit.layout_node && hit.layout_node->node()) {
|
if (hit.layout_node && hit.layout_node->node()) {
|
||||||
layout_root()->selection().set_end({ hit.layout_node, hit.index_in_node });
|
layout_root()->selection().set_end({ hit.layout_node, hit.index_in_node });
|
||||||
|
layout_root()->recompute_selection_states();
|
||||||
}
|
}
|
||||||
dump_selection("MouseMove");
|
dump_selection("MouseMove");
|
||||||
page_client.page_did_change_selection();
|
page_client.page_did_change_selection();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue