Skip to content

Commit 71a485a

Browse files
committed
Merge pull request #104715 from Andrewyuan34/add-auto-scroll
Add auto-scroll behavior when selecting text outside the visible area in RichTextLabel
2 parents 1566eec + 4774b57 commit 71a485a

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
@@ -2670,6 +2670,11 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
26702670
deselect();
26712671
}
26722672
}
2673+
2674+
if (!selection.drag_attempt) {
2675+
is_selecting_text = true;
2676+
click_select_held->start();
2677+
}
26732678
}
26742679
}
26752680
} else if (b->is_pressed() && b->is_double_click() && selection.enabled) {
@@ -2751,6 +2756,9 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
27512756
}
27522757
}
27532758
}
2759+
2760+
is_selecting_text = false;
2761+
click_select_held->stop();
27542762
}
27552763
}
27562764

@@ -2939,95 +2947,119 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
29392947

29402948
Ref<InputEventMouseMotion> m = p_event;
29412949
if (m.is_valid()) {
2942-
ItemFrame *c_frame = nullptr;
2943-
int c_line = 0;
2944-
Item *c_item = nullptr;
2945-
int c_index = 0;
2946-
bool outside;
2947-
2948-
_find_click(main, m->get_position(), &c_frame, &c_line, &c_item, &c_index, &outside, false);
2949-
if (selection.click_item && c_item) {
2950-
selection.from_frame = selection.click_frame;
2951-
selection.from_line = selection.click_line;
2952-
selection.from_item = selection.click_item;
2953-
selection.from_char = selection.click_char;
2954-
2955-
selection.to_frame = c_frame;
2956-
selection.to_line = c_line;
2957-
selection.to_item = c_item;
2958-
selection.to_char = c_index;
2959-
2960-
bool swap = false;
2961-
if (selection.click_frame && c_frame) {
2962-
const Line &l1 = c_frame->lines[c_line];
2963-
const Line &l2 = selection.click_frame->lines[selection.click_line];
2964-
if (l1.char_offset + c_index < l2.char_offset + selection.click_char) {
2965-
swap = true;
2966-
} else if (l1.char_offset + c_index == l2.char_offset + selection.click_char && !selection.double_click) {
2967-
deselect();
2968-
return;
2969-
}
2970-
}
2950+
local_mouse_pos = get_local_mouse_position();
2951+
last_clamped_mouse_pos = local_mouse_pos.clamp(Vector2(), get_size());
2952+
}
2953+
}
29712954

