refactor(ui): migrate 6 meta screens to MetaScreen pattern (Workstream 2)
Relocates main_menu, character_creation, settings_dialog, debug_console,
bug_report_dialog, loading_screen from flat client/ui/ into structured
client/ui/meta/screens/<name>/. All six now extend MetaScreen instead
of Control; the base handles open/close lifecycle, visibility,
captures_input, and — for overlays — the sim-pause contract.
Screen policies set per Tyre's proposal:
- settings_dialog: pauses_sim=false, PUSHES onto MetaStack
- debug_console: pauses_sim=true, PUSHES (D-088 routing via base)
- bug_report_dialog: pauses_sim=true, PUSHES
- loading_screen: closable_by_escape=false, PUSHES
- main_menu, character_creation: scene-roots, extend MetaScreen for
the lifecycle contract only, do NOT push onto the stack
character_creation stays at its current surface (tabs, descriptor,
creation_confirmed signal unchanged). Tab consolidation and
CharacterProfile migration happen in Workstreams 5 and 6.
Knock-on changes:
- main.tscn ModalLayer CanvasLayer renamed to MetaLayer; main.gd
@onready refs updated; constants.gd comment updated; test_client_p3
and test_ui_framework_sprint15 assertions updated; test_monologue_display
and .tscn header comments updated.
- OPEN_MENU handler now pushes settings_dialog onto MetaStack before
calling open(). Full ESC priority chain lands in Workstream 4.
- atlas_app.gd: _unhandled_key_input signature widened from
InputEventKey to InputEvent with an is-check, per Godot 4 API. Pre-
existing narrowing was silently tolerated until main.tscn started
fully instantiating under the new pattern.
- test_client_p3: entity_renderer type annotations corrected from
ColorRect to Sprite2D (stale since a prior refactor); facing
indicator rotation assertion switched to angle_difference() for
modular-safe comparison.
Verification:
- gdlint client/scripts/ client/ui/ — zero problems
- godot --headless --path client --quit — no SCRIPT ERROR
- test_client_p3: 24/24 pass
- test_ui_framework_sprint15: 54/54 pass
- test_implant_nav_stack: 52/52 pass
- test_implant_registry: 42/42 pass
- test_implant_app_lifecycle: 36/36 pass
Workstream 1 foundation (84105916) remains unchanged. Workstreams 3-8
follow: protocol layer, Option A sequencing, 3-tab restructure,
Bookmark tab, location picker, Skills stub.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://ui/bug_report_dialog.gd" id="1_bugreport"]
|
||||
[ext_resource type="Script" path="res://ui/meta/screens/bug_report/bug_report_dialog.gd" id="1_bugreport"]
|
||||
|
||||
; #495: WRONG button (F12) — bug report capture dialog, ModalLayer
|
||||
; #495: WRONG button (F12) — bug report capture dialog, MetaLayer
|
||||
[node name="BugReportDialog" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://b2ndm9rvx8cqp"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c8pvt3xr7kmd2" path="res://ui/debug_console.gd" id="1_debug_console"]
|
||||
[ext_resource type="Script" path="res://ui/meta/screens/debug_console/debug_console.gd" id="1_debug_console"]
|
||||
|
||||
; #581: In-game debug console. Tilde key toggles. ModalLayer.
|
||||
; #581: In-game debug console. Tilde key toggles. MetaLayer.
|
||||
; UI built programmatically in _ready() — scene contains only root node + script.
|
||||
[node name="DebugConsole" type="Control"]
|
||||
layout_mode = 3
|
||||
|
||||
@@ -54,14 +54,16 @@ func on_open(_mode: int) -> void:
|
||||
_reach_screen.refresh_info_panel_visibility()
|
||||
|
||||
|
||||
func _unhandled_key_input(event: InputEventKey) -> void:
|
||||
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
|
||||
if current_screen_id() == "regional":
|
||||
return # AtlasViewer handles its own keyboard input
|
||||
_handle_key(event)
|
||||
_handle_key(event as InputEventKey)
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://b7rv9mkl4qpw3"]
|
||||
|
||||
[ext_resource type="Script" path="res://ui/loading_screen.gd" id="1_loading"]
|
||||
[ext_resource type="Script" path="res://ui/meta/screens/loading/loading_screen.gd" id="1_loading"]
|
||||
|
||||
; #257: Loading screen — full-screen overlay shown during save/load round-trip.
|
||||
; Blocks input; dismissed when save_result arrives from server.
|
||||
|
||||
+20
-48
@@ -1,4 +1,4 @@
|
||||
extends Control
|
||||
extends MetaScreen
|
||||
|
||||
## #507: WRONG button — full 60-tick capture: ring buffer, snapshot history, replay seed.
|
||||
## Upgrade of the Sprint 9 MVP (#495).
|
||||
@@ -36,7 +36,6 @@ const PADDING := 16
|
||||
const RING_SIZE := 60
|
||||
|
||||
var _line_edit: LineEdit = null
|
||||
var _active: bool = false
|
||||
var _captured_screenshot: Image = null
|
||||
|
||||
# #507: Pre-allocated ring buffers (no per-tick allocation after _ready).
|
||||
@@ -56,8 +55,9 @@ var _snapshot_count: int = 0
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
pauses_sim = true
|
||||
visible = false
|
||||
mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
|
||||
# Pre-allocate ring buffers — resize then fill sentinels.
|
||||
# The ring array itself never grows after _ready. Each write replaces the GDScript
|
||||
@@ -208,25 +208,15 @@ func _get_filled_snapshot_count() -> int:
|
||||
|
||||
|
||||
func start_capture() -> void:
|
||||
if _active:
|
||||
if is_open():
|
||||
return
|
||||
# Capture screenshot BEFORE showing the dialog overlay
|
||||
_captured_screenshot = get_viewport().get_texture().get_image()
|
||||
_active = true
|
||||
visible = true
|
||||
MetaStack.push(self)
|
||||
open()
|
||||
|
||||
# Pause the simulation
|
||||
(
|
||||
SimBridge
|
||||
. send_input(
|
||||
{
|
||||
"action": InputMapper.Action.PAUSE,
|
||||
"timestamp_msec": Time.get_ticks_msec(),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
# Create the LineEdit dynamically
|
||||
func on_open() -> void:
|
||||
_line_edit = LineEdit.new()
|
||||
_line_edit.placeholder_text = "Describe the issue..."
|
||||
_line_edit.size = Vector2(BOX_WIDTH - PADDING * 2, 30)
|
||||
@@ -240,41 +230,23 @@ func start_capture() -> void:
|
||||
_line_edit.grab_focus()
|
||||
|
||||
|
||||
func _on_text_submitted(text: String) -> void:
|
||||
_save_report(text)
|
||||
_close()
|
||||
capture_completed.emit()
|
||||
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if not _active:
|
||||
return
|
||||
if event is InputEventKey and event.pressed and event.keycode == KEY_ESCAPE:
|
||||
_close()
|
||||
capture_cancelled.emit()
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
|
||||
func _close() -> void:
|
||||
_active = false
|
||||
visible = false
|
||||
func on_close() -> void:
|
||||
if _line_edit:
|
||||
_line_edit.release_focus()
|
||||
_line_edit.queue_free()
|
||||
_line_edit = null
|
||||
|
||||
_captured_screenshot = null
|
||||
|
||||
# Unpause the simulation
|
||||
(
|
||||
SimBridge
|
||||
. send_input(
|
||||
{
|
||||
"action": InputMapper.Action.UNPAUSE,
|
||||
"timestamp_msec": Time.get_ticks_msec(),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
func on_escape() -> bool:
|
||||
capture_cancelled.emit()
|
||||
return false # let MetaStack close
|
||||
|
||||
|
||||
func _on_text_submitted(text: String) -> void:
|
||||
_save_report(text)
|
||||
close()
|
||||
capture_completed.emit()
|
||||
|
||||
|
||||
func _save_report(description: String) -> void:
|
||||
@@ -456,7 +428,7 @@ func _render_snapshot_text() -> String:
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
if not _active:
|
||||
if not is_open():
|
||||
return
|
||||
var viewport_size := get_viewport_rect().size
|
||||
# Full-screen dim
|
||||
@@ -489,4 +461,4 @@ func _draw() -> void:
|
||||
|
||||
|
||||
func is_active() -> bool:
|
||||
return _active
|
||||
return is_open()
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# gdlint:disable=max-file-lines
|
||||
class_name CharacterCreation
|
||||
extends Control
|
||||
extends MetaScreen
|
||||
## #705: Character creation screen.
|
||||
## Live 3D preview via SubViewport + 5-tab customisation panel (Body/Head/Hair/Clothing/Accessories).
|
||||
## Emits creation_confirmed(descriptor) on start, creation_cancelled on back.
|
||||
+15
-22
@@ -1,5 +1,5 @@
|
||||
class_name DebugConsole
|
||||
extends Control
|
||||
extends MetaScreen
|
||||
|
||||
## In-game debug console (#581). Tilde key (`) toggles open/closed.
|
||||
## Semi-transparent panel anchored to bottom ~40% of screen.
|
||||
@@ -23,7 +23,6 @@ const ERROR_COLOR := Color("#d45d5d")
|
||||
const INPUT_COLOR := Color("#e8c547")
|
||||
|
||||
var _enabled: bool = true
|
||||
var _open: bool = false
|
||||
var _log_lines: Array[String] = []
|
||||
var _panel: PanelContainer = null
|
||||
var _output_log: RichTextLabel = null
|
||||
@@ -33,6 +32,7 @@ var _history_idx: int = -1
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
pauses_sim = true
|
||||
_load_prefs()
|
||||
visible = false
|
||||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
@@ -107,11 +107,11 @@ func _unhandled_input(event: InputEvent) -> void:
|
||||
get_viewport().set_input_as_handled()
|
||||
_toggle()
|
||||
return
|
||||
if _open:
|
||||
if is_open():
|
||||
# Consume all keyboard events — prevent movement/action leaking through
|
||||
get_viewport().set_input_as_handled()
|
||||
if event.keycode == KEY_ESCAPE:
|
||||
_close()
|
||||
close()
|
||||
|
||||
|
||||
func _on_input_key(event: InputEvent) -> void:
|
||||
@@ -126,34 +126,26 @@ func _on_input_key(event: InputEvent) -> void:
|
||||
|
||||
|
||||
func _toggle() -> void:
|
||||
if _open:
|
||||
_close()
|
||||
if is_open():
|
||||
close()
|
||||
else:
|
||||
_open_console()
|
||||
MetaStack.push(self)
|
||||
open()
|
||||
|
||||
|
||||
func _open_console() -> void:
|
||||
_open = true
|
||||
visible = true
|
||||
mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
func on_open() -> void:
|
||||
_input_line.clear()
|
||||
_input_line.grab_focus()
|
||||
_history_idx = -1
|
||||
pause_requested.emit() # D-088: pause sim while typing debug commands
|
||||
|
||||
|
||||
func _close() -> void:
|
||||
_open = false
|
||||
visible = false
|
||||
func on_close() -> void:
|
||||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_input_line.release_focus()
|
||||
unpause_requested.emit() # D-088: resume sim when console closes
|
||||
|
||||
|
||||
func is_open() -> bool:
|
||||
return _open
|
||||
|
||||
|
||||
# -- Command input --
|
||||
|
||||
|
||||
@@ -422,8 +414,9 @@ func append_response(response: Dictionary) -> void:
|
||||
var text: String = response.get("text", "")
|
||||
var color := SUCCESS_COLOR if success else ERROR_COLOR
|
||||
_append_text(text, color)
|
||||
if not _open and _enabled:
|
||||
_open_console()
|
||||
if not is_open() and _enabled:
|
||||
MetaStack.push(self)
|
||||
open()
|
||||
|
||||
|
||||
# -- Log rendering --
|
||||
@@ -492,8 +485,8 @@ func _history_down() -> void:
|
||||
|
||||
func set_enabled(enabled: bool) -> void:
|
||||
_enabled = enabled
|
||||
if not _enabled and _open:
|
||||
_close()
|
||||
if not _enabled and is_open():
|
||||
close()
|
||||
_save_prefs()
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
extends Control
|
||||
extends MetaScreen
|
||||
## #257: Loading screen overlay — blocks input during save/load round-trip.
|
||||
## Shown when LOAD_GAME fires; hidden when save_result arrives (success or failure).
|
||||
## Full-screen, dark overlay with centered status text.
|
||||
@@ -15,8 +15,9 @@ var _version_label: Label = null
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
closable_by_escape = false
|
||||
visible = false
|
||||
mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
_build_ui()
|
||||
|
||||
@@ -76,9 +77,10 @@ func _read_client_version() -> String:
|
||||
|
||||
|
||||
func show_loading() -> void:
|
||||
visible = true
|
||||
MetaStack.push(self)
|
||||
open()
|
||||
|
||||
|
||||
## Hide the loading overlay. success=false is reserved for future failure-state UI.
|
||||
func hide_loading(_success: bool = true) -> void:
|
||||
visible = false
|
||||
close()
|
||||
@@ -1,4 +1,4 @@
|
||||
extends Control
|
||||
extends MetaScreen
|
||||
## #258: Main menu — New Game / Continue / Load Game / Quit.
|
||||
## New Game: opens character creation screen, then starts game.
|
||||
## Continue: loads most recent save directory.
|
||||
@@ -1,4 +1,4 @@
|
||||
extends Control
|
||||
extends MetaScreen
|
||||
|
||||
## #528: Audio settings dialog — 5-bus volume sliders.
|
||||
## #646: AI-Enhanced Dialogue toggle + hardware detection status (D-138).
|
||||
@@ -6,7 +6,6 @@ extends Control
|
||||
## Volumes persist via AudioManager._save_prefs() on each slider change.
|
||||
## AI Dialogue toggle persists via ConfigFile (client-local) + ChangeSettings IPC (server SQLite).
|
||||
|
||||
signal closed
|
||||
signal debug_console_toggled(enabled: bool) # #581: debug console enabled/disabled
|
||||
signal ai_dialogue_toggled(enabled: bool) # #646: AI-Enhanced Dialogue enabled/disabled
|
||||
|
||||
@@ -37,7 +36,6 @@ const BUS_ROWS: Array = [
|
||||
["UI Sounds", "UISounds"],
|
||||
]
|
||||
|
||||
var _active: bool = false
|
||||
var _container: VBoxContainer = null
|
||||
|
||||
# #646: AI Dialogue hardware status and toggle node ref — used by testable API methods
|
||||
@@ -49,30 +47,17 @@ var _ai_battery_warning_label: Label = null # shown when on battery; toggle sta
|
||||
|
||||
func _ready() -> void:
|
||||
visible = false
|
||||
mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
|
||||
|
||||
func open() -> void:
|
||||
if _active:
|
||||
return
|
||||
_active = true
|
||||
visible = true
|
||||
func on_open() -> void:
|
||||
_build_ui()
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func close() -> void:
|
||||
if not _active:
|
||||
return
|
||||
_active = false
|
||||
visible = false
|
||||
func on_close() -> void:
|
||||
_destroy_ui()
|
||||
queue_redraw()
|
||||
closed.emit()
|
||||
|
||||
|
||||
func is_open() -> bool:
|
||||
return _active
|
||||
|
||||
|
||||
func _build_ui() -> void:
|
||||
@@ -139,7 +124,7 @@ func _build_ui() -> void:
|
||||
|
||||
var debug_check := CheckButton.new()
|
||||
# Query live DebugConsole node if available; fall back to prefs file
|
||||
var console_node := get_node_or_null("/root/Main/ModalLayer/DebugConsole")
|
||||
var console_node := get_node_or_null("/root/Main/MetaLayer/DebugConsole")
|
||||
if console_node and console_node.has_method("is_enabled"):
|
||||
debug_check.button_pressed = console_node.is_enabled()
|
||||
else:
|
||||
@@ -351,7 +336,7 @@ func _save_ai_pref(enabled: bool) -> void:
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
if not _active:
|
||||
if not is_open():
|
||||
return
|
||||
var viewport_size := get_viewport_rect().size
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://ui/settings_dialog.gd" id="1_settings"]
|
||||
[ext_resource type="Script" path="res://ui/meta/screens/settings/settings_dialog.gd" id="1_settings"]
|
||||
|
||||
; #528: Audio settings dialog — 5-bus volume sliders, OPEN_MENU (ESC) to toggle
|
||||
[node name="SettingsDialog" type="Control"]
|
||||
|
||||
Reference in New Issue
Block a user