From 45a564f4f83bf707d782af2c13f2e3e10e7b42b2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pa=CC=84vels=20Nadtoc=CC=8Cajevs?=
<7645683+bruvzg@users.noreply.github.com>
Date: Tue, 8 Jul 2025 11:12:23 +0300
Subject: [PATCH] [RTL] Add option to scroll follow visible characters.
---
doc/classes/RichTextLabel.xml | 3 +++
scene/gui/rich_text_label.cpp | 34 ++++++++++++++++++++++++++++++++++
scene/gui/rich_text_label.h | 6 ++++++
3 files changed, 43 insertions(+)
diff --git a/doc/classes/RichTextLabel.xml b/doc/classes/RichTextLabel.xml
index c79880a5ceb..3479dabb96a 100644
--- a/doc/classes/RichTextLabel.xml
+++ b/doc/classes/RichTextLabel.xml
@@ -729,6 +729,9 @@
If [code]true[/code], the window scrolls down to display new content automatically.
+
+ If [code]true[/code], the window scrolls to display the last visible line when [member visible_characters] or [member visible_ratio] is changed.
+
If [code]true[/code], the label allows text selection.
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index 9e5d253bb99..78bcfe23123 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -2516,6 +2516,9 @@ void RichTextLabel::_notification(int p_what) {
ofs.y += main->lines[from_line].text_buf->get_size().y + (main->lines[from_line].text_buf->get_line_count() - 1) * (theme_cache.line_separation + vsep) + (theme_cache.paragraph_separation + vsep);
from_line++;
}
+ if (scroll_follow_visible_characters && scroll_active) {
+ vscroll->set_visible(follow_vc_pos > 0);
+ }
if (has_focus() && get_tree()->is_accessibility_enabled()) {
RID ae;
if (keyboard_focus_frame && keyboard_focus_item) {
@@ -5019,6 +5022,31 @@ bool RichTextLabel::is_scroll_following() const {
return scroll_follow;
}
+void RichTextLabel::_update_follow_vc() {
+ if (!scroll_follow_visible_characters) {
+ return;
+ }
+ int vc = (visible_characters < 0 ? get_total_character_count() : MIN(visible_characters, get_total_character_count())) - 1;
+ int voff = get_character_line(vc) + 1;
+ if (voff <= get_line_count() - 1) {
+ follow_vc_pos = get_line_offset(voff) - _get_text_rect().size.y;
+ } else {
+ follow_vc_pos = vscroll->get_max();
+ }
+ vscroll->scroll_to(follow_vc_pos);
+}
+
+void RichTextLabel::set_scroll_follow_visible_characters(bool p_follow) {
+ if (scroll_follow_visible_characters != p_follow) {
+ scroll_follow_visible_characters = p_follow;
+ _update_follow_vc();
+ }
+}
+
+bool RichTextLabel::is_scroll_following_visible_characters() const {
+ return scroll_follow_visible_characters;
+}
+
void RichTextLabel::parse_bbcode(const String &p_bbcode) {
clear();
append_text(p_bbcode);
@@ -7207,6 +7235,7 @@ void RichTextLabel::set_visible_ratio(float p_ratio) {
total_height = _calculate_line_vertical_offset(main->lines[i]);
}
}
+ _update_follow_vc();
queue_redraw();
}
}
@@ -7397,6 +7426,9 @@ void RichTextLabel::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_scroll_active", "active"), &RichTextLabel::set_scroll_active);
ClassDB::bind_method(D_METHOD("is_scroll_active"), &RichTextLabel::is_scroll_active);
+ ClassDB::bind_method(D_METHOD("set_scroll_follow_visible_characters", "follow"), &RichTextLabel::set_scroll_follow_visible_characters);
+ ClassDB::bind_method(D_METHOD("is_scroll_following_visible_characters"), &RichTextLabel::is_scroll_following_visible_characters);
+
ClassDB::bind_method(D_METHOD("set_scroll_follow", "follow"), &RichTextLabel::set_scroll_follow);
ClassDB::bind_method(D_METHOD("is_scroll_following"), &RichTextLabel::is_scroll_following);
@@ -7501,6 +7533,7 @@ void RichTextLabel::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fit_content"), "set_fit_content", "is_fit_content_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_active"), "set_scroll_active", "is_scroll_active");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_following"), "set_scroll_follow", "is_scroll_following");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_following_visible_characters"), "set_scroll_follow_visible_characters", "is_scroll_following_visible_characters");
ADD_PROPERTY(PropertyInfo(Variant::INT, "autowrap_mode", PROPERTY_HINT_ENUM, "Off,Arbitrary,Word,Word (Smart)"), "set_autowrap_mode", "get_autowrap_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "autowrap_trim_flags", PROPERTY_HINT_FLAGS, vformat("Trim Spaces After Break:%d,Trim Spaces Before Break:%d", TextServer::BREAK_TRIM_START_EDGE_SPACES, TextServer::BREAK_TRIM_END_EDGE_SPACES)), "set_autowrap_trim_flags", "get_autowrap_trim_flags");
ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_size", PROPERTY_HINT_RANGE, "0,24,1"), "set_tab_size", "get_tab_size");
@@ -7682,6 +7715,7 @@ void RichTextLabel::set_visible_characters(int p_visible) {
total_height = _calculate_line_vertical_offset(main->lines[i]);
}
}
+ _update_follow_vc();
queue_redraw();
}
}
diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h
index e84d3d2c508..b90df07d42d 100644
--- a/scene/gui/rich_text_label.h
+++ b/scene/gui/rich_text_label.h
@@ -527,6 +527,8 @@ private:
bool scroll_visible = false;
bool scroll_follow = false;
+ bool scroll_follow_visible_characters = false;
+ int follow_vc_pos = 0;
bool scroll_following = false;
bool scroll_active = true;
int scroll_w = 0;
@@ -554,6 +556,7 @@ private:
HashMap ac_element_bounds_cache;
+ void _update_follow_vc();
void _invalidate_accessibility();
void _invalidate_current_line(ItemFrame *p_frame);
@@ -839,6 +842,9 @@ public:
void set_scroll_follow(bool p_follow);
bool is_scroll_following() const;
+ void set_scroll_follow_visible_characters(bool p_follow);
+ bool is_scroll_following_visible_characters() const;
+
void set_tab_size(int p_spaces);
int get_tab_size() const;