Split settings into a quiz card and an application Settings popup
Build / test (push) Successful in 44s
Build / build (push) Successful in 1m13s

- A Quiz settings button under the player bar opens a small card above
  it with the quiz settings, including Auto repeat, which moves there
  from under the player bar. The card does not dim the quiz, so it can
  be changed during a song, and grows to fit its rows.
- A Settings button in the top bar opens the application settings:
  server, fullscreen and songs to download ahead. Quick sync is gone,
  as the server no longer has it, and the server list is in Settings.
- Labels use sentence case, and seconds to hide show as whole numbers.
- Debug builds switch to the local server and a low winning score in
  the Settings autoload, before any scene asks the server for anything.
  The DEBUG label moves to the top bar, so it shows on every tab.
- Search moves from the top bar back to the quiz toolbar, as the
  Statistics tab has its own search.
This commit is contained in:
Sansan committed 2026-10-07 16:05:13 +02:00
1 parent 8d21b0617d
commit b510aa47f2
34 files changed
+3074 -2525

No files matched your search

File diff suppressed because it is too large. Load diff
-2
View File
@@ -41,8 +41,6 @@ func before_test() -> void:
_runner = scene_runner("res://main/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
## Override to change the fake server before the window starts and sends its first
+1 -2
View File
@@ -57,6 +57,7 @@ const POPUPS: Array[Array] = [
["SettingsWindow", "SettingsPopup"], ["QRWindow", "QRPopup"],
["NewCharacterSelect", "CharacterSelectPopup"], ["ShortcutsWindow", "ShortcutsPopup"],
["AboutWindow", "AboutPopup"], ["WinnerWindow", "WinnerPopup"], ["Log", "LogPopup"],
["QuizSettings", "QuizSettingsCard"],
]
var _main: Control
@@ -175,8 +176,6 @@ static func prepare_autoloads() -> void:
## Runs the tour. [param on_state] is called (and awaited) with the name of each state
## and the popup that is open in it, or [code]null[/code] when no popup is open.
func run(on_state: Callable) -> void:
# The debug build lowers the winning score on startup; keep it out of reach
Settings.winning_score = 20
_stop_caret_blink(_main)
await _settle()
await on_state.call("startup", null)
+3 -3
View File
@@ -63,9 +63,9 @@ func test_qr_popup_has_a_title_and_the_search_address() -> void:
func test_qr_code_follows_the_server_picked_in_the_settings() -> void:
_qr.open()
_qr.close()
Settings.default_path = "http://localhost:8080"
Settings.default_path = "http://another-server:8080"
_qr.open()
assert_str(_qr.url_label.text).is_equal("http://localhost:8080/search")
assert_str(_qr.qr_code_rect.data).is_equal("http://localhost:8080/search")
assert_str(_qr.url_label.text).is_equal("http://another-server:8080/search")
assert_str(_qr.qr_code_rect.data).is_equal("http://another-server:8080/search")
+3 -3
View File
@@ -161,15 +161,15 @@ func test_cache_keeps_filling_after_a_failed_download() -> void:
func _open_settings() -> void:
_press("QuizToolbar/SettingsButton")
(_window.get_node("TopBar/SettingsButton") as Button).pressed.emit()
func _close_settings() -> void:
(_quiz.get_node("SettingsPopup") as Control).visible = false
(_window.get_node("SettingsPopup") as Control).visible = false
func _press_settings_button(unique_name: String) -> void:
(_quiz.get_node("SettingsPopup").get_node("%" + unique_name) as Button).pressed.emit()
(_window.get_node("SettingsPopup").get_node("%" + unique_name) as Button).pressed.emit()
func test_raising_cache_size_does_not_download_while_settings_are_open() -> void:
+1 -23
View File
@@ -1,5 +1,5 @@
extends GdUnitTestSuite
## The music player scene: the player bar and the auto repeat switch under it.
## The music player scene: the player bar. Auto repeat is in the quiz settings card.
var _snapshot: AutoloadSnapshot
var _runner: GdUnitSceneRunner
@@ -24,27 +24,5 @@ func _click(control: Control) -> void:
await _runner.await_input_processed()
func test_auto_repeat_starts_on() -> void:
assert_bool(_player.auto_repeat_button.button_pressed).is_true()
func test_clicking_auto_repeat_turns_it_off_and_on() -> void:
await _click(_player.auto_repeat_button)
assert_bool(Settings.auto_repeat_song).is_false()
assert_bool(_player.auto_repeat_button.button_pressed).is_false()
await _click(_player.auto_repeat_button)
assert_bool(Settings.auto_repeat_song).is_true()
func test_auto_repeat_is_under_the_player_bar() -> void:
await _runner.simulate_frames(1)
var bar: Rect2 = (_player.get_node("PlayerBar") as Control).get_global_rect()
var repeat: Rect2 = _player.auto_repeat_button.get_global_rect()
assert_float(repeat.position.y).is_greater_equal(bar.end.y)
assert_bool(_player.get_global_rect().encloses(repeat)).is_true()
func test_empty_space_lets_clicks_through() -> void:
assert_int(_player.mouse_filter).is_equal(Control.MOUSE_FILTER_IGNORE)
+5 -4
View File
@@ -6,11 +6,12 @@ extends MainWindowTestSuite
## Popups in the main window: the ones opened from the top bar, and the log.
const MAIN_POPUPS: Array[String] = [
"SearchPopup", "SyncPopup", "QRPopup", "ShortcutsPopup", "AboutPopup", "LogPopup",
"SettingsPopup",
]
## Popups opened from the quiz, in the Quiz scene.
const QUIZ_POPUPS: Array[String] = [
"SettingsPopup", "CharacterSelectPopup", "WinnerPopup",
"CharacterSelectPopup", "WinnerPopup",
]
@@ -20,7 +21,7 @@ func _popup(node_name: String) -> BasePopup:
## The backdrop that shows behind [param popup].
func _backdrop(popup: BasePopup = _popup("SettingsPopup")) -> ColorRect:
func _backdrop(popup: BasePopup = _popup("CharacterSelectPopup")) -> ColorRect:
return popup.get_parent().get_node("PopupBackdrop")
@@ -32,12 +33,12 @@ func _outside(popup: BasePopup) -> Vector2:
func test_backdrops_are_hidden_without_popups() -> void:
assert_bool(_backdrop(_popup("SettingsPopup")).visible).is_false()
assert_bool(_backdrop(_popup("CharacterSelectPopup")).visible).is_false()
assert_bool(_backdrop(_popup("AboutPopup")).visible).is_false()
func test_backdrops_cover_the_whole_window() -> void:
for node_name: String in ["SettingsPopup", "AboutPopup"]:
for node_name: String in ["CharacterSelectPopup", "AboutPopup"]:
var popup: BasePopup = _popup(node_name)
popup.open()
@@ -0,0 +1,160 @@
extends MainWindowTestSuite
## The quiz settings card: opened from the button under the player bar, it changes the
## quiz settings without dimming or stopping the quiz.
var _card: QuizSettingsCard
var _button: Button
func before_test() -> void:
super()
_card = _quiz.get_node("QuizSettingsCard")
_button = _quiz.get_node("QuizSettingsButton")
func _open() -> void:
await _click_at(_button.get_global_rect().get_center())
await _runner.simulate_frames(1)
func test_button_opens_the_card_above_it_without_dimming() -> void:
await _open()
assert_bool(_card.visible).is_true()
var card: Rect2 = _card.card_panel.get_global_rect()
var button: Rect2 = _button.get_global_rect()
assert_float(card.end.y).is_less_equal(button.position.y)
var bar: Rect2 = (_quiz.get_node("MusicPlayer/PlayerBar") as Control).get_global_rect()
assert_float(card.end.y).override_failure_message("The card covers the player bar") \
.is_less_equal(bar.position.y)
assert_float(card.position.x).is_equal(button.position.x)
assert_bool(Rect2(Vector2.ZERO, Vector2(1920, 1080)).encloses(card)).is_true()
assert_bool((_quiz.get_node("PopupBackdrop") as Control).visible).is_false()
assert_bool((_window.get_node("PopupBackdrop") as Control).visible).is_false()
func test_song_keeps_playing_while_the_card_is_open() -> void:
await _play_first_song()
await _open()
assert_int(Playlist.currently_playing_song).is_equal(0)
assert_bool((_quiz.get_node("MusicPlayer") as MusicPlayer).audio_player.stream_paused) \
.is_false()
func test_clicking_outside_closes_it_without_pressing_what_is_behind() -> void:
await _open()
var reset_button: Button = _quiz.get_node("QuizToolbar/ResetPlaylistButton")
await _click_at(reset_button.get_global_rect().get_center())
assert_bool(_card.visible).is_false()
assert_int(_server.count("/music/reset")).is_equal(0)
func test_button_closes_an_open_card() -> void:
await _open()
await _click_at(_button.get_global_rect().get_center())
assert_bool(_card.visible).is_false()
func test_each_toggle_changes_its_setting() -> void:
var toggles: Dictionary = {
"auto_repeat_button": "auto_repeat_song",
"stop_after_current_button": "stop_after_current",
"hide_next_track_button": "hide_next_track",
"hide_beginning_button": "hide_beginning",
"hide_length_button": "hide_length",
"hide_ticks_button": "hide_ticks",
"low_played_button": "use_low_played_mode",
"add_to_database_button": "add_to_stats",
}
await _open()
for button_name: String in toggles:
var toggle: CheckButton = _card.get(button_name)
var before: bool = Settings.get(toggles[button_name])
assert_bool(toggle.button_pressed).override_failure_message(button_name) \
.is_equal(before)
await _click_at(toggle.get_global_rect().get_center())
assert_bool(Settings.get(toggles[button_name])).override_failure_message(
button_name + " did not change its setting").is_equal(!before)
assert_bool(toggle.button_pressed).is_equal(!before)
func test_card_shows_settings_changed_elsewhere() -> void:
await _open()
_card.close()
Settings.add_to_stats = false
Settings.auto_repeat_song = false
await _open()
assert_bool(_card.add_to_database_button.button_pressed).is_false()
assert_bool(_card.auto_repeat_button.button_pressed).is_false()
func test_seconds_to_hide_only_show_while_hiding_the_beginning() -> void:
Settings.hide_beginning = false
await _open()
assert_bool(_card.hide_for_seconds_row.visible).is_false()
_card.hide_beginning_button.button_pressed = true
assert_bool(_card.hide_for_seconds_row.visible).is_true()
assert_str(_card.hide_for_seconds_label.text).is_equal("%d" % Settings.hide_for_seconds)
func test_seconds_to_hide_are_whole_numbers_between_0_and_10() -> void:
Settings.hide_for_seconds = 9.0
await _open()
_card.increase_hide_for_seconds_button.pressed.emit()
_card.increase_hide_for_seconds_button.pressed.emit()
assert_float(Settings.hide_for_seconds).is_equal(10.0)
assert_str(_card.hide_for_seconds_label.text).is_equal("10")
for i: int in 12:
_card.lower_hide_for_seconds_button.pressed.emit()
assert_float(Settings.hide_for_seconds).is_equal(0.0)
assert_str(_card.hide_for_seconds_label.text).is_equal("0")
func test_winning_score_goes_up_and_down_but_not_below_one() -> void:
Settings.winning_score = 2
await _open()
_card.increase_winning_score_button.pressed.emit()
assert_int(Settings.winning_score).is_equal(3)
for i: int in 5:
_card.lower_winning_score_button.pressed.emit()
assert_int(Settings.winning_score).is_equal(1)
assert_str(_card.score_label.text).is_equal("1")
func test_auto_repeat_in_the_card_is_what_the_player_uses() -> void:
Settings.auto_repeat_song = true
await _open()
await _click_at(_card.auto_repeat_button.get_global_rect().get_center())
assert_bool(Settings.auto_repeat_song).is_false()
assert_object(_quiz.get_node("MusicPlayer").get_node_or_null("RepeatSongCheckButton")) \
.is_null()
func test_card_grows_upward_for_the_seconds_row() -> void:
Settings.hide_beginning = false
await _open()
var closed_row: Rect2 = _card.card_panel.get_global_rect()
_card.hide_beginning_button.button_pressed = true
var open_row: Rect2 = _card.card_panel.get_global_rect()
assert_float(open_row.end.y).is_equal(closed_row.end.y)
assert_float(open_row.size.y).is_greater(closed_row.size.y)
# Every row stays inside the card
assert_bool(open_row.encloses(_card.rows.get_global_rect())).is_true()
@@ -0,0 +1 @@
uid://idy30j6v07pe
+4 -4
View File
@@ -3,9 +3,9 @@ extends GdUnitTestSuite
## buttons that use the song list.
const BUTTON_SIGNALS: Dictionary = {
"SearchButton": "search_pressed",
"ResetPlaylistButton": "reset_playlist_pressed",
"ResetPointsButton": "reset_points_pressed",
"SettingsButton": "settings_pressed",
"SoundTestButton": "sound_test_pressed",
}
@@ -40,8 +40,9 @@ func test_clicking_each_button_reports_it() -> void:
func test_buttons_are_in_order_left_to_right() -> void:
var lefts: Array = ["ResetPlaylistButton", "ResetPointsButton", "SettingsButton",
"SoundTestButton"].map(func(n: String) -> float: return _button(n).position.x)
var lefts: Array = ["SearchButton", "ResetPlaylistButton", "ResetPointsButton",
"SoundTestButton"] \
.map(func(n: String) -> float: return _button(n).position.x)
for i: int in lefts.size() - 1:
assert_float(lefts[i]).is_less(lefts[i + 1])
@@ -53,7 +54,6 @@ func test_locking_disables_only_buttons_that_use_the_song_list() -> void:
assert_bool(_button("ResetPlaylistButton").disabled).is_true()
assert_bool(_button("SoundTestButton").disabled).is_true()
assert_bool(_button("ResetPointsButton").disabled).is_false()
assert_bool(_button("SettingsButton").disabled).is_false()
func test_unlocking_enables_them_again() -> void:
+44 -15
View File
@@ -7,6 +7,7 @@ extends MainWindowTestSuite
const MAIN_WINDOW_CHILDREN: Array[String] = [
"Background", "TopBar", "Quiz", "PopupBackdrop", "SearchPopup", "SyncPopup", "QRPopup",
"ShortcutsPopup", "StatisticsView", "AboutPopup", "LogPopup", "SoundtrackStatsPopup",
"SettingsPopup",
]
## The parts of the quiz and the scene each one comes from.
@@ -27,14 +28,15 @@ const LOCKED_BY_SYNC: Array[String] = [
]
const TOP_BAR_CHILDREN: Array[String] = [
"TopPanelContainer", "Title", "SyncButton", "SearchButton", "ShortcutsButton",
"TopPanelContainer", "Title", "SyncButton", "SettingsButton", "ShortcutsButton",
"DebugLabel",
"AboutButton", "QRButton", "QuizTab", "StatisticsTab",
]
## Each top bar button and the popup it opens in the main window.
const TOP_BAR_POPUPS: Dictionary = {
"SearchButton": "SearchPopup",
"SyncButton": "SyncPopup",
"SettingsButton": "SettingsPopup",
"ShortcutsButton": "ShortcutsPopup",
"AboutButton": "AboutPopup",
"QRButton": "QRPopup",
@@ -76,9 +78,13 @@ func test_quiz_downloads_songs_with_a_song_loader() -> void:
assert_object(_quiz.song_loader).is_instanceof(SongLoader)
func test_auto_repeat_is_part_of_the_music_player() -> void:
assert_object(_quiz.get_node("MusicPlayer/RepeatSongCheckButton")).is_instanceof(CheckButton)
assert_object(_quiz.get_node_or_null("RepeatSongCheckButton")).is_null()
func test_quiz_settings_button_sits_under_the_player_bar() -> void:
var player: Control = _quiz.get_node("MusicPlayer")
var bar: Rect2 = (player.get_node("PlayerBar") as Control).get_global_rect()
var button: Rect2 = (_quiz.get_node("QuizSettingsButton") as Control).get_global_rect()
assert_float(button.position.y).is_greater_equal(bar.end.y)
assert_float(button.position.x).is_equal(player.get_global_rect().position.x)
func test_top_bar_holds_only_title_and_buttons() -> void:
@@ -190,8 +196,8 @@ func test_loaded_song_list_shows_in_the_music_list() -> void:
func test_quiz_popup_backdrop_covers_the_top_bar() -> void:
var settings: BasePopup = _quiz.get_node("SettingsPopup")
settings.open()
var character_select: CharacterSelectPopup = _quiz.get_node("CharacterSelectPopup")
character_select.show_grid("Crono")
await _runner.simulate_frames(1)
var backdrop: Control = _quiz.get_node("PopupBackdrop")
@@ -202,15 +208,15 @@ func test_quiz_popup_backdrop_covers_the_top_bar() -> void:
func test_click_on_top_bar_while_quiz_popup_is_open_only_closes_it() -> void:
var settings: BasePopup = _quiz.get_node("SettingsPopup")
var search: BasePopup = _window.get_node("SearchPopup")
settings.open()
var character_select: CharacterSelectPopup = _quiz.get_node("CharacterSelectPopup")
var about: BasePopup = _window.get_node("AboutPopup")
character_select.show_grid("Crono")
await _runner.simulate_frames(1)
await _click_at(_center(_top_bar_button("SearchButton")))
await _click_at(_center(_top_bar_button("AboutButton")))
assert_bool(settings.visible).is_false()
assert_bool(search.visible).is_false()
assert_bool(character_select.visible).is_false()
assert_bool(about.visible).is_false()
func test_sync_disables_quiz_buttons_until_finished() -> void:
@@ -245,7 +251,7 @@ func test_changing_server_reloads_search_and_quiz() -> void:
var player: PlayerObject = _add_player("Crono")
player.add_point()
_quiz.get_node("SettingsPopup").server_changed.emit()
_window.get_node("SettingsPopup").server_changed.emit()
assert_int(_server.count("/music/all/order")).is_equal(search_loads + 1)
assert_int(_server.count("/music/all")).is_equal(inspiration_loads + 1)
@@ -274,7 +280,7 @@ func test_alt_down_lowers_the_volume() -> void:
func test_alt_enter_switches_fullscreen_and_settings_shows_it() -> void:
var settings: BasePopup = _quiz.get_node("SettingsPopup")
var settings: BasePopup = _window.get_node("SettingsPopup")
var was_fullscreen: bool = Settings.fullscreen
await _press_keys([KEY_ALT, KEY_ENTER])
@@ -284,3 +290,26 @@ func test_alt_enter_switches_fullscreen_and_settings_shows_it() -> void:
assert_bool(settings.fullscreen_button.button_pressed).is_equal(!was_fullscreen)
await _press_keys([KEY_ALT, KEY_ENTER])
assert_bool(Settings.fullscreen).is_equal(was_fullscreen)
func test_debug_label_is_in_the_top_bar_and_stays_on_the_statistics_tab() -> void:
var label: Label = _window.get_node("TopBar/DebugLabel")
assert_bool(label.visible).is_equal(Settings.is_debug)
_top_bar_button("StatisticsTab").pressed.emit()
assert_bool(label.is_visible_in_tree()).is_equal(Settings.is_debug)
func test_search_is_in_the_quiz_toolbar_and_opens_the_search_popup() -> void:
assert_object(_window.get_node_or_null("TopBar/SearchButton")).is_null()
await _click_at(_center(_quiz.get_node("QuizToolbar/SearchButton")))
assert_bool((_window.get_node("SearchPopup") as Control).visible).is_true()
func test_alt_s_opens_search() -> void:
await _press_keys([KEY_ALT, KEY_S])
assert_bool((_window.get_node("SearchPopup") as Control).visible).is_true()
+1 -1
View File
@@ -20,7 +20,7 @@ func before_test() -> void:
func _open() -> void:
await _click_at(_window.get_node("TopBar/SearchButton").get_global_rect().get_center())
await _click_at(_quiz.get_node("QuizToolbar/SearchButton").get_global_rect().get_center())
await _runner.simulate_frames(1)
+4 -4
View File
@@ -12,7 +12,7 @@ func before_test() -> void:
func _stats_checkbox() -> CheckButton:
return _quiz.get_node("SettingsPopup").get_node("%AddToDatabaseCheckButton")
return _quiz.get_node("QuizSettingsCard").get_node("%AddToDatabaseCheckButton")
func test_winning_turns_statistics_off() -> void:
@@ -41,7 +41,7 @@ func test_statistics_turned_off_by_hand_stay_off_after_reset() -> void:
func test_settings_checkbox_shows_statistics_off_after_win() -> void:
await _give_point_with_keybind(1)
_press("QuizToolbar/SettingsButton")
_press("QuizSettingsButton")
assert_bool(_stats_checkbox().button_pressed).is_false()
@@ -56,8 +56,8 @@ func test_songs_after_a_win_are_not_counted() -> void:
func test_winning_score_cannot_go_below_one() -> void:
_press("QuizToolbar/SettingsButton")
var lower_button: Button = _quiz.get_node("SettingsPopup").get_node("%LowerButton")
_press("QuizSettingsButton")
var lower_button: Button = _quiz.get_node("QuizSettingsCard").get_node("%LowerButton")
for i: int in 3:
lower_button.pressed.emit()
+1
View File
@@ -17,6 +17,7 @@ const STYLES: Dictionary = {
"DarkSpacer": "Panel",
"CharacterCard": "Panel",
"CharacterCardHover": "Panel",
"SettingsRowLabel": "Label",
"MusicList": "PanelContainer",
"SongRow": "PanelContainer",
"ShortcutRowDark": "PanelContainer",
+1 -1
View File
@@ -324,7 +324,7 @@ func test_changing_server_gets_a_new_token() -> void:
_server.respond_json("/music/info", {})
MusicServerClient.fetch_song_info(func(_data: SongInfo) -> void: pass)
Settings.default_path = "http://localhost:8080"
Settings.default_path = "http://another-server:8080"
MusicServerClient.fetch_song_info(func(_data: SongInfo) -> void: pass)
Settings.default_path = previous_path