mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-07 20:31:04 +03:00
LibWeb: Move selection state from layout tree to paint tree
Where we paint the selection is obviously paint-related information, so let's keep it in the paint tree.
This commit is contained in:
parent
d18a5b904d
commit
1987318cc2
Notes:
sideshowbarker
2024-07-17 11:29:41 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/1987318cc2 Pull-request: https://github.com/SerenityOS/serenity/pull/23626
@ -1110,7 +1110,7 @@ void Document::update_layout()
|
||||
page().client().page_did_layout();
|
||||
}
|
||||
|
||||
m_layout_root->recompute_selection_states();
|
||||
paintable()->recompute_selection_states();
|
||||
|
||||
m_needs_layout = false;
|
||||
m_layout_update_timer->stop();
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include <LibWeb/Layout/Viewport.h>
|
||||
#include <LibWeb/Namespace.h>
|
||||
#include <LibWeb/Painting/Paintable.h>
|
||||
#include <LibWeb/Painting/ViewportPaintable.h>
|
||||
|
||||
namespace Web::DOM {
|
||||
|
||||
@ -97,9 +98,9 @@ void Range::update_associated_selection()
|
||||
{
|
||||
if (!m_associated_selection)
|
||||
return;
|
||||
if (auto* layout_root = m_associated_selection->document()->layout_node(); layout_root && layout_root->paintable()) {
|
||||
layout_root->recompute_selection_states();
|
||||
layout_root->paintable()->set_needs_display();
|
||||
if (auto* viewport = m_associated_selection->document()->paintable()) {
|
||||
viewport->recompute_selection_states();
|
||||
viewport->set_needs_display();
|
||||
}
|
||||
|
||||
// https://w3c.github.io/selection-api/#selectionchange-event
|
||||
|
@ -158,17 +158,6 @@ public:
|
||||
bool children_are_inline() const { return m_children_are_inline; }
|
||||
void set_children_are_inline(bool value) { m_children_are_inline = value; }
|
||||
|
||||
enum class SelectionState {
|
||||
None, // No selection
|
||||
Start, // Selection starts in this Node
|
||||
End, // Selection ends in this Node
|
||||
StartAndEnd, // Selection starts and ends in this Node
|
||||
Full, // Selection starts before and ends after this Node
|
||||
};
|
||||
|
||||
SelectionState selection_state() const { return m_selection_state; }
|
||||
void set_selection_state(SelectionState state) { m_selection_state = state; }
|
||||
|
||||
u32 initial_quote_nesting_level() const { return m_initial_quote_nesting_level; }
|
||||
void set_initial_quote_nesting_level(u32 value) { m_initial_quote_nesting_level = value; }
|
||||
|
||||
@ -190,7 +179,6 @@ private:
|
||||
bool m_anonymous { false };
|
||||
bool m_has_style { false };
|
||||
bool m_children_are_inline { false };
|
||||
SelectionState m_selection_state { SelectionState::None };
|
||||
|
||||
bool m_is_flex_item { false };
|
||||
bool m_is_grid_item { false };
|
||||
|
@ -20,77 +20,6 @@ Viewport::Viewport(DOM::Document& document, NonnullRefPtr<CSS::StyleProperties>
|
||||
|
||||
Viewport::~Viewport() = default;
|
||||
|
||||
JS::GCPtr<Selection::Selection> Viewport::selection() const
|
||||
{
|
||||
return const_cast<DOM::Document&>(document()).get_selection();
|
||||
}
|
||||
|
||||
void Viewport::recompute_selection_states()
|
||||
{
|
||||
// 1. Start by resetting the selection state of all layout nodes to None.
|
||||
for_each_in_inclusive_subtree([&](auto& layout_node) {
|
||||
layout_node.set_selection_state(SelectionState::None);
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
// 2. If there is no active Selection or selected Range, return.
|
||||
auto selection = document().get_selection();
|
||||
if (!selection)
|
||||
return;
|
||||
auto range = selection->range();
|
||||
if (!range)
|
||||
return;
|
||||
|
||||
auto* start_container = range->start_container();
|
||||
auto* end_container = range->end_container();
|
||||
|
||||
// 3. If the selection starts and ends in the same node:
|
||||
if (start_container == end_container) {
|
||||
// 1. If the selection starts and ends at the same offset, return.
|
||||
if (range->start_offset() == range->end_offset()) {
|
||||
// NOTE: A zero-length selection should not be visible.
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. If it's a text node, mark it as StartAndEnd and return.
|
||||
if (is<DOM::Text>(*start_container)) {
|
||||
if (auto* layout_node = start_container->layout_node()) {
|
||||
layout_node->set_selection_state(SelectionState::StartAndEnd);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (start_container == end_container && is<DOM::Text>(*start_container)) {
|
||||
if (auto* layout_node = start_container->layout_node()) {
|
||||
layout_node->set_selection_state(SelectionState::StartAndEnd);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. Mark the selection start node as Start (if text) or Full (if anything else).
|
||||
if (auto* layout_node = start_container->layout_node()) {
|
||||
if (is<DOM::Text>(*start_container))
|
||||
layout_node->set_selection_state(SelectionState::Start);
|
||||
else
|
||||
layout_node->set_selection_state(SelectionState::Full);
|
||||
}
|
||||
|
||||
// 5. Mark the selection end node as End (if text) or Full (if anything else).
|
||||
if (auto* layout_node = end_container->layout_node()) {
|
||||
if (is<DOM::Text>(*end_container))
|
||||
layout_node->set_selection_state(SelectionState::End);
|
||||
else
|
||||
layout_node->set_selection_state(SelectionState::Full);
|
||||
}
|
||||
|
||||
// 6. Mark the nodes between start node and end node (in tree order) as Full.
|
||||
for (auto* node = start_container->next_in_pre_order(); node && node != end_container; node = node->next_in_pre_order()) {
|
||||
if (auto* layout_node = node->layout_node())
|
||||
layout_node->set_selection_state(SelectionState::Full);
|
||||
}
|
||||
}
|
||||
|
||||
JS::GCPtr<Painting::Paintable> Viewport::create_paintable() const
|
||||
{
|
||||
return Painting::ViewportPaintable::create(*this);
|
||||
|
@ -8,7 +8,6 @@
|
||||
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/Layout/BlockContainer.h>
|
||||
#include <LibWeb/Selection/Selection.h>
|
||||
|
||||
namespace Web::Layout {
|
||||
|
||||
@ -21,10 +20,6 @@ public:
|
||||
|
||||
const DOM::Document& dom_node() const { return static_cast<const DOM::Document&>(*Node::dom_node()); }
|
||||
|
||||
JS::GCPtr<Selection::Selection> selection() const;
|
||||
|
||||
void recompute_selection_states();
|
||||
|
||||
private:
|
||||
virtual JS::GCPtr<Painting::Paintable> create_paintable() const override;
|
||||
|
||||
|
@ -206,6 +206,17 @@ public:
|
||||
|
||||
CSSPixelPoint box_type_agnostic_position() const;
|
||||
|
||||
enum class SelectionState : u8 {
|
||||
None, // No selection
|
||||
Start, // Selection starts in this Node
|
||||
End, // Selection ends in this Node
|
||||
StartAndEnd, // Selection starts and ends in this Node
|
||||
Full, // Selection starts before and ends after this Node
|
||||
};
|
||||
|
||||
SelectionState selection_state() const { return m_selection_state; }
|
||||
void set_selection_state(SelectionState state) { m_selection_state = state; }
|
||||
|
||||
protected:
|
||||
explicit Paintable(Layout::Node const&);
|
||||
|
||||
@ -219,6 +230,8 @@ private:
|
||||
|
||||
OwnPtr<StackingContext> m_stacking_context;
|
||||
|
||||
SelectionState m_selection_state { SelectionState::None };
|
||||
|
||||
bool m_visible : 1 { false };
|
||||
bool m_positioned : 1 { false };
|
||||
bool m_fixed_position : 1 { false };
|
||||
|
@ -61,16 +61,16 @@ int PaintableFragment::text_index_at(CSSPixels x) const
|
||||
|
||||
CSSPixelRect PaintableFragment::selection_rect(Gfx::Font const& font) const
|
||||
{
|
||||
if (layout_node().selection_state() == Layout::Node::SelectionState::None)
|
||||
if (paintable().selection_state() == Paintable::SelectionState::None)
|
||||
return {};
|
||||
|
||||
if (layout_node().selection_state() == Layout::Node::SelectionState::Full)
|
||||
if (paintable().selection_state() == Paintable::SelectionState::Full)
|
||||
return absolute_rect();
|
||||
|
||||
if (!is<Layout::TextNode>(layout_node()))
|
||||
return {};
|
||||
|
||||
auto selection = layout_node().root().selection();
|
||||
auto selection = paintable().document().get_selection();
|
||||
if (!selection)
|
||||
return {};
|
||||
auto range = selection->range();
|
||||
@ -84,7 +84,7 @@ CSSPixelRect PaintableFragment::selection_rect(Gfx::Font const& font) const
|
||||
auto& layout_text = verify_cast<Layout::TextNode>(layout_node());
|
||||
auto text = layout_text.text_for_rendering().bytes_as_string_view().substring_view(m_start, m_length);
|
||||
|
||||
if (layout_node().selection_state() == Layout::Node::SelectionState::StartAndEnd) {
|
||||
if (paintable().selection_state() == Paintable::SelectionState::StartAndEnd) {
|
||||
// we are in the start/end node (both the same)
|
||||
if (start_index > range->end_offset())
|
||||
return {};
|
||||
@ -105,7 +105,7 @@ CSSPixelRect PaintableFragment::selection_rect(Gfx::Font const& font) const
|
||||
|
||||
return rect;
|
||||
}
|
||||
if (layout_node().selection_state() == Layout::Node::SelectionState::Start) {
|
||||
if (paintable().selection_state() == Paintable::SelectionState::Start) {
|
||||
// we are in the start node
|
||||
if (end_index < range->start_offset())
|
||||
return {};
|
||||
@ -121,7 +121,7 @@ CSSPixelRect PaintableFragment::selection_rect(Gfx::Font const& font) const
|
||||
|
||||
return rect;
|
||||
}
|
||||
if (layout_node().selection_state() == Layout::Node::SelectionState::End) {
|
||||
if (paintable().selection_state() == Paintable::SelectionState::End) {
|
||||
// we are in the end node
|
||||
if (start_index > range->end_offset())
|
||||
return {};
|
||||
|
@ -4,11 +4,13 @@
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/DOM/Range.h>
|
||||
#include <LibWeb/Layout/Viewport.h>
|
||||
#include <LibWeb/Painting/SVGPaintable.h>
|
||||
#include <LibWeb/Painting/SVGSVGPaintable.h>
|
||||
#include <LibWeb/Painting/StackingContext.h>
|
||||
#include <LibWeb/Painting/ViewportPaintable.h>
|
||||
#include <LibWeb/Selection/Selection.h>
|
||||
|
||||
namespace Web::Painting {
|
||||
|
||||
@ -440,4 +442,75 @@ void ViewportPaintable::resolve_paint_only_properties()
|
||||
});
|
||||
}
|
||||
|
||||
JS::GCPtr<Selection::Selection> ViewportPaintable::selection() const
|
||||
{
|
||||
return const_cast<DOM::Document&>(document()).get_selection();
|
||||
}
|
||||
|
||||
void ViewportPaintable::recompute_selection_states()
|
||||
{
|
||||
// 1. Start by resetting the selection state of all layout nodes to None.
|
||||
for_each_in_inclusive_subtree([&](auto& layout_node) {
|
||||
layout_node.set_selection_state(SelectionState::None);
|
||||
return TraversalDecision::Continue;
|
||||
});
|
||||
|
||||
// 2. If there is no active Selection or selected Range, return.
|
||||
auto selection = document().get_selection();
|
||||
if (!selection)
|
||||
return;
|
||||
auto range = selection->range();
|
||||
if (!range)
|
||||
return;
|
||||
|
||||
auto* start_container = range->start_container();
|
||||
auto* end_container = range->end_container();
|
||||
|
||||
// 3. If the selection starts and ends in the same node:
|
||||
if (start_container == end_container) {
|
||||
// 1. If the selection starts and ends at the same offset, return.
|
||||
if (range->start_offset() == range->end_offset()) {
|
||||
// NOTE: A zero-length selection should not be visible.
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. If it's a text node, mark it as StartAndEnd and return.
|
||||
if (is<DOM::Text>(*start_container)) {
|
||||
if (auto* paintable = start_container->paintable()) {
|
||||
paintable->set_selection_state(SelectionState::StartAndEnd);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (start_container == end_container && is<DOM::Text>(*start_container)) {
|
||||
if (auto* paintable = start_container->paintable()) {
|
||||
paintable->set_selection_state(SelectionState::StartAndEnd);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. Mark the selection start node as Start (if text) or Full (if anything else).
|
||||
if (auto* paintable = start_container->paintable()) {
|
||||
if (is<DOM::Text>(*start_container))
|
||||
paintable->set_selection_state(SelectionState::Start);
|
||||
else
|
||||
paintable->set_selection_state(SelectionState::Full);
|
||||
}
|
||||
|
||||
// 5. Mark the selection end node as End (if text) or Full (if anything else).
|
||||
if (auto* paintable = end_container->paintable()) {
|
||||
if (is<DOM::Text>(*end_container))
|
||||
paintable->set_selection_state(SelectionState::End);
|
||||
else
|
||||
paintable->set_selection_state(SelectionState::Full);
|
||||
}
|
||||
|
||||
// 6. Mark the nodes between start node and end node (in tree order) as Full.
|
||||
for (auto* node = start_container->next_in_pre_order(); node && node != end_container; node = node->next_in_pre_order()) {
|
||||
if (auto* paintable = node->paintable())
|
||||
paintable->set_selection_state(SelectionState::Full);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -30,6 +30,9 @@ public:
|
||||
|
||||
void resolve_paint_only_properties();
|
||||
|
||||
JS::GCPtr<Selection::Selection> selection() const;
|
||||
void recompute_selection_states();
|
||||
|
||||
private:
|
||||
void build_stacking_context_tree();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user