RichText, changing hyperlink colors on hover

Godot Version

4.4

Question

I use meta_clicked to show players what Turn Sequence means. This is sample RichText bbCode value. All works great when players click the “hyperlink”

image

But I would like to make this “hyperlink” more interactive. When user hovers mouse on the “Turn Sequence” from above, I would like it to change color. The tricky part is I do not want to change color of entire RichText , but only the actual “hyperlink”.

I used following signals

Signals work. But what should I do to actually change the color of the hyperlink? (or give some other visual that will tell players this field can be clicked or is interactive).

I tried to look into documentation of meta_push , but I could not make it work .

An option is to String.format() the RichTextLabel.text with the color and assign it back again to the RichTextLabel.text:

Example:

The RichTextLabel.text is:

* [color={{meta1}}][url=meta1]Something like this[/url][/color]: Thing here
* [color={{meta2}}][url=meta2]Something like this[/url][/color]: Thing here
extends RichTextLabel


@export var hover_colors: Dictionary[String, Color]

@onready var original_text = text


func _ready() -> void:
	_set_meta_color(null)
	meta_hover_started.connect(_set_meta_color.bind(true))
	meta_hover_ended.connect(_set_meta_color.bind(false))


func _set_meta_color(meta: Variant, hovering: bool = false, default_color: Color = Color.RED) -> void:
	var colors = {}
	for key in hover_colors.keys():
		if hovering and meta == key:
			colors.set(key, hover_colors[key].to_html())
		else:
			colors.set(key, default_color.to_html())
	text = original_text.format(colors, "{{_}}")

But it may not work fine in some cases or may cause the layout to shift. In the video below it’s the one on the top.

Another option is to implement a RichTextEffect and update it when needed. This method will fail to change the color of the underline though.

The RichTextLabel.text is:

* [meta_hover on=blue off=red meta=meta1][url=meta1]Something like this[/url][/meta_hover]: Thing here
* [meta_hover on=green off=yellow meta=meta2][url=meta2]Something like this[/url][/meta_hover]: Thing here
extends RichTextLabel


var meta_hover_effect := MetaHoverEffect.new()


func _ready() -> void:
	install_effect(meta_hover_effect)
	meta_hover_started.connect(func(meta: Variant):
		if not meta_hover_effect.current_metas.has(meta):
			meta_hover_effect.current_metas.append(meta)
	)
	meta_hover_ended.connect(func(meta: Variant):
		meta_hover_effect.current_metas.erase(meta)
	)


class MetaHoverEffect extends RichTextEffect:


	var bbcode = "meta_hover"
	var current_metas: Array[String]


	func _process_custom_fx(char_fx: CharFXTransform) -> bool:
		var meta = char_fx.env.get("meta", null)
		var on = char_fx.env.get("on", "red")
		var off = char_fx.env.get("off", "red")

		if current_metas.has(meta):
			char_fx.color = Color.from_string(on, Color.RED)
		else:
			char_fx.color = Color.from_string(off, Color.RED)

		return true

In the video is the bottom one.

2 Likes

thank you ! will try it out tonight :slight_smile:

it worked!

thank you so much, this wasn’t simple answer but you actually wrote code and made video for me . Greatly appreciated!

1 Like

one more question, how would you suggest modifying 2nd method , so it also changes to 3rd color on press? it does not seem there is MetaHoverEffect equivalent for Press or Click

Kinda like this:

* [meta_hover on=blue off=red click=magenta meta=meta1][url=meta1]Something like this[/url][/meta_hover]: Thing here
* [meta_hover on=green off=yellow click=orange meta=meta2][url=meta2]Something like this[/url][/meta_hover]: Thing here
extends RichTextLabel


var meta_hover_effect := MetaHoverEffect.new()


func _ready() -> void:
	install_effect(meta_hover_effect)
	
	meta_hover_started.connect(func(meta: Variant):
		if not meta_hover_effect.current_metas.has(meta):
			meta_hover_effect.current_metas.append(meta)
	)
	meta_hover_ended.connect(func(meta: Variant):
		meta_hover_effect.current_metas.erase(meta)
	)


func _gui_input(event: InputEvent) -> void:
	if event is InputEventMouseButton:
		if event.button_index == MOUSE_BUTTON_LEFT:
			meta_hover_effect.meta_is_clicked = event.pressed


class MetaHoverEffect extends RichTextEffect:


	var bbcode = "meta_hover"
	var current_metas: Array[String]
	var meta_is_clicked: bool = false


	func _process_custom_fx(char_fx: CharFXTransform) -> bool:
		var meta = char_fx.env.get("meta", null)
		var on = char_fx.env.get("on", "red")
		var off = char_fx.env.get("off", "red")
		var click = char_fx.env.get("click", "red")

		if current_metas.has(meta):
			if meta_is_clicked:
				char_fx.color = Color.from_string(click, Color.RED)
			else:
				char_fx.color = Color.from_string(on, Color.RED)
		else:
			char_fx.color = Color.from_string(off, Color.RED)

		return true

Result:

We can’t use the RichTextLabel.meta_clicked signal because it gets fired on mouse release.

1 Like

amazing, thank you

this adds a lot of polish to my game UI