Auto Text Resizing

Godot Version

4.3.1

Question

Hello,

I’m trying to make a UI, but I’m having trouble getting my font size to adapt to the size of my window.

This is what my window looks like:

But when I enlarge the window, the text does not change size:

My nodes for the UI look like this:

image

Is there any easy option for labels that allows them to automatically change their font size based on their parent controller width/height?

Thanks

Hey! There is probably a better solution, but I had the same problem several months ago now, I share to you my custom solution:

At the time I had created this static function, which you can call:
_ On initialization
_ When the font changes
_ When the parent size changes
_ When the viewport size changes
_ When the text changes

The script will try to fit the text in the parent

class_name FixFontTool


static func apply_text_with_corrected_max_scale(parent_size: Vector2, label: Label, text: String, scale: float = 1.0, should_correct_shadow: bool = false, shadow_offset: Vector2 = Vector2()):
	label.text = text

	var default_font_size = 16
	var default_text_size = label.get_theme_font("font_size").get_string_size(text, HORIZONTAL_ALIGNMENT_CENTER, -1, default_font_size)

	var scale_to_apply_to_font = parent_size.x / default_text_size.x

	if default_text_size.x / default_text_size.y < parent_size.x / parent_size.y:
		scale_to_apply_to_font = parent_size.y / default_text_size.y

	label.add_theme_font_size_override("font_size", int(scale_to_apply_to_font * default_font_size * scale))

	if should_correct_shadow:
		label.add_theme_constant_override("shadow_offset_x", int(shadow_offset.x * scale_to_apply_to_font * scale))
		label.add_theme_constant_override("shadow_offset_y", int(shadow_offset.y * scale_to_apply_to_font * scale))

You should probably adapt the code to your needs, personally I had created a CustomLabel, which extend from Label, and which I used in my UI instead of classic Label (I used @tool to be able to have a preview in the editor)

@tool
class_name CustomLabel extends Label


@export_range(0.5, 5) var font_scale: float = 1.0

@export var parent: Control

@export var should_correct_shadow: bool

@export var shadow_offset: Vector2

@export var force_refresh: bool


var _previous_font_scale: float = -1.0
var _previous_viewport_size: Vector2 = Vector2()
var _previous_text: String = ""
var _previous_parent_size: Vector2 = Vector2()


func _process(__delta: float) -> void:
	if self.parent == null:
		return

	var viewport_size: Vector2 = get_viewport_rect().size
	var parent_size = self.parent.get_rect().size

	if (not Engine.is_editor_hint() 
		and
		(self._previous_font_scale != self.font_scale or
		 self._previous_text != self.text or 
		 self._previous_parent_size != parent_size)
		 or
		 self._previous_viewport_size != viewport_size or
		 self.force_refresh
	):		
		self._previous_viewport_size = viewport_size
		self._previous_font_scale = self.font_scale
		self._previous_text = self.text
		self._previous_parent_size = parent_size
		self.force_refresh = false
		

		FixFontTool.apply_text_with_corrected_max_scale(
			parent_size, 
			self, 
			self._previous_text, 
			self.font_scale, 
			self.should_correct_shadow, 
			self.shadow_offset
		)

1 Like

Hey, thanks for the help. I borrowed some of the stuff you wrote and adapted it for my own menu. For anyone who has the same problem later, here is my version as well (there is probably a better way to do this) :

extends Control

var full_screen = 0

# Called when the node enters the scene tree for the first time.
func _ready() -> void:
	get_tree().get_root().size_changed.connect(window_resized)
	full_screen = DisplayServer.window_get_mode()
	call_deferred("set_children", self.get_children())

func _physics_process(delta: float) -> void:
	if full_screen != DisplayServer.window_get_mode():
		full_screen = DisplayServer.window_get_mode()
		call_deferred("window_resized")

func window_resized():
	check_children(self.get_children())

func set_children(children):
	if children == null:
		return
	if len(children) == 0:
		return

	for child in children:
		set_children(child.get_children())

		if child is Label or child is TextEdit or child is Button or child is LineEdit or child is RichTextLabel:
			print(child)
			save_font_size(child, child.get_parent())

func check_children(children):
	if children == null:
		return
	if len(children) == 0:
		return

	for child in children:
		check_children(child.get_children())

		if child is Label or child is TextEdit or child is Button or child is LineEdit or child is RichTextLabel:
			fix_font_size(child, child.get_parent())

func fix_font_size(text_element, parent):
	if text_element.has_meta("fsize"):
		var font_override_default = text_element.get_meta("fsize")
		var parent_width_default = text_element.get_meta("pwidth")
		var parent_current_width = text_element.size.x

		if text_element is Label:
			parent_current_width = parent.size.x

		var difference = parent_current_width / parent_width_default
		var scale = int(font_override_default * difference)

		if text_element is RichTextLabel:
			text_element.add_theme_font_size_override("normal_font_size", scale)
			return 

		text_element.add_theme_font_size_override("font_size", scale)

func save_font_size(text_element, parent):
	if text_element == null:
		return
	if text_element.has_meta("fsize") == false:
		var font_override_default = 0
		if text_element is RichTextLabel:
			if text_element["theme_override_font_sizes/normal_font_size"] != null:
				font_override_default = text_element["theme_override_font_sizes/normal_font_size"]
			else:
				font_override_default = 16
		else:
			if text_element["theme_override_font_sizes/font_size"] != null:
				font_override_default = text_element["theme_override_font_sizes/font_size"]
			else:
				font_override_default = 16
			
		var parent_current_width = text_element.size.x

		if text_element is Label:
			parent_current_width = parent.size.x

		(text_element).set_meta("fsize", font_override_default)
		(text_element).set_meta("pwidth", parent_current_width)

I have a main root node which is a control for my menu. This root control has a script with the above code in it. The script listens for maximize, minimize, and screen size changes and then loops to check for children that can hold text. Then it checks if they have some metadata used for ratio stuff, and applies a scale to the font size based on that ratio.

1 Like