refactor(shell): author shell scenes as editor-previewable node trees

Move the three shell scenes (SystemDock, NarrationBook, MainWindowShell)
from build-everything-in-_ready() to authored .tscn node trees with the
theme + type-variations set in the editor, so the layout previews and can
be arranged in the Godot editor. Scripts keep only on-load work: @onready
node refs, DM-loop wiring, the dock slide, and binding seed ShellState into
the fixed authored turn-order tokens + consumable slots.

Fixes the dock offset + over-long buttons (now fixed-width DockButtons in a
right-anchored menu). Headless probe: root 1920x1080, world/book split
1180/740, dock at the world's right edge — no collapse. Suite 138/138.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-11 09:38:22 -05:00
parent 578b3e86c9
commit 4698732285
6 changed files with 525 additions and 218 deletions

View File

@@ -4,44 +4,37 @@ extends Control
## buttons in/out. §2: state (owns the open toggle via ShellState). The buttons
## are the routing seam — they emit screen_requested; the screens they open land
## in later milestones, so the signal is currently a no-op for the shell.
##
## Layout is authored in SystemDock.tscn (editor-previewable). This script only
## wires the buttons' signals, drives the slide animation, and toggles state. Each
## dock button carries a `screen_id` StringName in its scene metadata; add/reorder
## buttons in the editor and the wiring follows.
signal screen_requested(id: StringName)
const SCREENS := [
[&"inventory", "Inventory", "I"],
[&"character", "Character", "C"],
[&"quest_log", "Quest Log", "J"],
[&"spellbook", "Spellbook", "K"],
[&"world_map", "World Map", "M"],
[&"party", "Party", "P"],
]
const SLIDE_SECONDS := 0.32
const HIDDEN_SHIFT := 230.0
var _screen_buttons: Dictionary = {}
var _state: ShellState
var _menu: VBoxContainer
var _handle: Button
var _menu_open_pos: Vector2
@onready var _menu: VBoxContainer = $Menu
@onready var _handle: Button = $Handle
func _ready() -> void:
_menu = VBoxContainer.new()
_menu.add_theme_constant_override("separation", 10)
add_child(_menu)
for entry in SCREENS:
var id: StringName = entry[0]
var button := Button.new()
button.text = "%s %s" % [entry[2], entry[1]]
button.theme_type_variation = ThemeKeys.DOCK_BUTTON
button.pressed.connect(func(): screen_requested.emit(id))
_menu.add_child(button)
_screen_buttons[id] = button
_handle = Button.new()
_handle.text = ""
_handle.theme_type_variation = ThemeKeys.TAB_ACTIVE
# The buttons live in the scene; harvest each one's authored screen_id so the
# routing seam follows editor edits (add/remove/reorder needs no code change).
for child in _menu.get_children():
if child is Button and child.has_meta("screen_id"):
var id: StringName = child.get_meta("screen_id")
child.pressed.connect(func(): screen_requested.emit(id))
_screen_buttons[id] = child
_handle.pressed.connect(_on_handle_pressed)
add_child(_handle)
# Capture the authored open position so the slide is relative to wherever the
# menu is placed in the editor — moving it in the editor just works.
_menu_open_pos = _menu.position
func setup(state: ShellState) -> void:
@@ -57,7 +50,7 @@ func _on_handle_pressed() -> void:
func _apply_open(open: bool) -> void:
_handle.text = "" if open else ""
var target := Vector2.ZERO if open else Vector2(HIDDEN_SHIFT, 0.0)
var target := _menu_open_pos if open else _menu_open_pos + Vector2(HIDDEN_SHIFT, 0.0)
var alpha := 1.0 if open else 0.0
if not is_inside_tree():
_menu.position = target