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:

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