refactor(ui): PR #134 review — MetaScreen/ESC chain tightening, D-192 reword
Addresses Tyre's 9 architecture items from the sprint-36 client review. - decisions/architecture.md (Tyre #1): D-192 now says "deprecate; removal tracked in #868" instead of "remove". The branch does not remove the version field or guard — that belongs in the coordinated server+client PR. The decision text now matches the code on this branch. - meta_stack.gd (#2): handle_escape() on a screen with closable_by_escape=false now consumes the event unconditionally. Was returning whatever on_escape() returned, which default-returned false and leaked ESC into main.gd's implant/settings chain — opening the settings dialog behind the loading screen. - debug_console.gd (#4): drop the direct KEY_ESCAPE branch in _unhandled_input. ESC now falls through to main.gd → MetaStack, which finds the console on top of the stack and closes it via the normal path. Other keys are still consumed so movement/action can't leak. - main.gd (#6, #10): extract the ESC priority chain into _handle_menu_key() so "MetaStack → implant → settings" is a named thing. Add a comment near connect_to_sim explaining that GameState.bookmark_catalog survives the Option A scene transition via the autoload. - main_menu.gd (#7): header comment documenting the double LoadingScreen lifecycle — safe today because main_menu.tscn and main.tscn never co-exist, noted for future promotion to autoload if that changes. - meta_screen.gd (#8): apply captures_input symmetrically in open()/ close() — was set in open() only, so a screen changing the flag between open+close kept the opened value forever. - meta_screen.gd (#9): on_escape() docstring clarifies the tri-state (consume-and-hold / consume-and-close / ignore) — and that closable_by_escape=false is the screen-wide way to say "consume-and-hold". - bug_report_dialog.gd (#11): capture_cancelled now emits from on_close() (covers any close path — ESC, MetaStack pop, programmatic close) rather than only on_escape(). A new _completed flag distinguishes completion from cancel so the two signals stay mutually exclusive.
This commit is contained in:
@@ -22,8 +22,7 @@ func open() -> void:
|
||||
return
|
||||
_phase = Phase.OPENING
|
||||
visible = true
|
||||
if captures_input:
|
||||
mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
mouse_filter = Control.MOUSE_FILTER_STOP if captures_input else Control.MOUSE_FILTER_IGNORE
|
||||
on_open()
|
||||
_phase = Phase.OPEN
|
||||
|
||||
@@ -34,6 +33,7 @@ func close() -> void:
|
||||
_phase = Phase.CLOSING
|
||||
on_close()
|
||||
visible = false
|
||||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_phase = Phase.HIDDEN
|
||||
closed.emit()
|
||||
|
||||
@@ -43,7 +43,16 @@ func is_open() -> bool:
|
||||
|
||||
|
||||
## Called by MetaStack when ESC is pressed with this screen on top.
|
||||
## Return true to consume the event (prevent stack pop); false to allow pop.
|
||||
##
|
||||
## Return true: consumed-and-held — keep this screen open (e.g. "are you sure"
|
||||
## prompt was shown, do not close the underlying screen).
|
||||
## Return false: did nothing internally — let MetaStack close this screen.
|
||||
##
|
||||
## To express "consume-and-hold" — the screen must remain open and ESC must NOT
|
||||
## fall through to gameplay/implant — set `closable_by_escape = false` instead.
|
||||
## MetaStack treats that as: call on_escape (to let the screen react), do not
|
||||
## pop, return true so the event stops here. Returning true from `on_escape` is
|
||||
## the per-event variant; setting the flag is the screen-wide variant.
|
||||
func on_escape() -> bool:
|
||||
escape_pressed.emit()
|
||||
return false
|
||||
|
||||
@@ -38,12 +38,18 @@ func is_active() -> bool:
|
||||
|
||||
## Handle ESC key. Call from main.gd before HudGroups ESC handling.
|
||||
## Returns true if the event was consumed (callers must return after).
|
||||
##
|
||||
## A screen on the stack always consumes the event. `closable_by_escape = false`
|
||||
## means "I refuse to close on ESC" — not "pass the event through to the
|
||||
## implant/gameplay layer." Otherwise an un-escapable screen (e.g. LoadingScreen)
|
||||
## would leak ESC to main.gd and open the settings dialog behind it.
|
||||
func handle_escape() -> bool:
|
||||
var t = top()
|
||||
if t == null:
|
||||
return false
|
||||
if not t.closable_by_escape:
|
||||
return t.on_escape()
|
||||
t.on_escape()
|
||||
return true
|
||||
if t.on_escape():
|
||||
return true
|
||||
t.close()
|
||||
|
||||
@@ -37,6 +37,7 @@ const RING_SIZE := 60
|
||||
|
||||
var _line_edit: LineEdit = null
|
||||
var _captured_screenshot: Image = null
|
||||
var _completed: bool = false # set by _on_text_submitted; suppresses on_close cancel
|
||||
|
||||
# #507: Pre-allocated ring buffers (no per-tick allocation after _ready).
|
||||
# Input ring: replay-format PlayerInput arrays, one per tick.
|
||||
@@ -210,6 +211,7 @@ func _get_filled_snapshot_count() -> int:
|
||||
func start_capture() -> void:
|
||||
if is_open():
|
||||
return
|
||||
_completed = false # reset completion flag for this capture session
|
||||
# Capture screenshot BEFORE showing the dialog overlay
|
||||
_captured_screenshot = get_viewport().get_texture().get_image()
|
||||
MetaStack.push(self)
|
||||
@@ -236,17 +238,24 @@ func on_close() -> void:
|
||||
_line_edit.queue_free()
|
||||
_line_edit = null
|
||||
_captured_screenshot = null
|
||||
# Any close path that did not complete is a cancel — covers ESC, MetaStack
|
||||
# pop, programmatic close(). _completed flips to true in _on_text_submitted
|
||||
# right before capture_completed fires, so the two signals stay exclusive.
|
||||
if not _completed:
|
||||
capture_cancelled.emit()
|
||||
_completed = false
|
||||
|
||||
|
||||
func on_escape() -> bool:
|
||||
capture_cancelled.emit()
|
||||
# on_close() will emit capture_cancelled — don't double-emit here.
|
||||
return false # let MetaStack close
|
||||
|
||||
|
||||
func _on_text_submitted(text: String) -> void:
|
||||
_save_report(text)
|
||||
close()
|
||||
_completed = true
|
||||
capture_completed.emit()
|
||||
close()
|
||||
|
||||
|
||||
func _save_report(description: String) -> void:
|
||||
|
||||
@@ -108,10 +108,11 @@ func _unhandled_input(event: InputEvent) -> void:
|
||||
_toggle()
|
||||
return
|
||||
if is_open():
|
||||
# Consume all keyboard events — prevent movement/action leaking through
|
||||
get_viewport().set_input_as_handled()
|
||||
if event.keycode == KEY_ESCAPE:
|
||||
close()
|
||||
# Consume keyboard events so movement/action don't leak to main.gd,
|
||||
# but let ESC fall through to OPEN_MENU → MetaStack.handle_escape().
|
||||
# MetaStack finds this console at the top of the stack and closes it.
|
||||
if event.keycode != KEY_ESCAPE:
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
|
||||
func _on_input_key(event: InputEvent) -> void:
|
||||
|
||||
@@ -3,6 +3,13 @@ extends MetaScreen
|
||||
## New Game: opens character creation screen, then starts game.
|
||||
## Continue: loads most recent save directory.
|
||||
## Load Game: shows sorted save list for manual selection (#257).
|
||||
##
|
||||
## LoadingScreen lifecycle: this scene instantiates its own LoadingScreen child
|
||||
## (see `_ensure_loading_screen`). main.tscn has a separate `$MetaLayer/LoadingScreen`.
|
||||
## Safe today because main_menu.tscn and main.tscn never co-exist — the scene
|
||||
## transition in `_start_game` replaces the tree wholesale. If that invariant
|
||||
## ever changes (e.g. embedding the menu as an overlay), promote LoadingScreen
|
||||
## to an autoload to enforce single-instance across the MetaStack.
|
||||
|
||||
const GAME_SCENE := "res://scenes/main.tscn"
|
||||
const CHARACTER_CREATION_SCENE := "res://scenes/character_creation.tscn"
|
||||
|
||||
Reference in New Issue
Block a user