From 2eb2207f5049e6b154e3601faca1b7d69b62bf8a Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Mon, 20 Feb 2023 12:31:48 -0500 Subject: [PATCH] LibGUI: Convert mouse events from a visual to a physical position When clicking a position within a TextEditor, we should interpret that position as a visual location. That location should be converted to a "physical" location before using it to set the physical cursor position. For example, consider a document with 2 emoji, each consisting of 3 code points. Visually, these will occupy 2 columns. When a mouse click occurs between these columns, we need to convert the visual column number 1 to the physical column number 3 when storing the new cursor location. --- Userland/Libraries/LibGUI/TextEditor.cpp | 26 +++++++++++++++++++++--- Userland/Libraries/LibGUI/TextEditor.h | 1 + 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/Userland/Libraries/LibGUI/TextEditor.cpp b/Userland/Libraries/LibGUI/TextEditor.cpp index 43088e7b6b..8f81c896ff 100644 --- a/Userland/Libraries/LibGUI/TextEditor.cpp +++ b/Userland/Libraries/LibGUI/TextEditor.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -276,7 +277,7 @@ void TextEditor::mousedown_event(MouseEvent& event) m_in_drag_select = true; - set_cursor(text_position_at(event.position())); + set_cursor_to_text_position(event.position()); if (!(event.modifiers() & Mod_Shift)) { if (!has_selection()) @@ -306,7 +307,7 @@ void TextEditor::mousemove_event(MouseEvent& event) m_last_mousemove_position = event.position(); if (m_in_drag_select) { auto constrained = event.position().constrained(widget_inner_rect()); - set_cursor(text_position_at(constrained)); + set_cursor_to_text_position(constrained); m_selection.set_end(m_cursor); did_update_selection(); update(); @@ -336,7 +337,7 @@ void TextEditor::automatic_scrolling_timer_did_fire() set_automatic_scrolling_timer_active(false); return; } - set_cursor(text_position_at(m_last_mousemove_position)); + set_cursor_to_text_position(m_last_mousemove_position); m_selection.set_end(m_cursor); did_update_selection(); update(); @@ -1395,6 +1396,25 @@ void TextEditor::set_cursor(TextPosition const& a_position) update(); } +void TextEditor::set_cursor_to_text_position(Gfx::IntPoint position) +{ + auto visual_position = text_position_at(position); + size_t physical_column = 0; + + auto const& line = document().line(visual_position.line()); + size_t boundary_index = 0; + + Unicode::for_each_grapheme_segmentation_boundary(line.view(), [&](auto boundary) { + physical_column = boundary; + + if (boundary_index++ >= visual_position.column()) + return IterationDecision::Break; + return IterationDecision::Continue; + }); + + set_cursor({ visual_position.line(), physical_column }); +} + void TextEditor::focusin_event(FocusEvent& event) { if (event.source() == FocusSource::Keyboard) diff --git a/Userland/Libraries/LibGUI/TextEditor.h b/Userland/Libraries/LibGUI/TextEditor.h index 903dbd9046..4892794f29 100644 --- a/Userland/Libraries/LibGUI/TextEditor.h +++ b/Userland/Libraries/LibGUI/TextEditor.h @@ -190,6 +190,7 @@ public: void set_cursor_and_focus_line(size_t line, size_t column); void set_cursor(size_t line, size_t column); virtual void set_cursor(TextPosition const&); + void set_cursor_to_text_position(Gfx::IntPoint); Syntax::Highlighter* syntax_highlighter(); Syntax::Highlighter const* syntax_highlighter() const;