Automatically horizontal scrolling text using a RichTextLabel?

Godot Version

v4.5.1

Question

Hello! First time using this forum, so I’m probably going to sound stupid. I’m also new to Godot and programming as a whole, so that adds onto it.

For the music selector in my project, I’m trying to use a RichTextLabel to display the name of the song, and have it automatically scroll to fit the full name of the track and have it loop back to the beginning. Something akin to a news crawl.

I tried checking around for any similar topics that use RichTextLabel nodes, but couldn’t really find any, only one for a regular Label node, which required it to be a child to a Control node.

If anyone has any ideas on how a script for this might work, I’d love to hear cuz that would rock. Thank you!

You can add it as a child of a ScrollContainer with ScrollContainer.horizontal_scroll_mode set to Never Show and ScrollContainer.vertical_scroll_mode to Disabled

You’ll need to enable RichTextLabel.fit_content and set RichTextLabel.autowrap_mode to Off

You can then scroll it by changing the ScrollContainer.scroll_horizontal property.

4 Likes

Any solution that would work with a Label mode would also work for a RichTextLabel node with a few adjustments.

Anyway mrcdk’s solution would work, but I would also suggest having the name of the song twice in the label so there’s not a gap between the start and end of the text. Also, I don’t think a ScrollContainer will scroll past the end of the label if there’s no blank space there.

2 Likes

I’ve thought about doing this, but it’s low on my priority list. If I were going to scroll, I’d try to do it by character, basically pulling each character off the front after it disappears and appending it to the end. The suggestion @paintsimmon had of making it appear twice is a good idea, just in case your text is too short.

1 Like

yeah that also works, though it might not be as smooth as scrolling the scrollcontainer.

To make the marquee loop seamless, you should warp the scroll ratio or value back to the start when the second instance of the text reaches the position the first instance would be at the start. This creates the illusion of scrolling forever, like that infinite staircase in super mario 64 that speedrunners backwards long jump past

1 Like

Yeah, duplicating the label and wrapping the scroll to the beginning when the scroll value reaches the size of the Label does the trick. It seamlessly scrolls forever.

extends ScrollContainer

func _process(delta: float) -> void:
	scroll_horizontal += 1
	if scroll_horizontal >= $HBoxContainer/RichTextLabel.size.x:
		scroll_horizontal = 0


2 Likes

This seems to work flawlessly, now I just gotta figure out to update the text with whatever song is playing, but this is a good start. Thank you!

1 Like

Ok, I played around with what @mrcdk, @paintsimmon and @wchc posted. It’s almost perfect - until you start using BBCode. So I tweaked it a little, and made it work with my Sound plugin.

## Scrolls the title, artist (if available), and album (if available) of a song
## played using [member Music.play]. It uses the [RichTextLabel] child node
## if found, and otherwise creates one at runtime.
class_name MusicMarqueeHScrollContainer extends ScrollContainer

## The color to use for song titles.
@export var title_color: Color = Color.MAGENTA
## The color to use for artist names.
@export var artist_color: Color = Color.CORNFLOWER_BLUE
## The color to use for album names.
@export var album_color: Color = Color.GREEN

var rich_text_label: RichTextLabel


func _ready() -> void:
	horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_SHOW_NEVER
	vertical_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
	
	for node in get_children():
		if node is RichTextLabel:
			rich_text_label = node
	if not rich_text_label:
		rich_text_label = RichTextLabel.new()
		add_child(rich_text_label)
	
	rich_text_label.autowrap_mode = TextServer.AUTOWRAP_OFF
	rich_text_label.scroll_active = false
	rich_text_label.fit_content = true
	rich_text_label.bbcode_enabled = true
	
	Music.song_started.connect(_on_song_started)
	Music.song_stopped.connect(_on_song_stopped)


func _process(_delta: float) -> void:
	scroll_horizontal += 1
	if scroll_horizontal >= rich_text_label.size.x - size.x:
		scroll_horizontal = 0


func _on_song_started() -> void:
	rich_text_label.text = Music.get_current_song().get_song_info_as_bbcode(title_color, artist_color, album_color)
	rich_text_label.text = rich_text_label.text.lpad(rich_text_label.text.length() + int(size.x / 4))
	rich_text_label.text = rich_text_label.text.rpad(rich_text_label.text.length() + int(size.x / 4))
	scroll_horizontal = 0


func _on_song_stopped() -> void:
	rich_text_label.text = ""

This will create a MusicMarqueeHScrollContainer node you can add to your project like any normal Node. (Minus the fancy icon.)

image

If you use my Sound plugin, it will just work. You can see an example of it in the test project.

If you don’t want to use that, you just need to create your own Signals for when the song starts and stops, and another way to get the song information.

You can use something like this to get the title:

var title: String
if stream is AudioStreamOggVorbis or stream is AudioStreamWAV:
	var tags: Dictionary = stream.get_tags()
	if tags.has("title"):
		title = tags["title"]
if title.is_empty():
	title = stream.resource_path.get_file().to_snake_case().trim_suffix(".ogg").trim_suffix(".wav").capitalize()
1 Like