mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 02:47:34 +00:00
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.
This commit is contained in:
parent
2762e3d80a
commit
706fc3d1aa
3 changed files with 88 additions and 0 deletions
|
@ -84,6 +84,7 @@ void LayoutText::paint_fragment(PaintContext& context, const LineBoxFragment& fr
|
||||||
if (is_underline)
|
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);
|
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 = m_text_for_rendering;
|
||||||
auto text_transform = specified_style().string_or_fallback(CSS::PropertyID::TextTransform, "none");
|
auto text_transform = specified_style().string_or_fallback(CSS::PropertyID::TextTransform, "none");
|
||||||
if (text_transform == "uppercase")
|
if (text_transform == "uppercase")
|
||||||
|
@ -92,6 +93,14 @@ void LayoutText::paint_fragment(PaintContext& context, const LineBoxFragment& fr
|
||||||
text = m_text_for_rendering.to_lowercase();
|
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);
|
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>
|
template<typename Callback>
|
||||||
|
|
|
@ -95,4 +95,80 @@ int LineBoxFragment::text_index_at(float x) const
|
||||||
return m_start + m_length - 1;
|
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 {};
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
|
|
||||||
#include <AK/Weakable.h>
|
#include <AK/Weakable.h>
|
||||||
#include <LibGfx/FloatRect.h>
|
#include <LibGfx/FloatRect.h>
|
||||||
|
#include <LibGfx/Forward.h>
|
||||||
#include <LibWeb/Forward.h>
|
#include <LibWeb/Forward.h>
|
||||||
|
|
||||||
namespace Web {
|
namespace Web {
|
||||||
|
@ -68,6 +69,8 @@ public:
|
||||||
|
|
||||||
int text_index_at(float x) const;
|
int text_index_at(float x) const;
|
||||||
|
|
||||||
|
Gfx::FloatRect selection_rect(const Gfx::Font&) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const LayoutNode& m_layout_node;
|
const LayoutNode& m_layout_node;
|
||||||
int m_start { 0 };
|
int m_start { 0 };
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue