RichTextLabel scroll_to_line, with Vertical Alignment Bottom?

Godot Version

Godot 4.4 dev 7

Question

I’m trying to make a little text editor app. I want it to be like an old school word processor where you only have one line to type in, then they would print above that on paper when you hit enter, very much like a typewriter. It’s a very similar setup to a in game console like Valve games, or numerous others have. I can get that setup all fine, except when it comes to varying text sizes. I would like to keep some formatting, so I have a lineedit for typing into, and above that is a RichTextLabel with BBCode enabled. I need to align the scrolling of the richtextlabel with the top edge of the line edit so that when the user scrolling back up with the arrow keys the currently active line is hidden behind the lineedit, and the text is placed into the line edit for editing, until the user presses enter again, then it replaces the line in the richtextlabel and scrolls back up. The problem is that scroll_to_line only aligns to the top of the window. I was excited to see the vertical alignment property added in 4.4, as that cleaned up a lot of mess with workarounds, but I still need a way to scroll where I need it. There doesn’t appear to be any way to directly control the scrollContainer in the richtextedit, or a line_size property or any other way to determine the size of a line of text that could be changed with bbcode formatting.

I’ve been searching and I know that a console is common enough to implement, has anyone dealt with something similar to this problem, or have any ideas I can try?

What exactly is your problem:
You want to control scrolling perfectly,
You want the scroll to automatically come to the last line sent,
You want to create a structure that can be scrolled through the previous lines while the line you are typing is still visible.
I saw all of this in your message but didn’t understand which you did and which you wanted to do.

I’m sorry I’m terrible at explaining things, and I think I over explained my problem.

I have a RichTextLabel that has Vertical Alignment in the inspector set to Bottom. I need to scroll the label one line at a time with the keyboard, and I need the text to align to the bottom of the RichTextLabel when it’s scrolled.

Something like that should work for you I think

extends RichTextLabel

var current_line: int = 0

func _unhandled_input(event: InputEvent) -> void:
	if event.is_action_pressed("ui_up"):
		current_line = max(0, current_line - 1)
		scroll_to_line(current_line)
	if event.is_action_pressed("ui_down"):
		current_line = min(get_line_count() - get_visible_line_count(), current_line + 1)
		scroll_to_line(current_line)

The problem is that scroll_to_line aligns the text with the top of the RichTextLabel, which means that the bottom gets cut in half. It’s the bottom that needs to be aligned in my case and it’s ok if the top gets cut off

So you need to do counting and scrolling to the line from the bottom?

Visually I need each line of text to align with the bottom of the screen as it is scrolled.

This code keeps scrolling down after the new command, more details are in the code:

# Connected to ScrollContainer.child_entered_tree
# Automatically scrolls the container down when a new child is added
func AdjustScroll(_child: Node) -> void:
        var scrollbar: VScrollBar = scrollFrame.get_v_scroll_bar()
        scrollFrame.scroll_vertical = scrollbar.max_value

Is that what you want to do? Your words are really confusing

1 Like

Ok, I think I get it now.
But that’s not easy to do out-of-the-box, because there is no such function built in. You’d need to calculate the position of your bottom line and scroll with that offset directly on the ScrollBar. It will be 100x easier to adjust your label size, or text size so that it doesn’t cut off the last line and you’d achieve the same effect.
Can you share a screenshot of how this Label looks like in your project currently?

I don’t think that’s possible - at least I couldn’t figure out how to do it.
I have an alternative solution for you though - on each text submit from the LineEdit, you can create a new RichTextLabel and add it to a VBoxContainer, which is inside of a ScrollContainer, and then scroll that container.
See here my little demo:


Scene structure:

And the code:

extends LineEdit

@export var label_container: VBoxContainer
@export var scroll_container: ScrollContainer
var current_id: int:
	set(value):
		current_id = clampi(value, 0, label_container.get_child_count() - 1)

func _ready() -> void:
	text_submitted.connect(_on_text_submitted)
	scroll_to_bottom()

func _on_text_submitted(new_text: String):
	if new_text == "":
		return
	create_new_label(new_text)
	scroll_to_bottom()
	clear()

func create_new_label(new_text: String) -> void:
	var new_label: RichTextLabel = RichTextLabel.new()
	new_label.text = new_text
	new_label.bbcode_enabled = true
	new_label.fit_content = true
	label_container.add_child(new_label)

func scroll_to_bottom() -> void:
	await get_tree().process_frame
	scroll_container.get_v_scroll_bar().value = scroll_container.get_v_scroll_bar().max_value
	current_id = label_container.get_child_count() - 1

func _unhandled_input(event: InputEvent) -> void:
	if event.is_action_pressed("ui_up"):
		scroll_container.get_v_scroll_bar().value -= label_container.get_child(current_id).size.y
		current_id -= 1
	if event.is_action_pressed("ui_down"):
		current_id += 1
		scroll_container.get_v_scroll_bar().value += label_container.get_child(current_id).size.y

You probably still need to adjust it to your project, but that might get you started somewhere.

2 Likes