2972-
if (swap) {
2973-
SWAP(selection.from_frame, selection.to_frame);
2974-
SWAP(selection.from_line, selection.to_line);
2975-
SWAP(selection.from_item, selection.to_item);
2976-
SWAP(selection.from_char, selection.to_char);
2977-
}
2955+
void RichTextLabel::_update_selection() {
2956+
ItemFrame *c_frame = nullptr;
2957+
int c_line = 0;
2958+
Item *c_item = nullptr;
2959+
int c_index = 0;
2960+
bool outside;
2961+
2962+
// Handle auto scrolling.
2963+
const Size2 size = get_size();
2964+
if (!(local_mouse_pos.x >= 0.0 && local_mouse_pos.y >= 0.0 &&
2965+
local_mouse_pos.x < size.x && local_mouse_pos.y < size.y)) {
2966+
real_t scroll_delta = 0.0;
2967+
if (local_mouse_pos.y < 0) {
2968+
scroll_delta = -auto_scroll_speed * (1 - (local_mouse_pos.y / 15.0));
2969+
} else if (local_mouse_pos.y > size.y) {
2970+
scroll_delta = auto_scroll_speed * (1 + (local_mouse_pos.y - size.y) / 15.0);
2971+
}
29782972

2979-
if (selection.double_click && c_frame) {
2980-
// Expand the selection to word edges.
2973+
if (scroll_delta != 0.0) {
2974+
vscroll->scroll(scroll_delta);
2975+
queue_redraw();
2976+
}
2977+
}
29812978

2982-
Line *l = &selection.from_frame->lines[selection.from_line];
2983-
MutexLock lock(l->text_buf->get_mutex());
2984-
PackedInt32Array words = TS->shaped_text_get_word_breaks(l->text_buf->get_rid());
2985-
for (int i = 0; i < words.size(); i = i + 2) {
2986-
if (selection.from_char > words[i] && selection.from_char < words[i + 1]) {
2987-
selection.from_char = words[i];
2988-
break;
2989-
}
2990-
}
2991-
l = &selection.to_frame->lines[selection.to_line];
2992-
lock = MutexLock(l->text_buf->get_mutex());
2993-
words = TS->shaped_text_get_word_breaks(l->text_buf->get_rid());
2994-
for (int i = 0; i < words.size(); i = i + 2) {
2995-
if (selection.to_char > words[i] && selection.to_char < words[i + 1]) {
2996-
selection.to_char = words[i + 1];
2997-
break;
2998-
}
2999-
}
2979+
// Update selection area.
2980+
_find_click(main, last_clamped_mouse_pos, &c_frame, &c_line, &c_item, &c_index, &outside, false);
2981+
if (selection.click_item && c_item) {
2982+
selection.from_frame = selection.click_frame;
2983+
selection.from_line = selection.click_line;
2984+
selection.from_item = selection.click_item;
2985+
selection.from_char = selection.click_char;
2986+
2987+
selection.to_frame = c_frame;
2988+
selection.to_line = c_line;
2989+
selection.to_item = c_item;
2990+
selection.to_char = c_index;
2991+
2992+
bool swap = false;
2993+
if (selection.click_frame && c_frame) {
2994+
const Line &l1 = c_frame->lines[c_line];
2995+
const Line &l2 = selection.click_frame->lines[selection.click_line];
2996+
if (l1.char_offset + c_index < l2.char_offset + selection.click_char) {
2997+
swap = true;
2998+
} else if (l1.char_offset + c_index == l2.char_offset + selection.click_char && !selection.double_click) {
2999+
deselect();
3000+
return;
30003001
}
3002+
}
30013003

3002-
selection.active = true;
3003-
queue_accessibility_update();
3004-
queue_redraw();
3004+
if (swap) {
3005+
SWAP(selection.from_frame, selection.to_frame);
3006+
SWAP(selection.from_line, selection.to_line);
3007+
SWAP(selection.from_item, selection.to_item);
3008+
SWAP(selection.from_char, selection.to_char);
30053009
}
30063010

3007-
_find_click(main, m->get_position(), nullptr, nullptr, &c_item, nullptr, &outside, true);
3008-
Variant meta;
3009-
ItemMeta *item_meta;
3010-
ItemMeta *prev_meta = meta_hovering;
3011-
if (c_item && !outside && _find_meta(c_item, &meta, &item_meta)) {
3012-
if (meta_hovering != item_meta) {
3013-
if (meta_hovering) {
3014-
emit_signal(SNAME("meta_hover_ended"), current_meta);
3011+
if (selection.double_click && c_frame) {
3012+
// Expand the selection to word edges.
3013+
3014+
Line *l = &selection.from_frame->lines[selection.from_line];
3015+
MutexLock lock(l->text_buf->get_mutex());
3016+
PackedInt32Array words = TS->shaped_text_get_word_breaks(l->text_buf->get_rid());
3017+
for (int i = 0; i < words.size(); i = i + 2) {
3018+
if (selection.from_char > words[i] && selection.from_char < words[i + 1]) {
3019+
selection.from_char = words[i];
3020+
break;
30153021
}
3016-
meta_hovering = item_meta;
3017-
current_meta = meta;
3018-
emit_signal(SNAME("meta_hover_started"), meta);
3019-
if ((item_meta && item_meta->underline == META_UNDERLINE_ON_HOVER) || (prev_meta && prev_meta->underline == META_UNDERLINE_ON_HOVER)) {
3020-
queue_redraw();
3022+
}
3023+
l = &selection.to_frame->lines[selection.to_line];
3024+
lock = MutexLock(l->text_buf->get_mutex());
3025+
words = TS->shaped_text_get_word_breaks(l->text_buf->get_rid());
3026+
for (int i = 0; i < words.size(); i = i + 2) {
3027+
if (selection.to_char > words[i] && selection.to_char < words[i + 1]) {
3028+
selection.to_char = words[i + 1];
3029+
break;
30213030
}
30223031
}
3023-
} else if (meta_hovering) {
3024-
meta_hovering = nullptr;
3025-
emit_signal(SNAME("meta_hover_ended"), current_meta);
3026-
current_meta = false;
3027-
if (prev_meta->underline == META_UNDERLINE_ON_HOVER) {
3032+
}
3033+
3034+
selection.active = true;
3035+
queue_accessibility_update();
3036+
queue_redraw();
3037+
}
3038+
3039+
// Update meta hovering.
3040+
_find_click(main, local_mouse_pos, nullptr, nullptr, &c_item, nullptr, &outside, true);
3041+
Variant meta;
3042+
ItemMeta *item_meta;
3043+
ItemMeta *prev_meta = meta_hovering;
3044+
if (c_item && !outside && _find_meta(c_item, &meta, &item_meta)) {
3045+
if (meta_hovering != item_meta) {
3046+
if (meta_hovering) {
3047+
emit_signal(SNAME("meta_hover_ended"), current_meta);
3048+
}
3049+
meta_hovering = item_meta;
3050+
current_meta = meta;
3051+
emit_signal(SNAME("meta_hover_started"), meta);
3052+
if ((item_meta && item_meta->underline == META_UNDERLINE_ON_HOVER) || (prev_meta && prev_meta->underline == META_UNDERLINE_ON_HOVER)) {
30283053
queue_redraw();
30293054
}
30303055
}
3056+
} else if (meta_hovering) {
3057+
meta_hovering = nullptr;
3058+
emit_signal(SNAME("meta_hover_ended"), current_meta);
3059+
current_meta = false;
3060+
if (prev_meta->underline == META_UNDERLINE_ON_HOVER) {
3061+
queue_redraw();
3062+
}
30313063
}
30323064
}
30333065

@@ -8076,6 +8108,11 @@ RichTextLabel::RichTextLabel(const String &p_text) {
80768108
parsing_bbcode.store(false);
80778109

80788110
set_clip_contents(true);
8111+
8112+
click_select_held = memnew(Timer);
8113+
add_child(click_select_held, false, INTERNAL_MODE_FRONT);
8114+
click_select_held->set_wait_time(0.05);
8115+
click_select_held->connect("timeout", callable_mp(this, &RichTextLabel::_update_selection));
80798116
}
80808117

80818118
RichTextLabel::~RichTextLabel() {

scene/gui/rich_text_label.h

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

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

@@ -680,6 +684,17 @@ class RichTextLabel : public Control {
680684
void _scroll_changed(double);
681685
int _find_first_line(int p_from, int p_to, int p_vofs) const;
682686

687+
#ifdef TOOLS_ENABLED
688+
const real_t auto_scroll_speed = EDSCALE * 2.0f;
689+
#else
690+
const real_t auto_scroll_speed = 2.0f;
691+
#endif
692+
Vector2 last_clamped_mouse_pos;
693+
Timer *click_select_held = nullptr;
694+
Vector2 local_mouse_pos;
695+
bool is_selecting_text = false;
696+
void _update_selection();
697+
683698
_FORCE_INLINE_ float _calculate_line_vertical_offset(const Line &line) const;
684699

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

0 commit comments

Comments
 (0)