D-226 layer 4, rebuilt against the post-D-255 stepped Atlas after the Phase-1 reconciliation (the original intent list targeted the retired continuous-zoom viewer). Eleven intents, each backed by the exact production handler a click calls — select/open for systems and bodies (extracted shared by-id tails so click and intent paths are one code path), scroll_rung, reset_view, back, open/close_atlas, set_overlay — plus two new first-class capabilities: jump_to_center (the fixed-center revisit pattern proven by five eyeball drivers, via a new StepCanvasViewer.jump_to seam that reuses _scroll_rung's exact request tail — same extent cap, same cache keys) and get_current_canvas_summary (allocation-light reads off the raw wire dict, courses-by-class, draw-matched settlement dedup — no PNG decode). Contract shape: dumb AtlasAgentBridge autoload holding the app handle (untyped per the parse-order rule), all logic in the static AtlasAgentInterface class. observe() is side-effect-free: current state + a generic Control-walk affordance tree. In-process consumers only this ticket (documented); the committed reference driver (atlas_agent_driver.gd, InputSwallower + settle-until-ready from the T-1157 inventory) replaces the scratch eyeball drivers as the sanctioned headless-drive pattern. select_city/open_regional dropped with recorded rationale (no settlement hit-test affordance exists post-D-255) — diff on the ticket. 33 new tests across three suites. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
160 lines
4.9 KiB
GDScript
160 lines
4.9 KiB
GDScript
class_name ImplantApp
|
|
extends Control
|
|
## Base class for all implant apps. Absorbs HudGroups boilerplate; subclasses
|
|
## override lifecycle hooks only (#844, D-191).
|
|
##
|
|
## === Lifecycle ordering ===
|
|
##
|
|
## on_install() — called once from _ready(), after nav is created but BEFORE any
|
|
## HudGroups open event fires. The nav stack is EMPTY at this point. Use this
|
|
## hook to: construct screens and call register_screen(id, screen), set
|
|
## nav.set_default("..."), wire intra-screen signals. Do NOT rely on
|
|
## current_screen_id() here — no screen has been pushed yet.
|
|
##
|
|
## on_open(mode) — called every time HudGroups activates this app (FULLSCREEN or
|
|
## INSERT). By the time on_open fires, the base has ensured the nav stack is
|
|
## non-empty: if preserves_state=false, nav.reset_to_default() was called; if
|
|
## preserves_state=true and the stack was empty, nav.push_default() was called.
|
|
## nav.current() returns the visible screen id. Safe to read navigation state
|
|
## and trigger data refreshes here.
|
|
##
|
|
## on_close() — called every time HudGroups deactivates this app (GAMEPLAY mode
|
|
## or another app taking focus). Nav stack state is preserved here — do not
|
|
## push or pop screens in on_close. Use this hook for: pausing timers, stopping
|
|
## animations, unsubscribing from high-frequency feeds. The stack survives
|
|
## intact for the next on_open (if preserves_state=true).
|
|
##
|
|
## on_insert_deactivated() — called by SnapshotConsumers when the server drops
|
|
## insert state. The base implementation closes the app only if it is currently
|
|
## active in INSERT mode. FULLSCREEN apps inherit a no-op; override to add
|
|
## custom handling (e.g. save draft, emit warning). Do not call close_app()
|
|
## manually — call super() or replicate the guard condition.
|
|
##
|
|
## === Subclass _ready() pattern ===
|
|
## func _ready() -> void:
|
|
## manifest = load("res://ui/implant/apps/my_app/app.tres")
|
|
## super._ready()
|
|
## # additional init here if needed
|
|
|
|
signal app_opened(mode: int)
|
|
signal app_closed
|
|
|
|
var manifest: ImplantAppManifest = null
|
|
var nav: ImplantNavStack = null
|
|
|
|
var _screens: Dictionary = {} # screen_id → Control
|
|
var _current_screen_id: String = ""
|
|
|
|
|
|
func _ready() -> void:
|
|
set_anchors_preset(Control.PRESET_FULL_RECT)
|
|
mouse_filter = Control.MOUSE_FILTER_STOP
|
|
visible = false
|
|
|
|
if manifest:
|
|
HudGroups.register(self, manifest.app_path)
|
|
HudGroups.app_changed.connect(_internal_app_changed)
|
|
|
|
nav = ImplantNavStack.new()
|
|
nav.screen_changed.connect(_on_screen_changed)
|
|
add_child(nav)
|
|
|
|
on_install()
|
|
|
|
|
|
func _internal_app_changed(app_path: String, mode: int) -> void:
|
|
if manifest == null:
|
|
return
|
|
if app_path != manifest.app_path:
|
|
if visible:
|
|
visible = false
|
|
on_close()
|
|
app_closed.emit()
|
|
return
|
|
|
|
match mode:
|
|
HudGroups.Mode.FULLSCREEN, HudGroups.Mode.INSERT:
|
|
if not visible:
|
|
visible = true
|
|
if not manifest.preserves_state:
|
|
nav.reset_to_default()
|
|
elif nav.is_empty():
|
|
nav.push_default()
|
|
on_open(mode)
|
|
app_opened.emit(mode)
|
|
HudGroups.Mode.GAMEPLAY:
|
|
if visible:
|
|
visible = false
|
|
on_close()
|
|
app_closed.emit()
|
|
|
|
|
|
# --- Lifecycle hooks — subclasses override ---
|
|
|
|
|
|
func on_install() -> void:
|
|
pass
|
|
|
|
|
|
func on_open(_mode: int) -> void:
|
|
pass
|
|
|
|
|
|
func on_close() -> void:
|
|
pass
|
|
|
|
|
|
func on_insert_deactivated() -> void:
|
|
# Default: close only if active in INSERT mode. FULLSCREEN apps override to customize.
|
|
if manifest and HudGroups.is_app_active(manifest.app_path):
|
|
if HudGroups.get_active_mode() == HudGroups.Mode.INSERT:
|
|
HudGroups.close_app()
|
|
|
|
|
|
# --- Screen management ---
|
|
|
|
|
|
## Register a screen under an id. Base adds it as a child and starts it hidden.
|
|
## Call from on_install(). Wire signals before calling register_screen.
|
|
func register_screen(id: String, screen: Control) -> void:
|
|
if _screens.has(id):
|
|
push_warning("ImplantApp: screen id '%s' already registered" % id)
|
|
return
|
|
_screens[id] = screen
|
|
screen.visible = false
|
|
if screen.get_parent() == null:
|
|
add_child(screen)
|
|
|
|
|
|
func current_screen_id() -> String:
|
|
return _current_screen_id
|
|
|
|
|
|
## T-971 (AtlasAgentInterface, D-226 item 4): a generic, read-only screen
|
|
## lookup by id — the same `_screens` dict register_screen() already
|
|
## maintains, exposed so an agent-control channel can reach a specific
|
|
## screen instance (StepCanvasViewer via RegionalScreen.get_viewer(), etc.)
|
|
## without every ImplantApp subclass growing its own bespoke getters. Returns
|
|
## null for an unregistered id, matching Dictionary.get()'s own default.
|
|
func get_screen(screen_id: String) -> Variant:
|
|
return _screens.get(screen_id, null)
|
|
|
|
|
|
func _on_screen_changed(new_id: String) -> void:
|
|
if _current_screen_id != new_id:
|
|
var old: Control = _screens.get(_current_screen_id, null)
|
|
if old:
|
|
if old.has_method("leave"):
|
|
old.leave()
|
|
old.visible = false
|
|
_current_screen_id = new_id
|
|
var s: Control = _screens.get(new_id, null)
|
|
if s:
|
|
s.visible = true
|
|
if s.has_method("enter"):
|
|
s.enter(nav.current_payload())
|
|
|
|
|
|
func handle_intent(_action: String, _params: Dictionary) -> void:
|
|
pass
|