Dynamic font scaling in Godot 4.4.1

My solution for dynamic font scaling in Godot (i.e. being able to increase/decrease all fonts in the game in the config menu). I searched for it a while ago, and found nothing, so I’m sharing my solution here to the next person who needs it.

EDIT: forgot to mention that this snippet runs on an autoload script

extends Node


signal font_size_changed

# Font size options, displayed in an
# OptionButton node in the config menu
enum FontSize {
	SMALL = 0,
	MEDIUM = 1,
	LARGE = 2
}

var _current_font_size_mult: float = 1

var _original_sizes: Dictionary[Node, int] = {}
var _rich_text_original_sizes: Dictionary[Node, int] = {}

var _font_size_mult: Dictionary[FontSize, float] = {
	FontSize.SMALL: 1,
	FontSize.MEDIUM: 1.25,
	FontSize.LARGE: 1.5
}


# Public functions
func get_font_size_mult() -> float:
	return _current_font_size_mult


func get_font_size_index() -> FontSize:
	return _font_size_mult.find_key(_current_font_size_mult)


func set_font_size_index(size: FontSize) -> void:
	if not _font_size_mult.has(size):
		return

	var new_font_size = _font_size_mult.get(size)
	if new_font_size == _current_font_size_mult:
		return

	_current_font_size_mult = new_font_size

	_update_all_fonts()

	font_size_changed.emit()


# Lifecycle overrides
func _ready() -> void:
	get_tree().node_added.connect(_add_to_list_and_update)
	get_tree().node_removed.connect(_remove_from_list)

	_font_size_initial_update.call_deferred()


# Private functions
func _font_size_initial_update() -> void:
	for node in get_tree().current_scene.find_children("*"):
		_add_to_list_and_update(node)

	# Also have to update the fonts on my scene manager autoload
	for node in SceneManger.find_children("*"):
		_add_to_list_and_update(node)



func _update_all_fonts() -> void:
	for node in _original_sizes:
		node.add_theme_font_size_override("font_size", _original_sizes[node] * _current_font_size_mult)

	for rich_text in _rich_text_original_sizes:
		rich_text.add_theme_font_size_override("normal_font_size", _rich_text_original_sizes[rich_text] * _current_font_size_mult)


func _add_to_list_and_update(node: Node) -> void:
	if node is RichTextLabel:
		_rich_text_original_sizes[node] = node.get_theme_font_size("normal_font_size")
		node.add_theme_font_size_override("normal_font_size", _rich_text_original_sizes[node] * _current_font_size_mult)
	elif node is Control or node is PopupMenu:
		_original_sizes[node] = node.get_theme_font_size("font_size")
		node.add_theme_font_size_override("font_size", _original_sizes[node] * _current_font_size_mult)

		# If you have an OptionButton in your game, you need
		# this snippet to uptade it's popup menu font
		if node is OptionButton:
			var popup_menu = node.get_popup()
			_original_sizes[popup_menu] = _original_sizes[node]
			popup_menu.add_theme_font_size_override("font_size", _original_sizes[popup_menu] * _current_font_size_mult)


func _remove_from_list(node: Node) -> void:
	if node is RichTextLabel:
		_rich_text_original_sizes.erase(node)
	elif node is Control or node is PopupMenu:
		_original_sizes.erase(node)