Skip to content

Commit 4774b57

Browse files
committed
Add auto-scroll behavior when selecting text outside the visible area in RichTextLabel
1 parent 42c7f14 commit 4774b57

File tree

2 files changed

+128
-76
lines changed

2 files changed

+128
-76
lines changed

scene/gui/rich_text_label.cpp

Lines changed: 113 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -2596,6 +2596,11 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
25962596
deselect();
25972597
}
25982598
}
2599+
2600+
if (!selection.drag_attempt) {
2601+
is_selecting_text = true;
2602+
click_select_held->start();
2603+
}
25992604
}
26002605
}
26012606
} else if (b->is_pressed() && b->is_double_click() && selection.enabled) {
@@ -2677,6 +2682,9 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
26772682
}
26782683
}
26792684
}
2685+
2686+
is_selecting_text = false;
2687+
click_select_held->stop();
26802688
}
26812689
}
26822690

@@ -2865,95 +2873,119 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
28652873

28662874
Ref<InputEventMouseMotion> m = p_event;
28672875
if (m.is_valid()) {
2868-
ItemFrame *c_frame = nullptr;
2869-
int c_line = 0;
2870-
Item *c_item = nullptr;
2871-
int c_index = 0;
2872-
bool outside;
2873-
2874-
_find_click(main, m->get_position(), &c_frame, &c_line, &c_item, &c_index, &outside, false);
2875-
if (selection.click_item && c_item) {
2876-
selection.from_frame = selection.click_frame;
2877-
selection.from_line = selection.click_line;
2878-
selection.from_item = selection.click_item;
2879-
selection.from_char = selection.click_char;
2880-
2881-
selection.to_frame = c_frame;
2882-
selection.to_line = c_line;
2883-
selection.to_item = c_item;
2884-
selection.to_char = c_index;
2885-
2886-
bool swap = false;
2887-
if (selection.click_frame && c_frame) {
2888-
const Line &l1 = c_frame->lines[c_line];
2889-
const Line &l2 = selection.click_frame->lines[selection.click_line];
2890-
if (l1.char_offset + c_index < l2.char_offset + selection.click_char) {
2891-
swap = true;
2892-
} else if (l1.char_offset + c_index == l2.char_offset + selection.click_char && !selection.double_click) {
2893-
deselect();
2894-
return;
2895-
}
2896-
}
2876+
local_mouse_pos = get_local_mouse_position();
2877+
last_clamped_mouse_pos = local_mouse_pos.clamp(Vector2(), get_size());
2878+
}
2879+
}
28972880

