Split the main window into TopBar and Quiz scenes

The quiz (song, playlist, players, music player and the popups opened from
it) moves into Quiz.tscn and the top panel into TopBar.tscn. The main window
keeps only the top bar, the quiz and the popups opened from the top bar, so
other views can be added next to the quiz later. Every node keeps its place
on screen.

Screenshots of every UI state before and after are pixel-identical, and a
layout test checks each node against a baseline taken before the split.
This commit is contained in:
Sansan committed 2026-10-06 10:02:22 +02:00
1 parent 75c59eb223
commit 3c7b4e5c9d
27 files changed
+3432 -980

No files matched your search

+16 -4
View File
@@ -11,6 +11,7 @@ var _server: FakeHttpTransport
var _snapshot: AutoloadSnapshot
var _runner: GdUnitSceneRunner
var _window: Control
var _quiz: Quiz
func before_test() -> void:
@@ -39,6 +40,7 @@ func before_test() -> void:
_before_window_starts()
_runner = scene_runner("res://MainWindow.tscn")
_window = _runner.scene()
_quiz = _window.get_node("Quiz")
# The debug build lowers the winning score on startup; keep it out of reach
Settings.winning_score = 20
@@ -61,8 +63,9 @@ func _next_song_is(info: Dictionary) -> void:
_server.respond_json("/music/info", info)
## Presses a button in the quiz.
func _press(button_path: String) -> void:
(_window.get_node(button_path) as Button).pressed.emit()
(_quiz.get_node(button_path) as Button).pressed.emit()
## Presses Next and waits until the first song is playing.
@@ -73,16 +76,16 @@ func _play_first_song() -> void:
## Adds a player through the name field, the same way the user does.
func _add_player(player_name: String) -> PlayerObject:
var name_field: TextEdit = _window.get_node(
var name_field: TextEdit = _quiz.get_node(
"Players/VBoxContainer/AddPlayerContainer/PlayerNameField")
name_field.text = player_name
_runner.invoke("add_player")
_quiz.add_player()
return Settings.player_array.back() if !Settings.player_array.is_empty() else null
## Returns the row in the player list that shows [param player_object].
func _player_row(player_object: PlayerObject) -> Player:
var rows: Node = _window.get_node(
var rows: Node = _quiz.get_node(
"Players/VBoxContainer/ScrollContainer/PlayersListVBoxContainer")
for row: Player in rows.get_children():
if row.player_object == player_object:
@@ -97,6 +100,15 @@ func _remove_player(player_object: PlayerObject) -> void:
await _runner.simulate_frames(1)
## Clicks at [param position], given in the window's 1920x1080 layout coordinates.
func _click_at(position: Vector2) -> void:
# The runner takes window pixels; the headless test window is much smaller than the
# layout, so convert like a real mouse position would be
_runner.set_mouse_position(_window.get_viewport().get_final_transform() * position)
await _runner.simulate_mouse_button_pressed(MOUSE_BUTTON_LEFT)
await _runner.await_input_processed()
## Presses [param keys] together, e.g. [code][KEY_ALT, KEY_1][/code], then releases them.
func _press_keys(keys: Array[Key]) -> void:
for key: Key in keys: