feat(title): TitleScreen composition, menu + selection, boot scene
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
95
client/scripts/ui/title/title_screen.gd
Normal file
95
client/scripts/ui/title/title_screen.gd
Normal file
@@ -0,0 +1,95 @@
|
||||
class_name TitleScreen
|
||||
extends Control
|
||||
## The entry-point screen (mock Title Screen). Editor-first (ADR 0001): the whole
|
||||
## composition lives in TitleScreen.tscn; this script drives selection, input, and
|
||||
## dispatches the menu. §2: presentation/state — no AI here (the DM enters at the
|
||||
## shell). The atmosphere (bg shader, glow, embers) is layered on in a later task.
|
||||
|
||||
signal menu_activated(id: StringName)
|
||||
|
||||
const SHELL := "res://scenes/shell/MainWindowShell.tscn"
|
||||
|
||||
var _rows: Array = []
|
||||
var _sel: int = 0
|
||||
|
||||
@onready var _menu: VBoxContainer = $Content/Menu
|
||||
@onready var _footer_left: Label = $FooterLeft
|
||||
@onready var _background: ColorRect = $Background
|
||||
@onready var _rule: ColorRect = $Content/Rule
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
# Root Control run via F6 is not sized by full-rect anchors — size explicitly.
|
||||
_fit_to_viewport()
|
||||
get_viewport().size_changed.connect(_fit_to_viewport)
|
||||
|
||||
_background.color = Palette.STAGE_1
|
||||
_rule.color = Palette.GOLD
|
||||
|
||||
_rows = []
|
||||
for row in _menu.get_children():
|
||||
if row.has_meta("action"):
|
||||
_rows.append(row)
|
||||
(row.get_node("Marker") as Label).add_theme_color_override("font_color", Palette.GOLD)
|
||||
_wire_row_mouse()
|
||||
|
||||
_footer_left.text = Version.new().footer()
|
||||
menu_activated.connect(_on_menu_activated)
|
||||
_update_selection()
|
||||
|
||||
|
||||
func _fit_to_viewport() -> void:
|
||||
size = get_viewport_rect().size
|
||||
|
||||
|
||||
func _wire_row_mouse() -> void:
|
||||
for i in range(_rows.size()):
|
||||
var idx := i # fresh per-iteration binding for the closures
|
||||
var row: Control = _rows[idx]
|
||||
row.mouse_entered.connect(func():
|
||||
_sel = idx
|
||||
_update_selection())
|
||||
row.gui_input.connect(func(e: InputEvent):
|
||||
if e is InputEventMouseButton and e.pressed and e.button_index == MOUSE_BUTTON_LEFT:
|
||||
_activate(idx))
|
||||
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event.is_action_pressed("ui_down"):
|
||||
_move(1)
|
||||
elif event.is_action_pressed("ui_up"):
|
||||
_move(-1)
|
||||
elif event.is_action_pressed("ui_accept"):
|
||||
_activate(_sel)
|
||||
|
||||
|
||||
func _move(delta: int) -> void:
|
||||
if _rows.is_empty():
|
||||
return
|
||||
_sel = wrapi(_sel + delta, 0, _rows.size())
|
||||
_update_selection()
|
||||
|
||||
|
||||
func _activate(i: int) -> void:
|
||||
if i < 0 or i >= _rows.size():
|
||||
return
|
||||
menu_activated.emit(_rows[i].get_meta("action"))
|
||||
|
||||
|
||||
func _on_menu_activated(id: StringName) -> void:
|
||||
match id:
|
||||
&"new_game":
|
||||
get_tree().change_scene_to_file(SHELL)
|
||||
&"quit":
|
||||
get_tree().quit()
|
||||
_:
|
||||
pass # inert placeholder — the screen lands in a later milestone
|
||||
|
||||
|
||||
func _update_selection() -> void:
|
||||
for i in range(_rows.size()):
|
||||
var row: Control = _rows[i]
|
||||
var selected := i == _sel
|
||||
(row.get_node("Marker") as Label).modulate.a = 1.0 if selected else 0.0
|
||||
var label := row.get_node("Body/Label") as Label
|
||||
label.add_theme_color_override("font_color", Palette.CREAM_BRIGHT if selected else Palette.MUTED_MONO)
|
||||
1
client/scripts/ui/title/title_screen.gd.uid
Normal file
1
client/scripts/ui/title/title_screen.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://clmbt3dy7bdwb
|
||||
Reference in New Issue
Block a user