mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-24 21:45:20 +00:00
GTextEditor: Unbreak selection painting in the new line-wrapping world
To expand a bit on how the line-wrapping works, each physical line of text is broken up into multiple visual lines. This is recomputed when the document changes, or when the widget is resized. Each GTextEditor::Line keeps track of the visual breaking points, and also their visual rect in content coordinates. This allows us to do painting and hit testing reasonably efficiently for now. This code needs some cleanup, but it's finally in a working state, so here it goes. :^)
This commit is contained in:
parent
7b5bcec562
commit
3ca1c72c77
Notes:
sideshowbarker
2024-07-19 12:32:37 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/3ca1c72c77f
1 changed files with 82 additions and 17 deletions
|
@ -355,26 +355,87 @@ void GTextEditor::paint_event(GPaintEvent& event)
|
||||||
};
|
};
|
||||||
painter.add_clip_rect(text_clip_rect);
|
painter.add_clip_rect(text_clip_rect);
|
||||||
|
|
||||||
for (int i = first_visible_line; i <= last_visible_line; ++i) {
|
for (int line_index = first_visible_line; line_index <= last_visible_line; ++line_index) {
|
||||||
auto& line = m_lines[i];
|
auto& line = m_lines[line_index];
|
||||||
line.for_each_visual_line([&](const Rect& line_rect, const StringView& visual_line_text, int) {
|
|
||||||
|
bool physical_line_has_selection = has_selection && line_index >= selection.start().line() && line_index <= selection.end().line();
|
||||||
|
int first_visual_line_with_selection = -1;
|
||||||
|
int last_visual_line_with_selection = -1;
|
||||||
|
if (physical_line_has_selection) {
|
||||||
|
if (selection.start().line() < line_index) {
|
||||||
|
first_visual_line_with_selection = 0;
|
||||||
|
} else {
|
||||||
|
int visual_line_index = 0;
|
||||||
|
line.for_each_visual_line([&](const Rect&, const StringView& view, int start_of_visual_line) {
|
||||||
|
if (selection.start().column() >= start_of_visual_line && ((selection.start().column() - start_of_visual_line) < view.length()))
|
||||||
|
return IterationDecision::Break;
|
||||||
|
++visual_line_index;
|
||||||
|
return IterationDecision::Continue;
|
||||||
|
});
|
||||||
|
first_visual_line_with_selection = visual_line_index;
|
||||||
|
}
|
||||||
|
if (selection.end().line() > line_index) {
|
||||||
|
last_visual_line_with_selection = line.m_visual_line_breaks.size();
|
||||||
|
} else {
|
||||||
|
int visual_line_index = 0;
|
||||||
|
line.for_each_visual_line([&](const Rect&, const StringView& view, int start_of_visual_line) {
|
||||||
|
if (selection.end().column() >= start_of_visual_line && ((selection.end().column() - start_of_visual_line) < view.length()))
|
||||||
|
return IterationDecision::Break;
|
||||||
|
++visual_line_index;
|
||||||
|
return IterationDecision::Continue;
|
||||||
|
});
|
||||||
|
last_visual_line_with_selection = visual_line_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int selection_start_column_within_line = selection.start().line() == line_index ? selection.start().column() : 0;
|
||||||
|
int selection_end_column_within_line = selection.end().line() == line_index ? selection.end().column() : line.length();
|
||||||
|
|
||||||
|
int visual_line_index = 0;
|
||||||
|
line.for_each_visual_line([&](const Rect& visual_line_rect, const StringView& visual_line_text, int start_of_visual_line) {
|
||||||
// FIXME: Make sure we always fill the entire line.
|
// FIXME: Make sure we always fill the entire line.
|
||||||
//line_rect.set_width(exposed_width);
|
//line_rect.set_width(exposed_width);
|
||||||
if (is_multi_line() && i == m_cursor.line())
|
if (is_multi_line() && line_index == m_cursor.line())
|
||||||
painter.fill_rect(line_rect, Color(230, 230, 230));
|
painter.fill_rect(visual_line_rect, Color(230, 230, 230));
|
||||||
painter.draw_text(line_rect, visual_line_text, m_text_alignment, Color::Black);
|
painter.draw_text(visual_line_rect, visual_line_text, m_text_alignment, Color::Black);
|
||||||
bool line_has_selection = has_selection && i >= selection.start().line() && i <= selection.end().line();
|
bool physical_line_has_selection = has_selection && line_index >= selection.start().line() && line_index <= selection.end().line();
|
||||||
if (line_has_selection) {
|
if (physical_line_has_selection) {
|
||||||
int selection_start_column_on_line = selection.start().line() == i ? selection.start().column() : 0;
|
|
||||||
int selection_end_column_on_line = selection.end().line() == i ? selection.end().column() : line.length();
|
|
||||||
|
|
||||||
int selection_left = content_x_for_position({ i, selection_start_column_on_line });
|
bool current_visual_line_has_selection = (line_index != selection.start().line() && line_index != selection.end().line())
|
||||||
int selection_right = content_x_for_position({ i, selection_end_column_on_line });
|
|| (visual_line_index >= first_visual_line_with_selection && visual_line_index <= last_visual_line_with_selection);
|
||||||
|
if (current_visual_line_has_selection) {
|
||||||
|
bool selection_begins_on_current_visual_line = visual_line_index == first_visual_line_with_selection;
|
||||||
|
bool selection_ends_on_current_visual_line = visual_line_index == last_visual_line_with_selection;
|
||||||
|
|
||||||
Rect selection_rect { selection_left, line_rect.y(), selection_right - selection_left, line_rect.height() };
|
int selection_left = selection_begins_on_current_visual_line
|
||||||
painter.fill_rect(selection_rect, Color::from_rgb(0x955233));
|
? content_x_for_position({ line_index, selection_start_column_within_line })
|
||||||
painter.draw_text(selection_rect, StringView(line.characters() + selection_start_column_on_line, line.length() - selection_start_column_on_line - (line.length() - selection_end_column_on_line)), TextAlignment::CenterLeft, Color::White);
|
: m_horizontal_content_padding;
|
||||||
|
|
||||||
|
int selection_right = selection_ends_on_current_visual_line
|
||||||
|
? content_x_for_position({ line_index, selection_end_column_within_line })
|
||||||
|
: visual_line_rect.right();
|
||||||
|
|
||||||
|
Rect selection_rect {
|
||||||
|
selection_left,
|
||||||
|
visual_line_rect.y(),
|
||||||
|
selection_right - selection_left,
|
||||||
|
visual_line_rect.height()
|
||||||
|
};
|
||||||
|
|
||||||
|
painter.fill_rect(selection_rect, Color::from_rgb(0x955233));
|
||||||
|
|
||||||
|
int start_of_selection_within_visual_line = max(0, selection_start_column_within_line - start_of_visual_line);
|
||||||
|
int end_of_selection_within_visual_line = selection_end_column_within_line - start_of_visual_line;
|
||||||
|
|
||||||
|
StringView visual_selected_text {
|
||||||
|
visual_line_text.characters_without_null_termination() + start_of_selection_within_visual_line,
|
||||||
|
end_of_selection_within_visual_line - start_of_selection_within_visual_line
|
||||||
|
};
|
||||||
|
|
||||||
|
painter.draw_text(selection_rect, visual_selected_text, TextAlignment::CenterLeft, Color::White);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
++visual_line_index;
|
||||||
return IterationDecision::Continue;
|
return IterationDecision::Continue;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -712,7 +773,7 @@ int GTextEditor::content_x_for_position(const GTextPosition& position) const
|
||||||
switch (m_text_alignment) {
|
switch (m_text_alignment) {
|
||||||
case TextAlignment::CenterLeft:
|
case TextAlignment::CenterLeft:
|
||||||
line.for_each_visual_line([&](const Rect&, const StringView& view, int start_of_visual_line) {
|
line.for_each_visual_line([&](const Rect&, const StringView& view, int start_of_visual_line) {
|
||||||
if (position.column() >= start_of_visual_line && ((position.column() - start_of_visual_line) < view.length())) {
|
if (position.column() >= start_of_visual_line && ((position.column() - start_of_visual_line) <= view.length())) {
|
||||||
x_offset = (position.column() - start_of_visual_line) * glyph_width();
|
x_offset = (position.column() - start_of_visual_line) * glyph_width();
|
||||||
return IterationDecision::Break;
|
return IterationDecision::Break;
|
||||||
}
|
}
|
||||||
|
@ -746,7 +807,7 @@ Rect GTextEditor::content_rect_for_position(const GTextPosition& position) const
|
||||||
auto& line = m_lines[position.line()];
|
auto& line = m_lines[position.line()];
|
||||||
Rect rect;
|
Rect rect;
|
||||||
line.for_each_visual_line([&](const Rect& visual_line_rect, const StringView& view, int start_of_visual_line) {
|
line.for_each_visual_line([&](const Rect& visual_line_rect, const StringView& view, int start_of_visual_line) {
|
||||||
if (position.column() >= start_of_visual_line && ((position.column() - start_of_visual_line) < view.length())) {
|
if (position.column() >= start_of_visual_line && ((position.column() - start_of_visual_line) <= view.length())) {
|
||||||
// NOTE: We have to subtract the horizontal padding here since it's part of the visual line rect
|
// NOTE: We have to subtract the horizontal padding here since it's part of the visual line rect
|
||||||
// *and* included in what we get from content_x_for_position().
|
// *and* included in what we get from content_x_for_position().
|
||||||
rect = {
|
rect = {
|
||||||
|
@ -1141,6 +1202,10 @@ void GTextEditor::did_update_selection()
|
||||||
m_copy_action->set_enabled(has_selection());
|
m_copy_action->set_enabled(has_selection());
|
||||||
if (on_selection_change)
|
if (on_selection_change)
|
||||||
on_selection_change();
|
on_selection_change();
|
||||||
|
if (is_line_wrapping_enabled()) {
|
||||||
|
// FIXME: Try to repaint less.
|
||||||
|
update();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GTextEditor::context_menu_event(GContextMenuEvent& event)
|
void GTextEditor::context_menu_event(GContextMenuEvent& event)
|
||||||
|
|
Loading…
Add table
Reference in a new issue