2898-
if (swap) {
2899-
SWAP(selection.from_frame, selection.to_frame);
2900-
SWAP(selection.from_line, selection.to_line);
2901-
SWAP(selection.from_item, selection.to_item);
2902-
SWAP(selection.from_char, selection.to_char);
2903-
}
2881+
void RichTextLabel::_update_selection() {
2882+
ItemFrame *c_frame = nullptr;
2883+
int c_line = 0;
2884+
Item *c_item = nullptr;
2885+
int c_index = 0;
2886+
bool outside;
2887+
2888+
// Handle auto scrolling.
2889+
const Size2 size = get_size();
2890+
if (!(local_mouse_pos.x >= 0.0 && local_mouse_pos.y >= 0.0 &&
2891+
local_mouse_pos.x < size.x && local_mouse_pos.y < size.y)) {
2892+
real_t scroll_delta = 0.0;
2893+
if (local_mouse_pos.y < 0) {
2894+
scroll_delta = -auto_scroll_speed * (1 - (local_mouse_pos.y / 15.0));
2895+
} else if (local_mouse_pos.y > size.y) {
2896+
scroll_delta = auto_scroll_speed * (1 + (local_mouse_pos.y - size.y) / 15.0);
2897+
}
29042898

2905-
if (selection.double_click && c_frame) {
2906-
// Expand the selection to word edges.
2899+
if (scroll_delta != 0.0) {
2900+
vscroll->scroll(scroll_delta);
2901+
queue_redraw();
2902+
}
2903+
}
29072904

2908-
Line *l = &selection.from_frame->lines[selection.from_line];
2909-
MutexLock lock(l->text_buf->get_mutex());
2910-
PackedInt32Array words = TS->shaped_text_get_word_breaks(l->text_buf->get_rid());
2911-
for (int i = 0; i < words.size(); i = i + 2) {
2912-
if (selection.from_char > words[i] && selection.from_char < words[i + 1]) {
2913-
selection.from_char = words[i];
2914-
break;
2915-
}
2916-
}
2917-
l = &selection.to_frame->lines[selection.to_line];
2918-
lock = MutexLock(l->text_buf->get_mutex());
2919-
words = TS->shaped_text_get_word_breaks(l->text_buf->get_rid());
2920-
for (int i = 0; i < words.size(); i = i + 2) {
2921-
if (selection.to_char > words[i] && selection.to_char < words[i + 1]) {
2922-
selection.to_char = words[i + 1];
2923-
break;
2924-
}
2925-
}
2905+
// Update selection area.
2906+
_find_click(main, last_clamped_mouse_pos, &c_frame, &c_line, &c_item, &c_index, &outside, false);
2907+
if (selection.click_item && c_item) {
2908+
selection.from_frame = selection.click_frame;
2909+
selection.from_line = selection.click_line;
2910+
selection.from_item = selection.click_item;
2911+
selection.from_char = selection.click_char;
2912+
2913+
selection.to_frame = c_frame;
2914+
selection.to_line = c_line;
2915+
selection.to_item = c_item;
2916+
selection.to_char = c_index;
2917+
2918+
bool swap = false;
2919+
if (selection.click_frame && c_frame) {
2920+
const Line &l1 = c_frame->lines[c_line];
2921+
const Line &l2 = selection.click_frame->lines[selection.click_line];
2922+
if (l1.char_offset + c_index < l2.char_offset + selection.click_char) {
2923+
swap = true;
2924+
} else if (l1.char_offset + c_index == l2.char_offset + selection.click_char && !selection.double_click) {
2925+
deselect();
2926+
return;
29262927
}
2928+
}
29272929

2928-
selection.active = true;
2929-
queue_accessibility_update();
2930-
queue_redraw();
2930+
if (swap) {
2931+
SWAP(selection.from_frame, selection.to_frame);
2932+
SWAP(selection.from_line, selection.to_line);
2933+
SWAP(selection.from_item, selection.to_item);
2934+
SWAP(selection.from_char, selection.to_char);
29312935
}
29322936

2933-
_find_click(main, m->get_position(), nullptr, nullptr, &c_item, nullptr, &outside, true);
2934-
Variant meta;
2935-
ItemMeta *item_meta;
2936-
ItemMeta *prev_meta = meta_hovering;
2937-
if (c_item && !outside && _find_meta(c_item, &meta, &item_meta)) {
2938-
if (meta_hovering != item_meta) {
2939-
if (meta_hovering) {
2940-
emit_signal(SNAME("meta_hover_ended"), current_meta);
2937+
if (selection.double_click && c_frame) {
2938+
// Expand the selection to word edges.
2939+
2940+
Line *l = &selection.from_frame->lines[selection.from_line];
2941+
MutexLock lock(l->text_buf->get_mutex());
2942+
PackedInt32Array words = TS->shaped_text_get_word_breaks(l->text_buf->get_rid());
2943+
for (int i = 0; i < words.size(); i = i + 2) {
2944+
if (selection.from_char > words[i] && selection.from_char < words[i + 1]) {
2945+
selection.from_char = words[i];
2946+
break;
29412947
}
2942-
meta_hovering = item_meta;
2943-
current_meta = meta;
2944-
emit_signal(SNAME("meta_hover_started"), meta);
2945-
if ((item_meta && item_meta->underline == META_UNDERLINE_ON_HOVER) || (prev_meta && prev_meta->underline == META_UNDERLINE_ON_HOVER)) {
2946-
queue_redraw();
2948+
}
2949+
l = &selection.to_frame->lines[selection.to_line];
2950+
lock = MutexLock(l->text_buf->get_mutex());
2951+
words = TS->shaped_text_get_word_breaks(l->text_buf->get_rid());
2952+
for (int i = 0; i < words.size(); i = i + 2) {
2953+
if (selection.to_char > words[i] && selection.to_char < words[i + 1]) {
2954+
selection.to_char = words[i + 1];
2955+
break;
29472956
}
29482957
}
2949-
} else if (meta_hovering) {
2950-
meta_hovering = nullptr;
2951-
emit_signal(SNAME("meta_hover_ended"), current_meta);
2952-
current_meta = false;
2953-
if (prev_meta->underline == META_UNDERLINE_ON_HOVER) {
2958+
}
2959+
2960+
selection.active = true;
2961+
queue_accessibility_update();
2962+
queue_redraw();
2963+
}
2964+
2965+
// Update meta hovering.
2966+
_find_click(main, local_mouse_pos, nullptr, nullptr, &c_item, nullptr, &outside, true);
2967+
Variant meta;
2968+
ItemMeta *item_meta;
2969+
ItemMeta *prev_meta = meta_hovering;
2970+
if (c_item && !outside && _find_meta(c_item, &meta, &item_meta)) {
2971+
if (meta_hovering != item_meta) {
2972+
if (meta_hovering) {
2973+
emit_signal(SNAME("meta_hover_ended"), current_meta);
2974+
}
2975+
meta_hovering = item_meta;
2976+
current_meta = meta;
2977+
emit_signal(SNAME("meta_hover_started"), meta);
2978+
if ((item_meta && item_meta->underline == META_UNDERLINE_ON_HOVER) || (prev_meta && prev_meta->underline == META_UNDERLINE_ON_HOVER)) {
29542979
queue_redraw();
29552980
}
29562981
}
2982+
} else if (meta_hovering) {
2983+
meta_hovering = nullptr;
2984+
emit_signal(SNAME("meta_hover_ended"), current_meta);
2985+
current_meta = false;
2986+
if (prev_meta->underline == META_UNDERLINE_ON_HOVER) {
2987+
queue_redraw();
2988+
}
29572989
}
29582990
}
29592991

@@ -7783,6 +7815,11 @@ RichTextLabel::RichTextLabel(const String &p_text) {
77837815
parsing_bbcode.store(false);
77847816

77857817
set_clip_contents(true);
7818+
7819+
click_select_held = memnew(Timer);
7820+
add_child(click_select_held, false, INTERNAL_MODE_FRONT);
7821+
click_select_held->set_wait_time(0.05);
7822+
click_select_held->connect("timeout", callable_mp(this, &RichTextLabel::_update_selection));
77867823
}
77877824

77887825
RichTextLabel::~RichTextLabel() {

scene/gui/rich_text_label.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@
3636
#include "scene/gui/scroll_bar.h"
3737
#include "scene/resources/text_paragraph.h"
3838

39+
#ifdef TOOLS_ENABLED
40+
#include "editor/themes/editor_scale.h"
41+
#endif
42+
3943
class CharFXTransform;
4044
class RichTextEffect;
4145

@@ -667,6 +671,17 @@ class RichTextLabel : public Control {
667671
void _scroll_changed(double);
668672
int _find_first_line(int p_from, int p_to, int p_vofs) const;
669673

674+
#ifdef TOOLS_ENABLED
675+
const real_t auto_scroll_speed = EDSCALE * 2.0f;
676+
#else
677+
const real_t auto_scroll_speed = 2.0f;
678+
#endif
679+
Vector2 last_clamped_mouse_pos;
680+
Timer *click_select_held = nullptr;
681+
Vector2 local_mouse_pos;
682+
bool is_selecting_text = false;
683+
void _update_selection();
684+
670685
_FORCE_INLINE_ float _calculate_line_vertical_offset(const Line &line) const;
671686

672687
virtual void gui_input(const Ref<InputEvent> &p_event) override;

0 commit comments

Comments
 (0)