diff options
author | Andreas Kling <kling@serenityos.org> | 2020-06-29 00:09:32 +0200 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2020-06-29 00:39:51 +0200 |
commit | 706fc3d1aa4647e2c7c2a454cbca4e8646097f0e (patch) | |
tree | 57160479dcc296ef0d7b79d5b7077ed9ab61e447 /Libraries | |
parent | 2762e3d80a935f1fc2cedeec11a19a19e477a1d8 (diff) | |
download | serenity-706fc3d1aa4647e2c7c2a454cbca4e8646097f0e.zip |
LibWeb: Paint the text selection :^)
Text selection currently works at the LayoutNode level. The root of the
layout tree has a LayoutRange selection() which in turn has two
LayoutPosition objects: start() and end().
A LayoutPosition is a LayoutNode + a text offset into that node.
We handle the selection painting in LayoutText::paint_fragment(), after
the normal painting is finished. The basic mechanism is that each
LayoutFragment is queried for its selection_rect(), and if a non-empty
rect is returned, we clip to it and paint the text once more.
Diffstat (limited to 'Libraries')
-rw-r--r-- | Libraries/LibWeb/Layout/LayoutText.cpp | 9 | ||||
-rw-r--r-- | Libraries/LibWeb/Layout/LineBoxFragment.cpp | 76 | ||||
-rw-r--r-- | Libraries/LibWeb/Layout/LineBoxFragment.h | 3 |
3 files changed, 88 insertions, 0 deletions
diff --git a/Libraries/LibWeb/Layout/LayoutText.cpp b/Libraries/LibWeb/Layout/LayoutText.cpp index 71ee4d9fe8..4199a50d0f 100644 --- a/Libraries/LibWeb/Layout/LayoutText.cpp +++ b/Libraries/LibWeb/Layout/LayoutText.cpp @@ -84,6 +84,7 @@ void LayoutText::paint_fragment(PaintContext& context, const LineBoxFragment& fr if (is_underline) painter.draw_line(enclosing_int_rect(fragment.absolute_rect()).bottom_left().translated(0, 1), enclosing_int_rect(fragment.absolute_rect()).bottom_right().translated(0, 1), color); + // FIXME: text-transform should be done already in layout, since uppercase glyphs may be wider than lowercase, etc. auto text = m_text_for_rendering; auto text_transform = specified_style().string_or_fallback(CSS::PropertyID::TextTransform, "none"); if (text_transform == "uppercase") @@ -92,6 +93,14 @@ void LayoutText::paint_fragment(PaintContext& context, const LineBoxFragment& fr text = m_text_for_rendering.to_lowercase(); painter.draw_text(enclosing_int_rect(fragment.absolute_rect()), text.substring_view(fragment.start(), fragment.length()), Gfx::TextAlignment::TopLeft, color); + + auto selection_rect = fragment.selection_rect(specified_style().font()); + if (!selection_rect.is_empty()) { + painter.fill_rect(enclosing_int_rect(selection_rect), context.palette().selection()); + Gfx::PainterStateSaver saver(painter); + painter.add_clip_rect(enclosing_int_rect(selection_rect)); + painter.draw_text(enclosing_int_rect(fragment.absolute_rect()), text.substring_view(fragment.start(), fragment.length()), Gfx::TextAlignment::TopLeft, context.palette().selection_text()); + } } template<typename Callback> diff --git a/Libraries/LibWeb/Layout/LineBoxFragment.cpp b/Libraries/LibWeb/Layout/LineBoxFragment.cpp index 714348fd50..651a3a251d 100644 --- a/Libraries/LibWeb/Layout/LineBoxFragment.cpp +++ b/Libraries/LibWeb/Layout/LineBoxFragment.cpp @@ -95,4 +95,80 @@ int LineBoxFragment::text_index_at(float x) const return m_start + m_length - 1; } +Gfx::FloatRect LineBoxFragment::selection_rect(const Gfx::Font& font) const +{ + auto& selection = layout_node().root().selection(); + if (!selection.is_valid()) + return {}; + if (!layout_node().is_text()) + return {}; + + const auto start_index = m_start; + const auto end_index = m_start + m_length; + + if (&layout_node() == selection.start().layout_node && &layout_node() == selection.end().layout_node) { + // we are in the start/end node (both the same) + if (start_index > selection.end().index_in_node) + return {}; + if (end_index < selection.start().index_in_node) + return {}; + + auto selection_start_in_this_fragment = selection.start().index_in_node - m_start; + auto selection_end_in_this_fragment = selection.end().index_in_node - m_start + 1; + auto pixel_distance_to_first_selected_character = font.width(text().substring_view(0, selection_start_in_this_fragment)); + auto pixel_width_of_selection = font.width(text().substring_view(selection_start_in_this_fragment, selection_end_in_this_fragment - selection_start_in_this_fragment)); + + auto rect = absolute_rect(); + rect.set_x(rect.x() + pixel_distance_to_first_selected_character); + rect.set_width(pixel_width_of_selection); + + return rect; + } + if (&layout_node() == selection.start().layout_node) { + // we are in the start node + if (end_index < selection.start().index_in_node) + return {}; + + auto selection_start_in_this_fragment = selection.start().index_in_node - m_start; + auto selection_end_in_this_fragment = m_length; + auto pixel_distance_to_first_selected_character = font.width(text().substring_view(0, selection_start_in_this_fragment)); + auto pixel_width_of_selection = font.width(text().substring_view(selection_start_in_this_fragment, selection_end_in_this_fragment - selection_start_in_this_fragment)); + + auto rect = absolute_rect(); + rect.set_x(rect.x() + pixel_distance_to_first_selected_character); + rect.set_width(pixel_width_of_selection); + + return rect; + } + if (&layout_node() == selection.end().layout_node) { + // we are in the end node + if (start_index > selection.end().index_in_node) + return {}; + + auto selection_start_in_this_fragment = 0; + auto selection_end_in_this_fragment = selection.end().index_in_node + 1; + auto pixel_distance_to_first_selected_character = font.width(text().substring_view(0, selection_start_in_this_fragment)); + auto pixel_width_of_selection = font.width(text().substring_view(selection_start_in_this_fragment, selection_end_in_this_fragment - selection_start_in_this_fragment)); + + auto rect = absolute_rect(); + rect.set_x(rect.x() + pixel_distance_to_first_selected_character); + rect.set_width(pixel_width_of_selection); + + 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 {}; +} + } diff --git a/Libraries/LibWeb/Layout/LineBoxFragment.h b/Libraries/LibWeb/Layout/LineBoxFragment.h index 71d840c52e..5d08e1b72f 100644 --- a/Libraries/LibWeb/Layout/LineBoxFragment.h +++ b/Libraries/LibWeb/Layout/LineBoxFragment.h @@ -28,6 +28,7 @@ #include <AK/Weakable.h> #include <LibGfx/FloatRect.h> +#include <LibGfx/Forward.h> #include <LibWeb/Forward.h> namespace Web { @@ -68,6 +69,8 @@ public: int text_index_at(float x) const; + Gfx::FloatRect selection_rect(const Gfx::Font&) const; + private: const LayoutNode& m_layout_node; int m_start { 0 }; |