class_name BrowserApp extends ImplantApp ## Data browser implant app (T-1133, D-254 §4). A SEPARATE app from the Atlas ## (implant/map) — the six registry entities do not share the Atlas's ## geographic drill-down (D-254 SS4, Jeroen's IA ruling). Registered as ## "implant/browser" in FULLSCREEN mode, key B. ## ## Nav: kind menu (root) -> index (per kind, scrollable + searchable) -> ## detail (per entity). Same push/pop convention the Atlas app uses. ## ## Composed entirely from D-169 implant components via the three screen ## classes below (BrowserKindMenuScreen/BrowserIndexScreen/BrowserDetailScreen) ## — no new UI primitives. var _kind_menu_screen = null # BrowserKindMenuScreen var _index_screen = null # BrowserIndexScreen var _detail_screen = null # BrowserDetailScreen func _ready() -> void: manifest = load("res://ui/implant/apps/browser/app.tres") super._ready() func on_install() -> void: var implant_theme = load("res://ui/implant/default_implant.tres") _kind_menu_screen = BrowserKindMenuScreen.new() _kind_menu_screen.setup(implant_theme) _kind_menu_screen.kind_selected.connect(_on_kind_selected) register_screen("kind_menu", _kind_menu_screen) _index_screen = BrowserIndexScreen.new() _index_screen.setup(implant_theme) _index_screen.row_selected.connect(_on_row_selected) register_screen("index", _index_screen) _detail_screen = BrowserDetailScreen.new() _detail_screen.setup(implant_theme) register_screen("detail", _detail_screen) SimBridge.browse_response_received.connect(_on_browse_response_received) nav.set_default("kind_menu") func _unhandled_key_input(event: InputEvent) -> void: if not event is InputEventKey: return if manifest == null or not HudGroups.is_app_active(manifest.app_path): return if not event.is_pressed() or event.is_echo(): return _handle_key(event as InputEventKey) get_viewport().set_input_as_handled() func _handle_key(event: InputEventKey) -> void: # Search-mode: on the index screen, printable keys type into the live # filter instead of being interpreted as navigation. Split into its own # function (not inlined here) — it is a genuinely separate input mode # from the top-level app navigation below, and keeping it here would # push this function's branch/return count well past what one function # should hold. if current_screen_id() == "index" and _index_screen: _handle_index_search_key(event) return match event.physical_keycode: KEY_B: HudGroups.close_app() KEY_ESCAPE: _close_or_pop() KEY_ENTER, KEY_KP_ENTER: _handle_enter() KEY_UP: if current_screen_id() == "kind_menu" and _kind_menu_screen: _kind_menu_screen.navigate(-1) KEY_DOWN: if current_screen_id() == "kind_menu" and _kind_menu_screen: _kind_menu_screen.navigate(1) ## Key handling while the index screen is current. Arrows/enter/escape/ ## backspace are the always-active control keys; everything else in the ## printable range (D-254 §4: "filterable/searchable by name") appends to ## the live search buffer. This mirrors the implant's existing ## keyboard-as-input-surface convention (no OS textbox anywhere in this ## component library) — there is no separate "focus the search box" step. func _handle_index_search_key(event: InputEventKey) -> void: match event.physical_keycode: KEY_UP: _index_screen.navigate(-1) return KEY_DOWN: _index_screen.navigate(1) return KEY_ENTER, KEY_KP_ENTER: _index_screen.trigger_enter() return KEY_ESCAPE: if _index_screen.has_search_text(): _index_screen.clear_search() else: _close_or_pop() return KEY_BACKSPACE: _index_screen.backspace_search() return # event.unicode carries the actual typed character (shift/caps already # resolved by the platform), NOT the physical keycode — String.chr() is # GDScript's codepoint-to-one-char-String conversion. Printable range # only (32 = space .. 126 = ~): control characters (arrows, tab, etc. # already handled above by physical_keycode) report unicode == 0 or a # non-printable codepoint and must not leak into the search buffer. if event.unicode >= 32 and event.unicode < 127: _index_screen.append_search_char(String.chr(event.unicode)) func _close_or_pop() -> void: if current_screen_id() == "kind_menu": HudGroups.close_app() else: nav.pop() func _handle_enter() -> void: match current_screen_id(): "kind_menu": if _kind_menu_screen: _kind_menu_screen.trigger_enter() # ============================================================================= # Signal handlers # ============================================================================= func _on_kind_selected(kind: String) -> void: nav.push("index", {"kind": kind, "filter_system_id": ""}) func _on_row_selected(entity_id: String) -> void: var kind: String = _index_screen.current_kind() if _index_screen else "" nav.push("detail", {"kind": kind, "entity_id": entity_id}) func _on_browse_response_received(response: Dictionary) -> void: if response == null: return # Fan out to whichever screen is currently waiting on a response for this # kind — both screens no-op via their own kind-match guard if the # response isn't theirs (a stale one from a prior navigation, or one # meant for the other screen type). if _index_screen: _index_screen.receive_response(response) if _detail_screen: _detail_screen.receive_response(response)