class_name ClosableMenu extends Control signal opened signal closed signal focusGained signal focusLost @export var canClose:bool = true @export var isOpen:bool = false: set(v): isOpen = v visible = v get(): return isOpen var _savedFocusNode:Control = null func _enter_tree() -> void: visible = isOpen func _exit_tree() -> void: visible = false func _ready() -> void: visible = isOpen if canClose: set_process_unhandled_input(false) func open() -> void: visible = true if canClose: UI.FOCUS_STACK.push(self) isOpen = true opened.emit() func close() -> void: if canClose: UI.FOCUS_STACK.pop() isOpen = false closed.emit() func toggle() -> void: if isOpen: close() else: open() func _onFocusGained() -> void: set_process_unhandled_input(true) get_viewport().gui_focus_changed.connect(_onViewportFocusChanged) if _savedFocusNode != null and is_instance_valid(_savedFocusNode): _savedFocusNode.grab_focus() else: _grabInitialFocus() var currentFocus:Control = get_viewport().gui_get_focus_owner() if currentFocus != null and is_ancestor_of(currentFocus): _savedFocusNode = currentFocus focusGained.emit() func _onFocusLost() -> void: _savedFocusNode = get_viewport().gui_get_focus_owner() if get_viewport().gui_focus_changed.is_connected(_onViewportFocusChanged): get_viewport().gui_focus_changed.disconnect(_onViewportFocusChanged) set_process_unhandled_input(false) focusLost.emit() func _onViewportFocusChanged(control:Control) -> void: if control == null or is_ancestor_of(control): return var node:Node = control while node != null: if node is Popup: return node = node.get_parent() if _savedFocusNode != null and is_instance_valid(_savedFocusNode): _savedFocusNode.grab_focus() else: _grabInitialFocus() func _grabInitialFocus() -> void: pass