- Link every modal's enabled controls into a wrapping UiFocus ring and give each screen a default focus target. - Add gamepad A/B bindings for ui_accept/ui_cancel; include Start menu footer actions and Continue in its focus ring. - Skip hidden/disabled controls, close focus traps via per-modal cancel behavior, and return review to the menu on cancel. - Record the focus audit and add regression coverage.
98 lines
3.2 KiB
GDScript
98 lines
3.2 KiB
GDScript
class_name PauseOverlay
|
|
extends CanvasLayer
|
|
## Shift pause overlay: resume, restart the shift, open settings, or quit to the
|
|
## venue menu. Opened with Esc from the world (or a dedicated pause action) and
|
|
## while the overlay is open the guard is frozen.
|
|
|
|
signal resume_requested()
|
|
signal restart_requested()
|
|
signal settings_requested()
|
|
signal quit_to_menu_requested()
|
|
|
|
const UiArt := preload("res://src/game/ui/art.gd")
|
|
const UiStyles := preload("res://src/game/ui/ui_styles.gd")
|
|
const UiFocus := preload("res://src/game/ui/ui_focus.gd")
|
|
const Strings := preload("res://src/core/strings.gd")
|
|
|
|
var _root: Control = null
|
|
var _resume_button: Button = null
|
|
var _buttons: Array[Button] = []
|
|
|
|
func _ready() -> void:
|
|
layer = 18
|
|
_build()
|
|
hide()
|
|
|
|
func _build() -> void:
|
|
_root = Control.new()
|
|
_root.name = "PauseRoot"
|
|
_root.set_anchors_preset(Control.PRESET_FULL_RECT)
|
|
_root.mouse_filter = Control.MOUSE_FILTER_STOP
|
|
add_child(_root)
|
|
|
|
var art := UiArt.new()
|
|
if not art.add_screen_backdrop(_root, "pause_overlay",
|
|
Rect2(0.172, 0.097, 0.59, 0.813)):
|
|
var bg := ColorRect.new()
|
|
bg.color = Color(0.02, 0.03, 0.05, 0.82)
|
|
bg.set_anchors_preset(Control.PRESET_FULL_RECT)
|
|
bg.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
_root.add_child(bg)
|
|
|
|
var center := CenterContainer.new()
|
|
center.set_anchors_preset(Control.PRESET_FULL_RECT)
|
|
_root.add_child(center)
|
|
|
|
var column := VBoxContainer.new()
|
|
column.custom_minimum_size = Vector2(320, 0)
|
|
column.add_theme_constant_override("separation", 10)
|
|
center.add_child(column)
|
|
|
|
var title := Label.new()
|
|
title.text = Strings.lookup("ui.pause_title")
|
|
title.add_theme_font_size_override("font_size", 36)
|
|
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
column.add_child(title)
|
|
|
|
_resume_button = _add_button(column, Strings.lookup("ui.pause_resume"), "ResumeButton", func(): resume_requested.emit())
|
|
_buttons.append(_add_button(column, Strings.lookup("ui.pause_restart"), "RestartButton", func(): restart_requested.emit()))
|
|
_buttons.append(_add_button(column, Strings.lookup("ui.pause_settings"), "SettingsButton", func(): settings_requested.emit()))
|
|
_buttons.append(_add_button(column, Strings.lookup("ui.pause_menu"), "MenuButton", func(): quit_to_menu_requested.emit()))
|
|
_buttons.push_front(_resume_button)
|
|
UiFocus.link(_buttons)
|
|
|
|
var hint := Label.new()
|
|
hint.text = Strings.lookup("ui.pause_hint")
|
|
hint.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
hint.add_theme_color_override("font_color", Color(0.55, 0.58, 0.65))
|
|
column.add_child(hint)
|
|
|
|
func _add_button(parent: Control, text: String, node_name: String, cb: Callable) -> Button:
|
|
var button := Button.new()
|
|
button.name = node_name
|
|
button.text = text
|
|
button.custom_minimum_size = Vector2(380, 52)
|
|
UiStyles.apply_button(button, 22)
|
|
button.pressed.connect(cb)
|
|
parent.add_child(button)
|
|
return button
|
|
|
|
func open() -> void:
|
|
visible = true
|
|
_root.visible = true
|
|
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
|
|
UiFocus.link(_buttons)
|
|
if _resume_button:
|
|
_resume_button.grab_focus()
|
|
|
|
func close() -> void:
|
|
visible = false
|
|
_root.visible = false
|
|
|
|
func _unhandled_input(event: InputEvent) -> void:
|
|
if not visible:
|
|
return
|
|
if event.is_action_pressed("ui_cancel"):
|
|
resume_requested.emit()
|
|
get_viewport().set_input_as_handled()
|