From 4774b57e4d03f23a22fdd41a7db536f0c6158ef4 Mon Sep 17 00:00:00 2001 From: AndrewYuan34 Date: Fri, 28 Mar 2025 03:21:20 -0400 Subject: [PATCH] Add auto-scroll behavior when selecting text outside the visible area in RichTextLabel --- scene/gui/rich_text_label.cpp | 197 ++++++++++++++++++++-------------- scene/gui/rich_text_label.h | 15 +++ 2 files changed, 132 insertions(+), 80 deletions(-) diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 9555d651a70..27224c8a690 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -2596,6 +2596,11 @@ void RichTextLabel::gui_input(const Ref &p_event) { deselect(); } } + + if (!selection.drag_attempt) { + is_selecting_text = true; + click_select_held->start(); + } } } } else if (b->is_pressed() && b->is_double_click() && selection.enabled) { @@ -2677,6 +2682,9 @@ void RichTextLabel::gui_input(const Ref &p_event) { } } } + + is_selecting_text = false; + click_select_held->stop(); } } @@ -2865,95 +2873,119 @@ void RichTextLabel::gui_input(const Ref &p_event) { Ref m = p_event; if (m.is_valid()) { - ItemFrame *c_frame = nullptr; - int c_line = 0; - Item *c_item = nullptr; - int c_index = 0; - bool outside; + local_mouse_pos = get_local_mouse_position(); + last_clamped_mouse_pos = local_mouse_pos.clamp(Vector2(), get_size()); + } +} - _find_click(main, m->get_position(), &c_frame, &c_line, &c_item, &c_index, &outside, false); - if (selection.click_item && c_item) { - selection.from_frame = selection.click_frame; - selection.from_line = selection.click_line; - selection.from_item = selection.click_item; - selection.from_char = selection.click_char; +void RichTextLabel::_update_selection() { + ItemFrame *c_frame = nullptr; + int c_line = 0; + Item *c_item = nullptr; + int c_index = 0; + bool outside; - selection.to_frame = c_frame; - selection.to_line = c_line; - selection.to_item = c_item; - selection.to_char = c_index; - - bool swap = false; - if (selection.click_frame && c_frame) { - const Line &l1 = c_frame->lines[c_line]; - const Line &l2 = selection.click_frame->lines[selection.click_line]; - if (l1.char_offset + c_index < l2.char_offset + selection.click_char) { - swap = true; - } else if (l1.char_offset + c_index == l2.char_offset + selection.click_char && !selection.double_click) { - deselect(); - return; - } - } - - if (swap) { - SWAP(selection.from_frame, selection.to_frame); - SWAP(selection.from_line, selection.to_line); - SWAP(selection.from_item, selection.to_item); - SWAP(selection.from_char, selection.to_char); - } - - if (selection.double_click && c_frame) { - // Expand the selection to word edges. - - Line *l = &selection.from_frame->lines[selection.from_line]; - MutexLock lock(l->text_buf->get_mutex()); - PackedInt32Array words = TS->shaped_text_get_word_breaks(l->text_buf->get_rid()); - for (int i = 0; i < words.size(); i = i + 2) { - if (selection.from_char > words[i] && selection.from_char < words[i + 1]) { - selection.from_char = words[i]; - break; - } - } - l = &selection.to_frame->lines[selection.to_line]; - lock = MutexLock(l->text_buf->get_mutex()); - words = TS->shaped_text_get_word_breaks(l->text_buf->get_rid()); - for (int i = 0; i < words.size(); i = i + 2) { - if (selection.to_char > words[i] && selection.to_char < words[i + 1]) { - selection.to_char = words[i + 1]; - break; - } - } - } - - selection.active = true; - queue_accessibility_update(); - queue_redraw(); + // Handle auto scrolling. + const Size2 size = get_size(); + if (!(local_mouse_pos.x >= 0.0 && local_mouse_pos.y >= 0.0 && + local_mouse_pos.x < size.x && local_mouse_pos.y < size.y)) { + real_t scroll_delta = 0.0; + if (local_mouse_pos.y < 0) { + scroll_delta = -auto_scroll_speed * (1 - (local_mouse_pos.y / 15.0)); + } else if (local_mouse_pos.y > size.y) { + scroll_delta = auto_scroll_speed * (1 + (local_mouse_pos.y - size.y) / 15.0); } - _find_click(main, m->get_position(), nullptr, nullptr, &c_item, nullptr, &outside, true); - Variant meta; - ItemMeta *item_meta; - ItemMeta *prev_meta = meta_hovering; - if (c_item && !outside && _find_meta(c_item, &meta, &item_meta)) { - if (meta_hovering != item_meta) { - if (meta_hovering) { - emit_signal(SNAME("meta_hover_ended"), current_meta); - } - meta_hovering = item_meta; - current_meta = meta; - emit_signal(SNAME("meta_hover_started"), meta); - if ((item_meta && item_meta->underline == META_UNDERLINE_ON_HOVER) || (prev_meta && prev_meta->underline == META_UNDERLINE_ON_HOVER)) { - queue_redraw(); + if (scroll_delta != 0.0) { + vscroll->scroll(scroll_delta); + queue_redraw(); + } + } + + // Update selection area. + _find_click(main, last_clamped_mouse_pos, &c_frame, &c_line, &c_item, &c_index, &outside, false); + if (selection.click_item && c_item) { + selection.from_frame = selection.click_frame; + selection.from_line = selection.click_line; + selection.from_item = selection.click_item; + selection.from_char = selection.click_char; + + selection.to_frame = c_frame; + selection.to_line = c_line; + selection.to_item = c_item; + selection.to_char = c_index; + + bool swap = false; + if (selection.click_frame && c_frame) { + const Line &l1 = c_frame->lines[c_line]; + const Line &l2 = selection.click_frame->lines[selection.click_line]; + if (l1.char_offset + c_index < l2.char_offset + selection.click_char) { + swap = true; + } else if (l1.char_offset + c_index == l2.char_offset + selection.click_char && !selection.double_click) { + deselect(); + return; + } + } + + if (swap) { + SWAP(selection.from_frame, selection.to_frame); + SWAP(selection.from_line, selection.to_line); + SWAP(selection.from_item, selection.to_item); + SWAP(selection.from_char, selection.to_char); + } + + if (selection.double_click && c_frame) { + // Expand the selection to word edges. + + Line *l = &selection.from_frame->lines[selection.from_line]; + MutexLock lock(l->text_buf->get_mutex()); + PackedInt32Array words = TS->shaped_text_get_word_breaks(l->text_buf->get_rid()); + for (int i = 0; i < words.size(); i = i + 2) { + if (selection.from_char > words[i] && selection.from_char < words[i + 1]) { + selection.from_char = words[i]; + break; } } - } else if (meta_hovering) { - meta_hovering = nullptr; - emit_signal(SNAME("meta_hover_ended"), current_meta); - current_meta = false; - if (prev_meta->underline == META_UNDERLINE_ON_HOVER) { + l = &selection.to_frame->lines[selection.to_line]; + lock = MutexLock(l->text_buf->get_mutex()); + words = TS->shaped_text_get_word_breaks(l->text_buf->get_rid()); + for (int i = 0; i < words.size(); i = i + 2) { + if (selection.to_char > words[i] && selection.to_char < words[i + 1]) { + selection.to_char = words[i + 1]; + break; + } + } + } + + selection.active = true; + queue_accessibility_update(); + queue_redraw(); + } + + // Update meta hovering. + _find_click(main, local_mouse_pos, nullptr, nullptr, &c_item, nullptr, &outside, true); + Variant meta; + ItemMeta *item_meta; + ItemMeta *prev_meta = meta_hovering; + if (c_item && !outside && _find_meta(c_item, &meta, &item_meta)) { + if (meta_hovering != item_meta) { + if (meta_hovering) { + emit_signal(SNAME("meta_hover_ended"), current_meta); + } + meta_hovering = item_meta; + current_meta = meta; + emit_signal(SNAME("meta_hover_started"), meta); + if ((item_meta && item_meta->underline == META_UNDERLINE_ON_HOVER) || (prev_meta && prev_meta->underline == META_UNDERLINE_ON_HOVER)) { queue_redraw(); } } + } else if (meta_hovering) { + meta_hovering = nullptr; + emit_signal(SNAME("meta_hover_ended"), current_meta); + current_meta = false; + if (prev_meta->underline == META_UNDERLINE_ON_HOVER) { + queue_redraw(); + } } } @@ -7783,6 +7815,11 @@ RichTextLabel::RichTextLabel(const String &p_text) { parsing_bbcode.store(false); set_clip_contents(true); + + click_select_held = memnew(Timer); + add_child(click_select_held, false, INTERNAL_MODE_FRONT); + click_select_held->set_wait_time(0.05); + click_select_held->connect("timeout", callable_mp(this, &RichTextLabel::_update_selection)); } RichTextLabel::~RichTextLabel() { diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index 02ae7ade4d8..2b46a811dea 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -36,6 +36,10 @@ #include "scene/gui/scroll_bar.h" #include "scene/resources/text_paragraph.h" +#ifdef TOOLS_ENABLED +#include "editor/themes/editor_scale.h" +#endif + class CharFXTransform; class RichTextEffect; @@ -667,6 +671,17 @@ private: void _scroll_changed(double); int _find_first_line(int p_from, int p_to, int p_vofs) const; +#ifdef TOOLS_ENABLED + const real_t auto_scroll_speed = EDSCALE * 2.0f; +#else + const real_t auto_scroll_speed = 2.0f; +#endif + Vector2 last_clamped_mouse_pos; + Timer *click_select_held = nullptr; + Vector2 local_mouse_pos; + bool is_selecting_text = false; + void _update_selection(); + _FORCE_INLINE_ float _calculate_line_vertical_offset(const Line &line) const; virtual void gui_input(const Ref &p_event) override;