UI improvements

This commit is contained in:
2026-06-14 10:19:31 -05:00
parent 8ffde98fdd
commit 0cf1f92eaa
16 changed files with 263 additions and 114 deletions
+57 -15
View File
@@ -1,18 +1,19 @@
class_name ClosableMenu extends Control
@export var isOpen: bool:
set(newValue):
isOpen = newValue
visible = newValue
if newValue:
opened.emit()
else:
closed.emit()
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
signal closed
signal opened
var _savedFocusNode:Control = null
func _enter_tree() -> void:
visible = isOpen
@@ -22,13 +23,54 @@ func _exit_tree() -> void:
func _ready() -> void:
visible = isOpen
print("ClosableMenu is ready, isOpen: ", isOpen)
func close() -> void:
isOpen = false
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:
isOpen = !isOpen
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
if _savedFocusNode != null and is_instance_valid(_savedFocusNode):
_savedFocusNode.grab_focus()
else:
_grabInitialFocus()
func _grabInitialFocus() -> void:
pass
+2 -4
View File
@@ -6,6 +6,7 @@ signal confirmed
@export var btnNo:Button
func _ready() -> void:
super._ready()
close()
btnYes.pressed.connect(_onYes)
btnNo.pressed.connect(close)
@@ -18,13 +19,10 @@ func _onYes() -> void:
close()
confirmed.emit()
func open() -> void:
super.open()
func _grabInitialFocus() -> void:
btnNo.grab_focus()
func _unhandled_input(event:InputEvent) -> void:
if !isOpen:
return
if event.is_action_pressed("ui_cancel"):
close()
get_viewport().set_input_as_handled()
+9 -41
View File
@@ -1,48 +1,16 @@
class_name ModalBackdrop extends ColorRect
# Tracks which overlays are currently open. Each entry must be a direct sibling
# (child of the same parent). The backdrop repositions itself in the scene tree
# to sit immediately below whichever open overlay has the highest tree index,
# so only one backdrop is ever visible regardless of how many overlays are open.
var _openOverlays:Array[Control] = []
func _ready() -> void:
visible = false
mouse_filter = MOUSE_FILTER_IGNORE
UI.FOCUS_STACK.activeLayerChanged.connect(_onActiveLayerChanged)
func register(overlay:Control) -> void:
assert(overlay.get_parent() == get_parent(), "ModalBackdrop: overlay must be a sibling")
assert(overlay.has_signal("opened") and overlay.has_signal("closed"),
"ModalBackdrop: overlay must have opened/closed signals")
overlay.connect("opened", func(): _onOpened(overlay))
overlay.connect("closed", func(): _onClosed(overlay))
func _onOpened(overlay:Control) -> void:
if overlay not in _openOverlays:
_openOverlays.append(overlay)
_reposition()
func _onClosed(overlay:Control) -> void:
_openOverlays.erase(overlay)
_reposition()
func _reposition() -> void:
if _openOverlays.is_empty():
func _onActiveLayerChanged(layer:ClosableMenu) -> void:
if layer == null or layer.get_parent() != get_parent():
visible = false
return
visible = true
var top := _topOverlay()
var topIdx := top.get_index()
var myIdx := get_index()
if myIdx == topIdx - 1:
return
if myIdx < topIdx:
get_parent().move_child(self, topIdx - 1)
mouse_filter = MOUSE_FILTER_IGNORE
z_index = 0
else:
get_parent().move_child(self, topIdx)
func _topOverlay() -> Control:
var top:Control = _openOverlays[0]
for overlay in _openOverlays:
if overlay.get_index() > top.get_index():
top = overlay
return top
visible = true
mouse_filter = MOUSE_FILTER_STOP
z_index = layer.z_index - 5