How to tween/transition between Button states / styleboxes

Godot Version

Godot 4.2.1.stable

Question

Hey everyone,
I’m trying to figure out how to tween/transition between my button states.
Currently I’m attempting this by grabbing the theme styleboxes, then using those to overwrite and tween to it, but no matter what I do, the button stylebox doesn’t tween but just switches instantly (in the script with a 1sec delay as set in the tween).

Maybe my approach is wrong, would appreciate any pointers:

extends Button

var button_tween : Tween
var hover_indent_x = 5
var hover_stylebox : StyleBox
var normal_stylebox : StyleBox


func _ready() -> void: #Connecting to needed signals.
	mouse_entered.connect(_on_button_mouse_entered)
	mouse_exited.connect(_on_button_mouse_exited)
	focus_entered.connect(_on_focus_entered)
	focus_exited.connect(_on_focus_exited)	
	
	normal_stylebox = get_theme_stylebox("normal")
	hover_stylebox = get_theme_stylebox("hover")
	add_theme_stylebox_override("hover", normal_stylebox)


func tween_to_hover(): 
	if button_tween and button_tween.is_running():
		button_tween.kill()
	button_tween = create_tween().set_ease(Tween.EASE_IN_OUT).set_trans(Tween.TRANS_CUBIC)
	button_tween.tween_property(self,"position",self.position + Vector2(hover_indent_x,0),.2)
	button_tween.tween_property(self,"theme_override_styles/hover",hover_stylebox,1)

You can’t tween a whole StyleBox like that. You’ll need to do a bit more setup to get it working.

Because StyleBox is a Resource and resources are shared by default, we can use the same StyleBox, override the other StyleBoxes with it, and tween the properties of that StyleBox when BaseButton.get_draw_mode() changes.

For example:

extends Button

var tween_stylebox:StyleBoxFlat
var styleboxes:Dictionary = {}
var current_state = BaseButton.DRAW_NORMAL

var tween:Tween

func _ready() -> void:
	# Duplicate the normal stylebox. We are going to use it as our base stylebox
	tween_stylebox = get_theme_stylebox('normal').duplicate()
	# Save the different styleboxes to be able to tween between their properties later
	styleboxes[BaseButton.DRAW_NORMAL] = get_theme_stylebox('normal').duplicate()
	styleboxes[BaseButton.DRAW_HOVER] = get_theme_stylebox('hover').duplicate()
	styleboxes[BaseButton.DRAW_PRESSED] = get_theme_stylebox('pressed').duplicate()
	styleboxes[BaseButton.DRAW_HOVER_PRESSED] = get_theme_stylebox('pressed').duplicate()
	# Override all the other styleboxes with our tween stylebox
	add_theme_stylebox_override('normal', tween_stylebox)
	add_theme_stylebox_override('hover', tween_stylebox)
	add_theme_stylebox_override('pressed', tween_stylebox)


func _process(delta: float) -> void:
	if get_draw_mode() != current_state:
		# If the draw mode changed
		current_state = get_draw_mode()
		# Kill the running tween
		if tween and tween.is_running():
			tween.kill()
		# And create a new one
		tween = create_tween().set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_CUBIC).set_parallel()
		# That tweens some properties of our tween stylebox to the target stylebox
		# depending on the current state
		var target = styleboxes[current_state] as StyleBoxFlat
		tween.tween_property(tween_stylebox, "bg_color", target.bg_color, 0.2)
		tween.tween_property(tween_stylebox, "border_color", target.border_color, 0.2)
		tween.tween_property(tween_stylebox, "corner_radius_top_left", target.corner_radius_top_left, 0.2)
		tween.tween_property(tween_stylebox, "corner_radius_top_right", target.corner_radius_top_right, 0.2)
		tween.tween_property(tween_stylebox, "corner_radius_bottom_left", target.corner_radius_bottom_left, 0.2)
		tween.tween_property(tween_stylebox, "corner_radius_bottom_right", target.corner_radius_bottom_right, 0.2)

Result:

2 Likes

Amazing, thank you so much for this, that’s exactly what I was looking for!
Really appreciate your explanation and the example.