How to make folding menu?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By fish2091852127

How to make a folding menu like this:
(they looks like a drawer, usually its closes.
When one is opened, expand the window it contains and squeeze down the other drawers)

:bust_in_silhouette: Reply From: flurick

Here is an example of a custom node that works like an accordion.

enter image description here

enter image description here

The Control moves each child to the end of the previous one (only in the y direction)
enter image description here

extends Control

export var spacing = 10

func _draw():
	
	var last_end_achor = Vector2.ZERO
	for child in get_children():
		child.rect_position = last_end_achor
		last_end_achor.y = child.rect_position.y + child.rect_size.y 
		last_end_achor.y += spacing
	
	rect_min_size.y = last_end_achor.y #to work with ScrollContainer

And clicking on the button “show” toggles the parent “Panel” size to the open or the closed value.
enter image description here

extends Panel

var is_expanded = false


func _ready():
	$VBoxContainer/show.connect("pressed",self,"expand")


func expand():
	is_expanded = !is_expanded


var last_rect_size = Vector2.ZERO
func _process(delta):
	
	#snap to end
	if abs(rect_size.y-rect_min_size.y) < 1:
		rect_size.y = rect_min_size.y
	
	#resize to target size
	if is_expanded:
		rect_size.y = lerp(rect_size.y, 70, 0.1)
	else:
		rect_size.y = lerp(rect_size.y, rect_min_size.y, 0.1)
	
	#update layout
	if last_rect_size != rect_size:
		get_parent().update()
		last_rect_size = rect_size

Wow!!!Thanks!!

fish2091852127 | 2019-03-18 08:05

I followed your instruction to make this menu. But the items in panel didn’t close. Can you help?

hoody monkey | 2019-12-06 02:29

What I have done to make this work is to make the show button a child of the root and then set “Clip Content” in the panel rect properties to true.

Now the content of the panel will shrink with the panel.

If I find the time I will post an github project with an working reusable example.

R. K. | 2020-02-11 10:22

For me that solution doesn’t work well with spinbox. It makes an overflow due to callback update. I have done a simpler script on a VBoxContainer and a button “show” as his child and it seems to work.

extends VBoxContainer

export var is_expanded = true

func _ready():
	$show.connect("pressed",self,"expand")
	get_parent().connect("expand",self,"resized")


func expand():
	is_expanded = !is_expanded
	for child in get_children():
		child.visible = true if is_expanded else child == $show

m21-cerutti | 2022-02-21 20:18

1 Like

These answers not work in godot 4. Just add a solution of mine.
This script is linked to the ExpandableVbox, and the vbox is saved to a branch scene. So that it can be used in other scene(just drag nodes as children)
微信截图_20240927144258
You can call theexpand method of it by other signals like button, or just enable the _gui_input function(and replace 0 with min size)

extends VBoxContainer

@export var is_expanded = true

enum STATE {OPEN,CLOSE,OPENING,CLOSING}

var init = false
var state :STATE
var max_size : Vector2i
var last_size : Vector2i

@onready var v_box = $"."

func _ready():
    state = STATE.OPEN


#func _gui_input(event):
    #if event is InputEventMouseButton:
        #if event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
            #self.expand()

func expand():
    is_expanded = !is_expanded
    if is_expanded:
        state = STATE.OPENING
    else:
        state = STATE.CLOSING


func _process(delta):
    if not init: # not in ready beacuse ready do not get corret size
        max_size = v_box.size
        last_size = v_box.size
        v_box.custom_minimum_size.y = max_size.y
        init = true
                
    if state == STATE.CLOSING:
        v_box.size_flags_vertical = Control.SIZE_SHRINK_BEGIN
        #print(last_size)
        if v_box.custom_minimum_size.y > 0:
            v_box.custom_minimum_size.y = lerp(last_size.y,0,0.1)
            last_size = v_box.custom_minimum_size
        elif v_box.custom_minimum_size.y == 0:
            v_box.size_flags_vertical = Control.SIZE_FILL
            for child in v_box.get_children():
                child.visible = true if is_expanded else child == $show
            state = STATE.CLOSE
    
    elif state == STATE.OPENING:
        v_box.size_flags_vertical = Control.SIZE_SHRINK_BEGIN
        for child in v_box.get_children():
            child.visible = true if is_expanded else child == $show
        if v_box.custom_minimum_size.y < max_size.y:
            v_box.custom_minimum_size.y = lerp(last_size.y,max_size.y,0.1)
            last_size = v_box.custom_minimum_size
        elif v_box.custom_minimum_size.y == max_size.y:
            v_box.size_flags_vertical = Control.SIZE_FILL
            state = STATE.OPEN