Files
settled-reach/client/ui/meta/meta_stack.gd
T
jpmschweitzerandClaude Opus 4.6 84105916cd feat(ui): introduce MetaScreen pattern foundation (#618, #680)
Establishes the faux-game-menu base pattern for non-diegetic UI,
analogous to ImplantApp but for pre-gameplay and meta-overlay
screens (main menu, character creation, settings, debug console,
bug report, loading screen). Workstream 1 of the MetaScreen
refactor — foundation only, no screen migrations yet.

- client/ui/meta/meta_screen.gd: base class (Control) with
  HIDDEN/OPENING/OPEN/CLOSING phase tracking, three orthogonal
  policy booleans (pauses_sim, closable_by_escape, captures_input),
  open/close lifecycle, on_escape contract, closed + escape_pressed
  signals, subclass hooks (on_open, on_close).
- client/ui/meta/meta_stack.gd: autoload coordinator. Overlay stack
  with push/pop/top/is_active; handle_escape chain; sim-pause
  coordination via SimBridge when pauses_sim=true; meta_active_changed
  signal. All class references kept inside method bodies — no
  top-level class_name refs, matching HudGroups / GameState
  autoload parse-order discipline.
- client/scripts/character_profile.gd: Resource wrapping the
  visual descriptor with bookmark_id and start_location_id.
  Target of the creation_confirmed signal once the character
  creation flow migrates.
- client/project.godot: MetaStack registered as autoload after
  HudGroups, before ImplantRegistry.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-19 20:18:29 +02:00

73 lines
1.9 KiB
GDScript

extends Node
## Autoload coordinator for overlay MetaScreens (#618, #680).
## Scene-root screens (main_menu, character_creation) do NOT push onto this stack.
## Only overlay screens (settings, debug_console, bug_report, loading_screen) push.
##
## Autoload parse-order: MetaScreen is a class_name type — referenced here only
## inside method bodies called at runtime, never at the top level or in _ready().
signal meta_active_changed(active: bool)
var _stack: Array = [] # Array[MetaScreen] — untyped per parse-order rule
func push(screen) -> void: # screen: MetaScreen
if screen in _stack:
return
_stack.append(screen)
screen.closed.connect(_on_screen_closed.bind(screen), CONNECT_ONE_SHOT)
if _stack.size() == 1:
meta_active_changed.emit(true)
if screen.pauses_sim:
_request_pause(true)
func pop() -> void:
if _stack.is_empty():
return
_stack[-1].close()
func top(): # returns MetaScreen or null
return _stack[-1] if not _stack.is_empty() else null
func is_active() -> bool:
return not _stack.is_empty()
## Handle ESC key. Call from main.gd before HudGroups ESC handling.
## Returns true if the event was consumed (callers must return after).
func handle_escape() -> bool:
var t = top()
if t == null:
return false
if not t.closable_by_escape:
return t.on_escape()
if t.on_escape():
return true
t.close()
return true
func _on_screen_closed(screen) -> void: # screen: MetaScreen
_stack.erase(screen)
if screen.pauses_sim and not _any_pausing():
_request_pause(false)
if _stack.is_empty():
meta_active_changed.emit(false)
func _any_pausing() -> bool:
for s in _stack:
if s.pauses_sim:
return true
return false
func _request_pause(pause: bool) -> void:
if SimBridge.state != SimBridge.ConnectionState.CONNECTED:
return
var action = InputMapper.Action.PAUSE if pause else InputMapper.Action.UNPAUSE
SimBridge.send_input({"action": action, "timestamp_msec": Time.get_ticks_msec()})