From 222d231b1b2031b60923c0a154ae6742566c5b25 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 7 Oct 2026 23:07:47 +0200 Subject: [PATCH] Add offline mode: play from a music folder instead of the server Settings has a Music folder row. A folder with subfolders is played as soundtracks: Next picks a random subfolder and a random song in it, the subfolder is the answer's soundtrack and the file name without its type the song. The inspiration list and Search show the subfolders. A folder with songs and no subfolders is a prepared quiz, played once in order (natural sort) or at random, showing only the song title. It has no winning score: after the last song Next becomes Show winner, and the players with the most points win, ties included. While a folder is used nothing but the characters is asked from the server: Sync, QR, Statistics, the update check and play counting are off. Characters fall back to the cached pictures when the server can't be reached. mp3, ogg and wav play; other files and broken ones are skipped. The old local playback code, the hidden Open button and the hidden Local toggle are removed. Next now waits for the song to start before preparing the next one. --- common/audio_file.gd | 30 + common/audio_file.gd.uid | 1 + common/character_cache.gd | 22 +- data/music_folder.gd | 141 + data/music_folder.gd.uid | 1 + data/player_object.gd | 36 +- data/playlist.gd | 6 +- data/settings.gd | 17 +- data/song_object.gd | 19 +- main/MainWindow.tscn | 4 +- main/TopBar.tscn | 14 + main/main_window.gd | 10 +- main/top_bar.gd | 33 +- player/music_player.gd | 27 +- popups/SettingsPopup.tscn | 76 +- popups/about_popup.gd | 5 +- popups/score_graph.gd | 4 +- popups/search_popup.gd | 3 + popups/settings_popup.gd | 91 +- popups/winner_popup.gd | 92 +- quiz/Quiz.tscn | 20 - quiz/QuizSettingsCard.tscn | 1 + quiz/inspiration_list.gd | 10 +- quiz/quiz.gd | 84 +- quiz/quiz_settings_card.gd | 3 + quiz/song_controls.gd | 30 +- quiz/song_loader.gd | 43 +- tests/data/layout_baseline.json | 7199 +++++++++++++++++++- tests/data/short.ogg | Bin 0 -> 22615 bytes tests/data/short.wav | Bin 0 -> 44244 bytes tests/fakes/ui_tour.gd | 50 + tests/integration/test_inspiration_list.gd | 14 +- tests/integration/test_offline_mode.gd | 339 + tests/integration/test_offline_mode.gd.uid | 1 + tests/integration/test_scene_split.gd | 2 +- tests/integration/test_settings_popup.gd | 2 +- tests/integration/test_winner_popup.gd | 17 + tests/unit/test_audio_file.gd | 45 + tests/unit/test_audio_file.gd.uid | 1 + tests/unit/test_character_cache.gd | 23 + tests/unit/test_music_folder.gd | 202 + tests/unit/test_music_folder.gd.uid | 1 + 42 files changed, 8245 insertions(+), 474 deletions(-) create mode 100644 common/audio_file.gd create mode 100644 common/audio_file.gd.uid create mode 100644 data/music_folder.gd create mode 100644 data/music_folder.gd.uid create mode 100644 tests/data/short.ogg create mode 100644 tests/data/short.wav create mode 100644 tests/integration/test_offline_mode.gd create mode 100644 tests/integration/test_offline_mode.gd.uid create mode 100644 tests/unit/test_audio_file.gd create mode 100644 tests/unit/test_audio_file.gd.uid create mode 100644 tests/unit/test_music_folder.gd create mode 100644 tests/unit/test_music_folder.gd.uid diff --git a/common/audio_file.gd b/common/audio_file.gd new file mode 100644 index 0000000..bada64a --- /dev/null +++ b/common/audio_file.gd @@ -0,0 +1,30 @@ +class_name AudioFile +extends RefCounted +## Reads songs from files: the songs downloaded from the server, and the songs in a music +## folder. + +## The file types Godot can play from a file while the game runs. +const PLAYABLE_EXTENSIONS: Array[String] = ["mp3", "ogg", "wav"] + + +## Whether [param path] is a file type the game can play. +static func is_playable(path: String) -> bool: + return path.get_extension().to_lower() in PLAYABLE_EXTENSIONS + + +## Reads the song in [param path]. Returns null if the file is missing, is not a playable +## type, or is broken. +static func load(path: String) -> AudioStream: + if !FileAccess.file_exists(path): + return null + var stream: AudioStream = null + match path.get_extension().to_lower(): + "mp3": + stream = AudioStreamMP3.load_from_file(path) + "ogg": + stream = AudioStreamOggVorbis.load_from_file(path) + "wav": + stream = AudioStreamWAV.load_from_file(path) + if stream == null || stream.get_length() <= 0.0: + return null + return stream diff --git a/common/audio_file.gd.uid b/common/audio_file.gd.uid new file mode 100644 index 0000000..2eb307c --- /dev/null +++ b/common/audio_file.gd.uid @@ -0,0 +1 @@ +uid://drle78nqw2ve0 diff --git a/common/character_cache.gd b/common/character_cache.gd index d624e6a..e292ab6 100644 --- a/common/character_cache.gd +++ b/common/character_cache.gd @@ -10,7 +10,8 @@ extends RefCounted ## [/codeblock] ## A cached picture is used while the server's change time matches the index. Servers that ## can't tell when pictures changed answer 404 for [code]/character/details[/code]; then a -## cached picture is used until it is [constant MAX_AGE_SECONDS] old. +## cached picture is used until it is [constant MAX_AGE_SECONDS] old. When the server +## can't be reached at all, the cached pictures are used as they are. const FOLDER_NAME: String = "characters" const INDEX_FILE_NAME: String = "index.json" @@ -24,6 +25,8 @@ const UNKNOWN_TIME: int = -1 var _index: Dictionary = {} # When the server last changed each picture on the list, or UNKNOWN_TIME var _modified: Dictionary[String, int] = {} +# True when the server could not be reached and only cached pictures are shown +var _cache_only: bool = false ## The folder the pictures are kept in. @@ -36,6 +39,7 @@ static func folder() -> String: ## deleted. func load_list(on_done: Callable) -> void: _load_index() + _cache_only = false var details_fetched: Callable = func details_fetched(details: Array) -> void: _modified.clear() for character: CharacterDetails in details: @@ -48,7 +52,7 @@ func load_list(on_done: Callable) -> void: ## disk when the cached one is still fresh, otherwise from the server. func fetch_image(character_name: String, on_done: Callable) -> void: var path: String = _file_path(character_name) - if _is_fresh(character_name) && FileAccess.file_exists(path): + if (_cache_only || _is_fresh(character_name)) && FileAccess.file_exists(path): var cached: Image = CharacterCard.image_from_bytes(FileAccess.get_file_as_bytes(path)) if cached != null: on_done.call(cached) @@ -72,7 +76,19 @@ func _load_names_only(on_done: Callable) -> void: for character_name: Variant in list: _modified[str(character_name)] = UNKNOWN_TIME _list_loaded(on_done) - MusicServerClient.fetch_character_list(list_fetched) + MusicServerClient.fetch_character_list(list_fetched, _load_cached_only.bind(on_done)) + + +# The server can't be reached, so the pictures already cached are shown and kept +func _load_cached_only(on_done: Callable) -> void: + GameLog.warning("Could not get the characters from the server, showing the cached ones") + _cache_only = true + var names: Array[String] = [] + for character_name: String in _index: + if FileAccess.file_exists(_file_path(character_name)): + names.append(character_name) + names.sort_custom(func(a: String, b: String) -> bool: return a.naturalnocasecmp_to(b) < 0) + on_done.call(names) func _list_loaded(on_done: Callable) -> void: diff --git a/data/music_folder.gd b/data/music_folder.gd new file mode 100644 index 0000000..87f277d --- /dev/null +++ b/data/music_folder.gd @@ -0,0 +1,141 @@ +class_name MusicFolder +extends RefCounted +## A folder of songs to play instead of the MusicServer's, picked in the settings. +## +## A folder with subfolders is a soundtrack folder: each subfolder is a soundtrack, and +## songs are picked at random from a random soundtrack. Songs at the top are left out. +## A folder with songs and no subfolders is a prepared quiz: every song plays once, in +## order or at random, and only the song's name is the answer. +## +## Hidden files and folders, and files the game can't play (see [AudioFile]), are left +## out. + +enum Kind { EMPTY, SOUNDTRACKS, PREPARED_QUIZ } +enum PlayOrder { IN_ORDER, RANDOM } + +## How many files are tried before giving up when files can't be read. +const MAX_TRIES: int = 20 + +## The folder's path. +var path: String = "" +var kind: Kind = Kind.EMPTY +## How a prepared quiz is played. Change it with [method set_play_order]. +var play_order: PlayOrder = PlayOrder.IN_ORDER + +# Soundtrack folder: soundtrack name -> paths of its songs +var _soundtracks: Dictionary[String, PackedStringArray] = {} +# Prepared quiz: the songs in order, the order they are played in and how many are played +var _quiz_songs: PackedStringArray = [] +var _quiz_queue: PackedStringArray = [] +var _quiz_next: int = 0 + + +## Reads the folder at [param folder_path]. Its [member kind] is [constant Kind.EMPTY] +## when there is nothing to play in it. +static func scan(folder_path: String) -> MusicFolder: + var folder: MusicFolder = MusicFolder.new() + folder.path = folder_path.trim_suffix("/") + var subfolders: PackedStringArray = _visible(DirAccess.get_directories_at(folder.path)) + if !subfolders.is_empty(): + for subfolder: String in subfolders: + var songs: PackedStringArray = _songs_in(folder.path.path_join(subfolder), true) + if !songs.is_empty(): + folder._soundtracks[subfolder] = songs + if !folder._soundtracks.is_empty(): + folder.kind = Kind.SOUNDTRACKS + else: + folder._quiz_songs = _songs_in(folder.path, false) + if !folder._quiz_songs.is_empty(): + folder.kind = Kind.PREPARED_QUIZ + folder.restart() + return folder + + +## The folder's name, to show to the user. +func display_name() -> String: + return path.get_file() + + +## The names of the soundtracks, sorted. Empty for a prepared quiz. +func soundtrack_names() -> Array[String]: + var names: Array[String] = [] + names.assign(_soundtracks.keys()) + names.sort_custom(func(a: String, b: String) -> bool: return a.naturalnocasecmp_to(b) < 0) + return names + + +## How many songs a prepared quiz has. +func quiz_size() -> int: + return _quiz_songs.size() + + +## Plays a prepared quiz in [param order] from its first song. +func set_play_order(order: PlayOrder) -> void: + play_order = order + restart() + + +## Starts a prepared quiz over from its first song, shuffled again if it is played at +## random. +func restart() -> void: + _quiz_queue = _quiz_songs.duplicate() + if play_order == PlayOrder.RANDOM: + var shuffled: Array = Array(_quiz_queue) + shuffled.shuffle() + _quiz_queue = PackedStringArray(shuffled) + _quiz_next = 0 + + +## The next song to play, or null when a prepared quiz has played every song or no file +## can be read. +func next_song() -> SongObject: + for i: int in MAX_TRIES: + var song_path: String = _pick() + if song_path.is_empty(): + return null + if AudioFile.load(song_path) != null: + return _song_object(song_path) + GameLog.warning("Skipping a song that can't be played: ", song_path) + return null + + +func _pick() -> String: + if kind == Kind.SOUNDTRACKS: + var songs: PackedStringArray = _soundtracks[_soundtracks.keys().pick_random()] + return songs[randi() % songs.size()] + if _quiz_next >= _quiz_queue.size(): + return "" + _quiz_next += 1 + return _quiz_queue[_quiz_next - 1] + + +func _song_object(song_path: String) -> SongObject: + var song_object: SongObject = SongObject.new() + song_object.song_path = song_path + song_object.song_title = song_path.get_file().get_basename() + if kind == Kind.SOUNDTRACKS: + # The soundtrack is the subfolder right under the music folder + song_object.soundtrack_title = song_path.trim_prefix(path + "/").get_slice("/", 0) + return song_object + + +# The playable songs in [param dir], in natural order, also in subfolders if [param deep] +static func _songs_in(dir: String, deep: bool) -> PackedStringArray: + var songs: Array[String] = [] + for file_name: String in _visible(DirAccess.get_files_at(dir)): + if AudioFile.is_playable(file_name): + songs.append(dir.path_join(file_name)) + songs.sort_custom(func(a: String, b: String) -> bool: + return a.get_file().naturalnocasecmp_to(b.get_file()) < 0) + if deep: + for subfolder: String in _visible(DirAccess.get_directories_at(dir)): + songs.append_array(_songs_in(dir.path_join(subfolder), true)) + return PackedStringArray(songs) + + +static func _visible(names: PackedStringArray) -> PackedStringArray: + var shown: PackedStringArray = [] + for entry: String in names: + if !entry.begins_with("."): + shown.append(entry) + return shown diff --git a/data/music_folder.gd.uid b/data/music_folder.gd.uid new file mode 100644 index 0000000..4aed865 --- /dev/null +++ b/data/music_folder.gd.uid @@ -0,0 +1 @@ +uid://bcrem1inl47ve diff --git a/data/player_object.gd b/data/player_object.gd index b569bc6..77cce1b 100644 --- a/data/player_object.gd +++ b/data/player_object.gd @@ -38,23 +38,27 @@ static func _generate_uuid() -> String: hex.substr(16, 4), hex.substr(20, 12)] +## Gives the player a point. With a winning score, the point that reaches it wins; a +## prepared quiz has none, so points just add up until every song has played. func add_point() -> void: - if player_score < Settings.winning_score: - player_score += 1 - player_point_given.emit() - var first_point: bool = player_score == 1 && is_first_point - if player_score == 1: - is_first_point = false - # With a low winning score the first point can also be match point or the win, - # so the more important event is checked first - if player_score == Settings.winning_score: - winner_triggered.emit() - elif player_score == Settings.winning_score - 1: - match_point_triggered.emit() - elif first_point: - first_point_triggered.emit() - else: - point_given_sound.emit() + var has_winning_score: bool = !Settings.is_prepared_quiz() + if has_winning_score && player_score >= Settings.winning_score: + return + player_score += 1 + player_point_given.emit() + var first_point: bool = player_score == 1 && is_first_point + if player_score == 1: + is_first_point = false + # With a low winning score the first point can also be match point or the win, + # so the more important event is checked first + if has_winning_score && player_score == Settings.winning_score: + winner_triggered.emit() + elif has_winning_score && player_score == Settings.winning_score - 1: + match_point_triggered.emit() + elif first_point: + first_point_triggered.emit() + else: + point_given_sound.emit() func minus_point() -> void: diff --git a/data/playlist.gd b/data/playlist.gd index 5a250a8..9060ad9 100644 --- a/data/playlist.gd +++ b/data/playlist.gd @@ -66,7 +66,7 @@ func song_is_answered(song_no: int = currently_playing_song) -> void: return GameLog.debug("show_answer_pressed song_object: ", song_object_array[song_no]._to_string()) song_object_array[song_no].is_answered = true - if Settings.add_to_stats: + if Settings.add_to_stats && !Settings.is_offline(): GameLog.debug("add to stats: ", song_no) MusicServerClient.mark_song_played(song_no) @@ -141,9 +141,7 @@ func get_song(song_no: int) -> AudioStream: var song_object: SongObject = song_object_array[song_no] if song_object.song_path.is_empty(): return song_object.song - if !FileAccess.file_exists(song_object.song_path): - return null - return AudioStreamMP3.load_from_file(song_object.song_path) + return AudioFile.load(song_object.song_path) func add_song_to_song_object(song_no: int, mp3: PackedByteArray) -> void: diff --git a/data/settings.gd b/data/settings.gd index 89391b1..d7ff19e 100644 --- a/data/settings.gd +++ b/data/settings.gd @@ -61,7 +61,6 @@ var api_password: String = "" ## the config file when it is not one of them. var servers: Array[String] = SERVERS.duplicate() var selected_server: int = 0 -var is_local: bool = false var is_debug: bool = false var stop_after_current: bool = true var auto_repeat_song: bool = true @@ -70,7 +69,6 @@ var add_to_stats: bool = true var use_low_played_mode: bool = false var winning_score: int = 20 var fullscreen: bool = false -var play_local: bool = false var inspiration_list_speed: int = 1 var hide_for_seconds: float = 5.0 var hide_beginning: bool = false @@ -87,6 +85,9 @@ var queue_ids: Dictionary[String, String] = {} ## The version the game last showed the About popup for. When it is not [member version], ## this version is new and the main window opens the About popup to show what is new. var last_seen_version: String = "" +## The folder songs are played from instead of the server, picked in the settings, or null +## to use the server. Kept for this session only. +var music_folder: MusicFolder = null ## The game version, set in Project Settings > Application > Config > Version. ## Releases are made from it, see RELEASING.md. @@ -220,6 +221,18 @@ func _saved_value(file: ConfigFile, section: String, key: String, default: Varia return value +## Whether songs come from a music folder instead of the server. Nothing but the +## characters is asked from the server then. +func is_offline() -> bool: + return music_folder != null + + +## Whether a prepared quiz is played from a music folder. It has no winning score: the +## game ends when every song has played. +func is_prepared_quiz() -> bool: + return music_folder != null && music_folder.kind == MusicFolder.Kind.PREPARED_QUIZ + + func delete_children(node: Node) -> void: for n: Node in node.get_children(): node.remove_child(n) diff --git a/data/song_object.gd b/data/song_object.gd index b3eca53..3bf00d6 100644 --- a/data/song_object.gd +++ b/data/song_object.gd @@ -12,8 +12,8 @@ var players_given_point: Array[String] = [] var is_answered: bool = false var has_played: bool = false var is_playing: bool = false -## The song's file in [method Playlist.songs_folder]. Read the song with -## [method Playlist.get_song]. +## The song's file: a download in [method Playlist.songs_folder], or a song in the music +## folder. Read the song with [method Playlist.get_song]. var song_path: String = "" ## The song itself, only when it could not be saved to a file. var song: AudioStream @@ -35,16 +35,21 @@ func get_song_title() -> String: func get_song_info() -> String: if Settings.is_debug: - var format_string: String = "%d. %s - %s | played: %s | answered: %s" - return format_string % [(song_number + 1), soundtrack_title, song_title, has_played, - is_answered] + return "%d. %s | played: %s | answered: %s" % [(song_number + 1), _full_title(), + has_played, is_answered] elif is_answered: - var format_string: String = "%d. %s - %s" - return format_string % [(song_number + 1), soundtrack_title, song_title] + return "%d. %s" % [(song_number + 1), _full_title()] else: return "??? - ???" +# "Soundtrack - Song", or only the song in a prepared quiz, which has no soundtracks +func _full_title() -> String: + if soundtrack_title.is_empty(): + return song_title + return "%s - %s" % [soundtrack_title, song_title] + + func add_point(player_id: String) -> void: GameLog.debug("add_point") if !players_given_point.has(player_id): diff --git a/main/MainWindow.tscn b/main/MainWindow.tscn index 366f148..e37f34d 100644 --- a/main/MainWindow.tscn +++ b/main/MainWindow.tscn @@ -98,9 +98,9 @@ unique_name_in_owner = true visible = false layout_mode = 1 offset_left = 700.0 -offset_top = 419.0 +offset_top = 405.0 offset_right = 700.0 -offset_bottom = 419.0 +offset_bottom = 405.0 [node name="SoundtrackStatsPopup" parent="." unique_id=1213999001 instance=ExtResource("soundtrack_stats_popup_scene")] visible = false diff --git a/main/TopBar.tscn b/main/TopBar.tscn index 450329c..69132ad 100644 --- a/main/TopBar.tscn +++ b/main/TopBar.tscn @@ -53,6 +53,20 @@ horizontal_alignment = 2 vertical_alignment = 1 text_overrun_behavior = 3 +[node name="OfflineLabel" type="Label" parent="." unique_id=1959221259] +visible = false +layout_mode = 0 +offset_left = 1096.0 +offset_top = 12.0 +offset_right = 1432.0 +offset_bottom = 50.0 +mouse_filter = 1 +theme_type_variation = &"HighlightMedium" +text = "Offline: Music" +horizontal_alignment = 2 +vertical_alignment = 1 +text_overrun_behavior = 3 + [node name="SyncButton" type="Button" parent="." unique_id=1228313556] layout_mode = 0 offset_left = 1450.0 diff --git a/main/main_window.gd b/main/main_window.gd index 6dde8b4..1e020d1 100644 --- a/main/main_window.gd +++ b/main/main_window.gd @@ -38,7 +38,7 @@ func _ready() -> void: sync_popup.sync_started.connect(_on_sync_started) sync_popup.sync_finished.connect(_on_sync_finished) quiz.server_changed.connect(search_popup.get_list_of_soundtracks) - quiz.server_changed.connect(top_bar.check_for_update) + quiz.server_changed.connect(_on_music_source_changed) quiz.event_logged.connect(log_popup.add_log_row) statistics_view.soundtrack_selected.connect(soundtrack_stats_popup.show_soundtrack) @@ -46,6 +46,14 @@ func _ready() -> void: show_about_if_new_version() +# After another server or a music folder was picked. Statistics need the server. +func _on_music_source_changed() -> void: + top_bar.update_offline_state() + top_bar.check_for_update() + if Settings.is_offline() && statistics_view.visible: + show_tab(&"quiz") + + ## Opens the About popup, with what is new, the first time this version starts. func show_about_if_new_version() -> void: if Settings.last_seen_version == Settings.version: diff --git a/main/top_bar.gd b/main/top_bar.gd index 3b1c9b5..e8a2b33 100644 --- a/main/top_bar.gd +++ b/main/top_bar.gd @@ -2,6 +2,12 @@ class_name TopBar extends Control ## The bar along the top of the main window, with the title, the Quiz and Statistics tabs ## and the buttons for things that are not part of a quiz. +## +## With a music folder picked, the buttons that need the server are turned off and the +## folder's name is shown. + +## Shown on the buttons that need the server while a music folder is used. +const OFFLINE_TOOLTIP: String = "Not available with a music folder" signal sync_pressed signal settings_pressed @@ -14,6 +20,8 @@ signal tab_selected(tab: StringName) @onready var sync_button: Button = $SyncButton @onready var debug_label: Label = $DebugLabel @onready var update_label: Label = $UpdateLabel +@onready var offline_label: Label = $OfflineLabel +@onready var qr_button: Button = $QRButton @onready var quiz_tab: Button = $QuizTab @onready var statistics_tab: Button = $StatisticsTab @@ -25,22 +33,41 @@ func _ready() -> void: $SettingsButton.pressed.connect(settings_pressed.emit) $ShortcutsButton.pressed.connect(shortcuts_pressed.emit) $AboutButton.pressed.connect(about_pressed.emit) - $QRButton.pressed.connect(qr_pressed.emit) + qr_button.pressed.connect(qr_pressed.emit) quiz_tab.pressed.connect(tab_selected.emit.bind(&"quiz")) statistics_tab.pressed.connect(tab_selected.emit.bind(&"statistics")) + update_offline_state() check_for_update() +## Turns the buttons that need the server off or on, after a music folder was picked or +## the server is used again. +func update_offline_state() -> void: + var offline: bool = Settings.is_offline() + for button: Button in [sync_button, qr_button, statistics_tab]: + button.disabled = offline + button.tooltip_text = OFFLINE_TOOLTIP if offline else "" + offline_label.visible = offline + if offline: + offline_label.text = "Offline: " + Settings.music_folder.display_name() + offline_label.tooltip_text = Settings.music_folder.path + update_label.visible = false + + ## Asks the server for the newest MusicPlayer release and tells the user when it is newer -## than this one. +## than this one. Not asked while a music folder is used. func check_for_update() -> void: + if Settings.is_offline(): + update_label.visible = false + return MusicServerClient.fetch_latest_client_version(show_latest_version) ## Shows that [param latest] is available when it is newer than this version, and hides ## the message otherwise. func show_latest_version(latest: Variant) -> void: - var newer: bool = latest is String && is_newer_version(latest, Settings.version) + var newer: bool = latest is String && is_newer_version(latest, Settings.version) \ + && !Settings.is_offline() update_label.visible = newer if newer: update_label.text = "Version %s is available" % latest diff --git a/player/music_player.gd b/player/music_player.gd index 8aa0edc..6fffd72 100644 --- a/player/music_player.gd +++ b/player/music_player.gd @@ -8,7 +8,6 @@ signal play_next_song var play_icon: Texture = preload("res://icons/play_icon_light.svg") var pause_icon: Texture = preload("res://icons/pause_icon_light.svg") -var songs: Array = [] var is_changing: bool = false var playback_position: float var stream: AudioStream @@ -199,31 +198,19 @@ func play_song_object(song_object_no: int) -> void: update_song_list.emit() +## Plays a song to test the sound: one from the server, or a song that comes with the +## game when playing from a music folder. func get_sound_test_song() -> void: + if Settings.is_offline(): + sound_test_local() + return var test_sound: Callable = func test_sound(body: PackedByteArray) -> void: var sound: AudioStream = AudioStreamMP3.new() sound.data = body play_song(sound) MusicServerClient.fetch_sound_test_song(test_sound) -##### LOCAL -var local_path: String = '/Users/sebastian/ResilioSync/Sorterat_test/Metal Gear Solid 4 - Guns of the Patriots/2-16 Metal Gear Saga.mp3' - - -func load_mp3(_path: String) -> AudioStream: - var file: FileAccess = FileAccess.open(_path, FileAccess.READ) - var sound: AudioStream = AudioStreamMP3.new() - sound.data = file.get_buffer(file.get_length()) - return sound - - -func play_local_song() -> void: - if songs: - local_path = songs[0] - GameLog.debug(local_path) - GameLog.debug(FileAccess.file_exists(local_path)) - play_sound(load_mp3(local_path)) - +# The sound test without a server: a song that comes with the game func sound_test_local() -> void: - play_sound(preload("res://sounds/01. Opening.mp3")) + play_song(preload("res://sounds/01. Opening.mp3")) diff --git a/popups/SettingsPopup.tscn b/popups/SettingsPopup.tscn index 0570563..4cc0488 100644 --- a/popups/SettingsPopup.tscn +++ b/popups/SettingsPopup.tscn @@ -16,7 +16,7 @@ panel = NodePath("SettingsPanel") unique_name_in_owner = true layout_mode = 0 offset_right = 520.0 -offset_bottom = 242.0 +offset_bottom = 270.0 theme_type_variation = &"PopupBackground" [node name="VBoxContainer" type="VBoxContainer" parent="SettingsPanel" unique_id=1796635163] @@ -25,7 +25,7 @@ layout_mode = 0 offset_left = 20.0 offset_top = 12.0 offset_right = 500.0 -offset_bottom = 230.0 +offset_bottom = 258.0 [node name="Label" type="Label" parent="SettingsPanel/VBoxContainer" unique_id=1111531402] layout_mode = 2 @@ -47,6 +47,65 @@ layout_mode = 2 size_flags_horizontal = 3 focus_mode = 0 +[node name="FolderRow" type="HBoxContainer" parent="SettingsPanel/VBoxContainer"] +layout_mode = 2 + +[node name="Title" type="Label" parent="SettingsPanel/VBoxContainer/FolderRow"] +layout_mode = 2 +theme_type_variation = &"SettingsRowLabel" +text = "Music folder" + +[node name="FolderLabel" type="Label" parent="SettingsPanel/VBoxContainer/FolderRow"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +mouse_filter = 1 +text = "None, using the server" +text_overrun_behavior = 3 + +[node name="ChooseFolderButton" type="Button" parent="SettingsPanel/VBoxContainer/FolderRow"] +unique_name_in_owner = true +layout_mode = 2 +focus_mode = 0 +text = "Choose…" + +[node name="UseServerButton" type="Button" parent="SettingsPanel/VBoxContainer/FolderRow"] +unique_name_in_owner = true +visible = false +layout_mode = 2 +focus_mode = 0 +text = "Use server" + +[node name="FolderErrorLabel" type="Label" parent="SettingsPanel/VBoxContainer"] +unique_name_in_owner = true +visible = false +layout_mode = 2 +theme_type_variation = &"HighlightMedium" +text = "No songs found" +text_overrun_behavior = 3 + +[node name="PlayOrderRow" type="HBoxContainer" parent="SettingsPanel/VBoxContainer"] +unique_name_in_owner = true +visible = false +layout_mode = 2 + +[node name="Title" type="Label" parent="SettingsPanel/VBoxContainer/PlayOrderRow"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_type_variation = &"SettingsRowLabel" +text = "Play the prepared quiz" + +[node name="PlayOrderButton" type="OptionButton" parent="SettingsPanel/VBoxContainer/PlayOrderRow"] +unique_name_in_owner = true +layout_mode = 2 +focus_mode = 0 +selected = 0 +item_count = 2 +popup/item_0/text = "In order" +popup/item_0/id = 0 +popup/item_1/text = "At random" +popup/item_1/id = 1 + [node name="FullscreenButton" type="CheckButton" parent="SettingsPanel/VBoxContainer" unique_id=400304684] unique_name_in_owner = true layout_mode = 2 @@ -99,9 +158,12 @@ focus_mode = 0 action_mode = 0 text = "Count plays in statistics" -[node name="LocalButton" type="CheckButton" parent="SettingsPanel/VBoxContainer" unique_id=905844805] +[node name="FolderDialog" type="FileDialog" parent="."] unique_name_in_owner = true -visible = false -layout_mode = 2 -disabled = true -text = "Local" +title = "Choose a music folder" +initial_position = 2 +size = Vector2i(800, 500) +ok_button_text = "Select Current Folder" +file_mode = 2 +access = 2 +use_native_dialog = true diff --git a/popups/about_popup.gd b/popups/about_popup.gd index 93258ad..32645f8 100644 --- a/popups/about_popup.gd +++ b/popups/about_popup.gd @@ -71,8 +71,11 @@ func open() -> void: whats_new_label.text = changelog_text(entries) # Asked every time, as another server can be picked in the settings - server_version_label.text = "Loading…" server_history_label.text = "" + if Settings.is_offline(): + server_version_label.text = "Not connected, playing from a music folder" + return + server_version_label.text = "Loading…" MusicServerClient.fetch_version_history(_show_server_history, _show_server_history_failed) diff --git a/popups/score_graph.gd b/popups/score_graph.gd index 6e9a5cd..b571375 100644 --- a/popups/score_graph.gd +++ b/popups/score_graph.gd @@ -20,7 +20,7 @@ var _winning_score: int = 0 ## Shows [param history]. The y axis goes up to [param winning_score], or higher if -## someone has more points. +## someone has more points. A winning score of 0 draws no winning line. func show_history(history: ScoreHistory, winning_score: int) -> void: _history = history _winning_score = winning_score @@ -117,6 +117,8 @@ func _draw_score_axis(font: Font, font_size: int, text_color: Color) -> void: draw_line(Vector2(MARGIN_LEFT, y), Vector2(size.x - MARGIN_RIGHT, y), grid_color, 1) draw_string(font, Vector2(0, y + font_size * 0.35), str(score), HORIZONTAL_ALIGNMENT_RIGHT, MARGIN_LEFT - 8, font_size, text_color) + if _winning_score <= 0: + return var winning_color: Color = get_theme_color(&"winning_line", THEME_TYPE) var winning_y: float = _y(_winning_score) draw_dashed_line(Vector2(MARGIN_LEFT, winning_y), Vector2(size.x - MARGIN_RIGHT, winning_y), diff --git a/popups/search_popup.gd b/popups/search_popup.gd index eb47664..d46b9ab 100644 --- a/popups/search_popup.gd +++ b/popups/search_popup.gd @@ -80,6 +80,9 @@ func shown_names() -> Array[String]: func get_list_of_soundtracks() -> void: GameLog.debug("get_list_of_soundtracks") + if Settings.is_offline(): + set_soundtracks(Settings.music_folder.soundtrack_names()) + return var handle_soundtracks: Callable = func handle_soundtracks(array: Variant) -> void: if typeof(array) == TYPE_ARRAY: set_soundtracks(array) diff --git a/popups/settings_popup.gd b/popups/settings_popup.gd index f248822..33d7c76 100644 --- a/popups/settings_popup.gd +++ b/popups/settings_popup.gd @@ -1,14 +1,21 @@ class_name SettingsPopup extends BasePopup -## The application settings: which MusicServer to use, fullscreen, how many songs to -## download ahead, whether to prefer less played soundtracks and whether plays count in -## the statistics. Opened from the Settings button in the top bar. +## The application settings: which MusicServer or music folder to play from, fullscreen, +## how many songs to download ahead, whether to prefer less played soundtracks and whether +## plays count in the statistics. Opened from the Settings button in the top bar. +## +## A music folder (see [MusicFolder]) is used instead of the server until Use server is +## pressed or the game is closed. The settings that need the server are off meanwhile. ## ## The quiz settings, such as the winning score, are in [QuizSettingsCard]. -## Emitted when another MusicServer is picked. +## Emitted when another MusicServer or music folder is picked, or a prepared quiz's play +## order is changed. A new game starts. signal server_changed +## Space between the rows and the panel's edges. +const PADDING: Vector2 = Vector2(20, 12) + @onready var settings_panel: Panel = %SettingsPanel @onready var select_server_button: OptionButton = %SelectServerButton @onready var fullscreen_button: CheckButton = %FullscreenButton @@ -17,7 +24,17 @@ signal server_changed @onready var increase_cache_button: Button = %IncreaseCacheButton @onready var low_played_button: CheckButton = %LowPlayedCheckButton @onready var add_to_database_button: CheckButton = %AddToDatabaseCheckButton -@onready var local_button: CheckButton = %LocalButton +@onready var rows: VBoxContainer = %VBoxContainer +@onready var folder_label: Label = %FolderLabel +@onready var choose_folder_button: Button = %ChooseFolderButton +@onready var use_server_button: Button = %UseServerButton +@onready var folder_error_label: Label = %FolderErrorLabel +@onready var play_order_row: HBoxContainer = %PlayOrderRow +@onready var play_order_button: OptionButton = %PlayOrderButton +@onready var folder_dialog: FileDialog = %FolderDialog + +# Where the panel's middle is, so it stays centred when rows are shown or hidden +var _panel_middle_y: float = 0.0 func _ready() -> void: @@ -28,7 +45,10 @@ func _ready() -> void: cache_label.text = str(Playlist.number_of_tracks_to_preload) fullscreen_button.pressed.connect(fullscreen) - local_button.pressed.connect(local_play) + choose_folder_button.pressed.connect(folder_dialog.popup_centered) + folder_dialog.dir_selected.connect(use_music_folder) + use_server_button.pressed.connect(use_server) + play_order_button.item_selected.connect(_on_play_order_selected) lower_cache_button.pressed.connect(lower_cache) increase_cache_button.pressed.connect(increase_cache) select_server_button.item_selected.connect(select_server) @@ -38,14 +58,34 @@ func _ready() -> void: visibility_changed.connect(_on_visibility_changed) # Alt+Enter switches fullscreen from the main window, which resizes the window get_window().size_changed.connect(_update_fullscreen_button) + _panel_middle_y = settings_panel.position.y + settings_panel.size.y / 2 + _show_music_source() func fullscreen() -> void: Settings.set_fullscreen(!Settings.fullscreen) -func local_play() -> void: - Settings.play_local = !Settings.play_local +## Plays songs from the folder at [param path] instead of the server's. A folder with +## nothing to play is not used, and the popup says so. +func use_music_folder(path: String) -> void: + var folder: MusicFolder = MusicFolder.scan(path) + if folder.kind == MusicFolder.Kind.EMPTY: + folder_error_label.text = "No songs found in " + folder.display_name() + folder_error_label.visible = true + _fit_to_rows() + return + folder.set_play_order(play_order_button.selected as MusicFolder.PlayOrder) + Settings.music_folder = folder + _show_music_source() + server_changed.emit() + + +## Plays from the server again instead of the music folder. +func use_server() -> void: + Settings.music_folder = null + _show_music_source() + server_changed.emit() func lower_cache() -> void: @@ -83,6 +123,41 @@ func _on_visibility_changed() -> void: _update_fullscreen_button() low_played_button.set_pressed_no_signal(Settings.use_low_played_mode) add_to_database_button.set_pressed_no_signal(Settings.add_to_stats) + else: + folder_error_label.visible = false + _fit_to_rows() + + +func _on_play_order_selected(order: int) -> void: + if Settings.is_offline(): + Settings.music_folder.set_play_order(order as MusicFolder.PlayOrder) + server_changed.emit() + + +# Shows where songs come from, and turns off what needs the server while offline +func _show_music_source() -> void: + var folder: MusicFolder = Settings.music_folder + folder_error_label.visible = false + use_server_button.visible = folder != null + if folder == null: + folder_label.text = "None, using the server" + folder_label.tooltip_text = "" + else: + folder_label.text = folder.display_name() + folder_label.tooltip_text = folder.path + play_order_row.visible = folder != null && folder.kind == MusicFolder.Kind.PREPARED_QUIZ + for control: BaseButton in [select_server_button, low_played_button, add_to_database_button]: + control.disabled = folder != null + _fit_to_rows() + + +# Grows or shrinks the panel to fit the rows shown, keeping it centred +func _fit_to_rows() -> void: + var height: float = rows.get_combined_minimum_size().y + PADDING.y * 2 + settings_panel.size.y = height + settings_panel.position.y = floorf(_panel_middle_y - height / 2) + rows.position = PADDING + rows.size = Vector2(settings_panel.size.x - PADDING.x * 2, height - PADDING.y * 2) func _update_fullscreen_button() -> void: diff --git a/popups/winner_popup.gd b/popups/winner_popup.gd index cbf56c0..46a7fe8 100644 --- a/popups/winner_popup.gd +++ b/popups/winner_popup.gd @@ -22,43 +22,95 @@ func _ready() -> void: func show_winner(winner: PlayerObject) -> void: + var winners: Array[PlayerObject] = [winner] + show_winners(winners) + + +## Shows [param winners], who share the top score, with everyone else's placing below. +## Tied players get the same placing. With no players, it says the quiz is over. +func show_winners(winners: Array[PlayerObject]) -> void: visible = true - winner_label.text = winner.player_name + " won with " + str(winner.player_score) + \ - " points!!" - winner_picture.texture = winner.character - # The "no character" placeholder is pixel art, which blurs when scaled up smoothly - if winner.character.resource_path == PlayerObject.NO_CHARACTER_PATH: - winner_picture.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST - else: - winner_picture.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR + winner_label.text = winners_text(winners) + _show_pictures(winners) - # Clear previous player list entries for child: Node in players_list.get_children(): child.queue_free() - - # Sort players by score (descending) and add to list - var sorted_players: Array = Settings.player_array.duplicate() + var sorted_players: Array[PlayerObject] = Settings.player_array.duplicate() sorted_players.sort_custom( func(a: PlayerObject, b: PlayerObject) -> bool: return a.player_score > b.player_score ) - - var placing: int = 2 for player: PlayerObject in sorted_players: - if player == winner: - continue # Skip the winner, already shown above + if player in winners: + continue var label: Label = Label.new() - label.text = str(placing) + ". " + player.player_name + " - " + \ - str(player.player_score) + " points" + label.text = "%d. %s - %s" % [placing(player), player.player_name, + points_text(player.player_score)] players_list.add_child(label) - placing += 1 show_history(ScoreHistory.from_playlist(Settings.player_array)) +## The headline for [param winners], e.g. "Crono won with 5 points!!" or "Crono and +## Marle won with 5 points!!". +static func winners_text(winners: Array[PlayerObject]) -> String: + if winners.is_empty(): + return "The quiz is over!" + var names: PackedStringArray = [] + for winner: PlayerObject in winners: + names.append(winner.player_name) + var joined: String = names[-1] + if names.size() > 1: + joined = ", ".join(names.slice(0, -1)) + " and " + names[-1] + return "%s won with %s!!" % [joined, points_text(winners[0].player_score)] + + +## [param points] in words, e.g. "1 point" or "5 points". +static func points_text(points: int) -> String: + return "%d point%s" % [points, "" if points == 1 else "s"] + + +## Where [param player] placed: 1 plus how many players have more points, so tied +## players share a placing. +static func placing(player: PlayerObject) -> int: + var place: int = 1 + for other: PlayerObject in Settings.player_array: + if other.player_score > player.player_score: + place += 1 + return place + + +# One picture per winner, next to each other +func _show_pictures(winners: Array[PlayerObject]) -> void: + var header: Node = winner_picture.get_parent() + for extra: Node in header.get_children(): + if extra != winner_picture && extra is TextureRect: + header.remove_child(extra) + extra.queue_free() + winner_picture.visible = !winners.is_empty() + for index: int in winners.size(): + var picture: TextureRect = winner_picture + if index > 0: + picture = winner_picture.duplicate() + header.add_child(picture) + header.move_child(picture, winner_picture.get_index() + index) + _show_character(picture, winners[index]) + + +func _show_character(picture: TextureRect, player: PlayerObject) -> void: + picture.texture = player.character + # The "no character" placeholder is pixel art, which blurs when scaled up smoothly + if player.character.resource_path == PlayerObject.NO_CHARACTER_PATH: + picture.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST + else: + picture.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR + + ## Shows how the game went: the score graph, its legend and the summary boxes. func show_history(history: ScoreHistory) -> void: - score_graph.show_history(history, Settings.winning_score) + # A prepared quiz has no winning score to draw + var winning_score: int = 0 if Settings.is_prepared_quiz() else Settings.winning_score + score_graph.show_history(history, winning_score) streak_label.text = history.longest_streak_text() standstill_label.text = history.longest_standstill_text() shared_label.text = history.most_shared_text() diff --git a/quiz/Quiz.tscn b/quiz/Quiz.tscn index 2fc3c00..3d79b56 100644 --- a/quiz/Quiz.tscn +++ b/quiz/Quiz.tscn @@ -24,24 +24,6 @@ grow_vertical = 2 mouse_filter = 2 script = ExtResource("quiz_script") -[node name="Open" type="Button" parent="." unique_id=165052329] -visible = false -layout_mode = 0 -offset_left = 1296.0 -offset_top = 66.0 -offset_right = 1346.0 -offset_bottom = 97.0 -text = "Open" - -[node name="FileDialog" type="FileDialog" parent="." unique_id=485771559] -title = "Open a Directory" -initial_position = 2 -size = Vector2i(616, 400) -ok_button_text = "Select Current Folder" -file_mode = 2 -access = 2 -show_hidden_files = true - [node name="MusicListPanel" parent="." unique_id=2011292375 instance=ExtResource("scene_MusicListPanel")] layout_mode = 0 offset_left = 64.0 @@ -131,5 +113,3 @@ offset_bottom = 984.0 [node name="SongLoader" type="Node" parent="." unique_id=1520867349] script = ExtResource("song_loader_script") - -[connection signal="dir_selected" from="FileDialog" to="." method="_on_file_dialog_dir_selected"] diff --git a/quiz/QuizSettingsCard.tscn b/quiz/QuizSettingsCard.tscn index d461fad..faa65b9 100644 --- a/quiz/QuizSettingsCard.tscn +++ b/quiz/QuizSettingsCard.tscn @@ -105,6 +105,7 @@ action_mode = 0 text = "Hide ticks in the playbar" [node name="WinningScoreRow" type="HBoxContainer" parent="CardPanel/VBox"] +unique_name_in_owner = true layout_mode = 2 [node name="Title" type="Label" parent="CardPanel/VBox/WinningScoreRow"] diff --git a/quiz/inspiration_list.gd b/quiz/inspiration_list.gd index 1a691ca..665867a 100644 --- a/quiz/inspiration_list.gd +++ b/quiz/inspiration_list.gd @@ -48,9 +48,13 @@ func _process(delta: float) -> void: scroll_bar.value = 0 -## Fetches every soundtrack from the server and shows them. -func load_from_server() -> void: - GameLog.debug("load_from_server") +## Shows every soundtrack: the music folder's soundtracks when one is picked, otherwise +## the server's. +func load_soundtracks() -> void: + GameLog.debug("load_soundtracks") + if Settings.is_offline(): + show_soundtracks(Settings.music_folder.soundtrack_names()) + return var populate_list: Callable = func populate_list(array: Variant) -> void: if typeof(array) == TYPE_ARRAY: show_soundtracks(array) diff --git a/quiz/quiz.gd b/quiz/quiz.gd index 1617a42..ca31f23 100644 --- a/quiz/quiz.gd +++ b/quiz/quiz.gd @@ -18,7 +18,6 @@ signal search_pressed ## Emitted with a line for the game log, e.g. when a player gets a point. signal event_logged(text: String) -var songs: Array = [] var current_player_row: PlayerRow ## True while statistics are off because a player won, until points are reset. @@ -27,8 +26,6 @@ var stats_paused_by_win: bool = false ## Dims the window behind an open popup and keeps clicks from reaching the buttons under it. var popup_backdrop: PopupBackdrop -@onready var open_button: Button = $Open -@onready var file_dialog: FileDialog = $FileDialog @onready var inspiration_list: InspirationList = $InspirationList @onready var now_playing: NowPlaying = $NowPlaying @onready var players_panel: PlayersPanel = $PlayersPanel @@ -47,6 +44,7 @@ func _ready() -> void: popup_backdrop = PopupBackdrop.add_to(self, _popups()) song_controls.next_pressed.connect(play_next_song) song_controls.show_answer_pressed.connect(show_answer_pressed) + song_controls.show_winner_pressed.connect(show_final_winners) toolbar.sound_test_pressed.connect(music_player.get_sound_test_song) toolbar.search_pressed.connect(search_pressed.emit) quiz_settings_button.pressed.connect(quiz_settings_card.toggle) @@ -63,9 +61,7 @@ func _ready() -> void: music_player.play_next_song.connect(play_next_song) music_player.update_song_list.connect(update_song_list) - open_button.pressed.connect(open) - - inspiration_list.load_from_server() + inspiration_list.load_soundtracks() song_loader.load_song_list() character_select_popup.load_characters() @@ -106,7 +102,7 @@ func on_sync_finished() -> void: ## Reloads the soundtracks, playlist and characters from the server. func refresh_from_server() -> void: GameLog.debug("refresh_from_server") - inspiration_list.load_from_server() + inspiration_list.load_soundtracks() reset_playlist() character_select_popup.load_characters() @@ -127,13 +123,23 @@ func show_answer_pressed() -> void: func play_next_song() -> void: GameLog.debug("play_next_song ", Playlist.latest_played_song) + # Every song of a prepared quiz has played, e.g. when songs play on by themselves + if Settings.is_prepared_quiz() && Playlist.first_song_played \ + && Playlist.get_preloaded_songs() == 0: + song_controls.show_winner_button() + return if Playlist.first_song_played: Playlist.set_latest_played_song() Playlist.first_song_played = true - music_player.play_song_object(Playlist.latest_played_song) - if !(Playlist.get_preloaded_songs() > 0): - song_controls.set_next_enabled(false) + # The song counts as played once it starts, which can take a moment if another song + # is stopping, so the next song is picked after that + await music_player.play_song_object(Playlist.latest_played_song) song_loader.fetch_song_if_idle() + if !(Playlist.get_preloaded_songs() > 0): + if Settings.is_prepared_quiz(): + song_controls.show_winner_button() + else: + song_controls.set_next_enabled(false) # The quiz settings card is left out: it does not dim the quiz @@ -159,6 +165,10 @@ func song_object_clicked(song_no: int) -> void: GameLog.debug("Clicked Song Object: ", Playlist.song_object_array[song_no].to_string()) if !Settings.character_select_open: if Playlist.get_song(song_no) == null: + if Settings.is_offline(): + GameLog.warning("The song's file is gone: ", + Playlist.song_object_array[song_no].song_path) + return GameLog.debug("has no song, need to download") var song_downloaded: Callable = func song_downloaded(body: PackedByteArray) -> void: Playlist.add_song_to_song_object(song_no, body) @@ -174,7 +184,10 @@ func reset_playlist() -> void: var playlist_reset_done: Callable = func playlist_reset_done() -> void: Playlist.reset_playlist() song_loader.load_song_list() - + if Settings.is_offline(): + Settings.music_folder.restart() + playlist_reset_done.call() + return MusicServerClient.reset_playlist(playlist_reset_done) @@ -224,6 +237,20 @@ func _on_point_taken(player_object: PlayerObject) -> void: update_song_list() +## Shows the players with the most points as the winners, at the end of a prepared quiz. +func show_final_winners() -> void: + GameLog.debug("show_final_winners") + var top_score: int = 0 + for player: PlayerObject in Settings.player_array: + top_score = maxi(top_score, player.player_score) + var winners: Array[PlayerObject] = [] + for player: PlayerObject in Settings.player_array: + if player.player_score == top_score: + winners.append(player) + winner_popup.show_winners(winners) + music_player.play_song(preload("res://sounds/winning.mp3")) + + func _on_player_won(player_object: PlayerObject) -> void: GameLog.debug("_on_player_won") winner_popup.show_winner(player_object) @@ -249,38 +276,3 @@ func _on_change_character_requested(new_player: PlayerRow, player_name: String) func _on_new_character_selected(character_texture: ImageTexture) -> void: GameLog.debug("_on_new_character_selected") current_player_row.new_character_selected(character_texture) - - -###Local - -var local_path: String = '/Users/sebastian/ResilioSync/Sorterat_test/Metal Gear Solid 4 - Guns of the Patriots/2-16 Metal Gear Saga.mp3' - - -func open() -> void: - file_dialog.popup() - - -func _on_file_dialog_dir_selected(path: String) -> void: - GameLog.debug(path) - dir_contents(path) - - -func dir_contents(path: String) -> void: - var dir: DirAccess = DirAccess.open(path) - if dir: - dir.list_dir_begin() - var file_name: String = dir.get_next() - songs.clear() - var soundtracks: Array = [] - while file_name != "": - if dir.current_is_dir(): - #GameLog.debug("Found directory: " + file_name) - soundtracks.append(file_name) - else: - #GameLog.debug("Found file: " + file_name) - if file_name.ends_with(".mp3"): - songs.append(path + "/" + file_name) - file_name = dir.get_next() - inspiration_list.show_soundtracks(soundtracks) - else: - GameLog.warning("An error occurred when trying to access the path.") diff --git a/quiz/quiz_settings_card.gd b/quiz/quiz_settings_card.gd index 1649372..900f140 100644 --- a/quiz/quiz_settings_card.gd +++ b/quiz/quiz_settings_card.gd @@ -28,6 +28,7 @@ const PADDING: Vector2 = Vector2(16, 12) @onready var increase_hide_for_seconds_button: Button = %IncreaseHideForSecondsButton @onready var hide_length_button: CheckButton = %HideLengthCheckButton @onready var hide_ticks_button: CheckButton = %HideTicksCheckButton +@onready var winning_score_row: HBoxContainer = %WinningScoreRow @onready var score_label: Label = %ScoreLabel @onready var lower_winning_score_button: Button = %LowerButton @onready var increase_winning_score_button: Button = %IncreaseButton @@ -65,6 +66,8 @@ func show_settings() -> void: hide_for_seconds_row.visible = Settings.hide_beginning hide_for_seconds_label.text = "%d" % Settings.hide_for_seconds score_label.text = str(Settings.winning_score) + # A prepared quiz is won by having the most points when every song has played + winning_score_row.visible = !Settings.is_prepared_quiz() inspiration_speed_label.text = str(Settings.inspiration_list_speed) _fit_to_rows() diff --git a/quiz/song_controls.gd b/quiz/song_controls.gd index 26d9fe0..7153bd7 100644 --- a/quiz/song_controls.gd +++ b/quiz/song_controls.gd @@ -4,6 +4,15 @@ extends Control signal show_answer_pressed signal next_pressed +## Emitted when Next is pressed at the end of a prepared quiz, where it reads +## "Show winner". +signal show_winner_pressed + +const NEXT_TEXT: String = "Randomize new track" +const SHOW_WINNER_TEXT: String = "Show winner" + +## True while Next reads "Show winner", after the last song of a prepared quiz. +var shows_winner: bool = false @onready var show_answer_button: Button = $ShowAnswerButton @onready var next_button: Button = $NextButton @@ -11,17 +20,36 @@ signal next_pressed func _ready() -> void: show_answer_button.pressed.connect(show_answer_pressed.emit) - next_button.pressed.connect(next_pressed.emit) + next_button.pressed.connect(_on_next_button_pressed) ## Enables Next while there is a downloaded song to play next. func set_next_enabled(enabled: bool) -> void: + _set_shows_winner(false) next_button.disabled = !enabled +## Turns Next into "Show winner", for when the last song of a prepared quiz has played. +func show_winner_button() -> void: + _set_shows_winner(true) + next_button.disabled = false + + ## Disables both buttons, e.g. while the server syncs the song list. Unlocking enables ## Show answer again; Next comes back when the next song has been downloaded. func set_locked(locked: bool) -> void: show_answer_button.disabled = locked if locked: next_button.disabled = true + + +func _set_shows_winner(on: bool) -> void: + shows_winner = on + next_button.text = SHOW_WINNER_TEXT if on else NEXT_TEXT + + +func _on_next_button_pressed() -> void: + if shows_winner: + show_winner_pressed.emit() + else: + next_pressed.emit() diff --git a/quiz/song_loader.gd b/quiz/song_loader.gd index b7df69d..c47cc6e 100644 --- a/quiz/song_loader.gd +++ b/quiz/song_loader.gd @@ -1,7 +1,8 @@ class_name SongLoader extends Node ## Downloads songs from the server into the Playlist ahead of time, so the next song -## can start at once. +## can start at once. With a music folder (see [method Settings.is_offline]) the songs are +## picked from the folder instead. ## ## How many songs it keeps ready is the cache size in the settings ## ([code]Playlist.number_of_tracks_to_preload[/code]). @@ -22,7 +23,12 @@ var retry_seconds: float = 5.0 ## Preloading waits for the list, because loading the list clears the playlist. func load_song_list() -> void: GameLog.debug("load_song_list") + if Settings.is_offline(): + _start_folder_playlist() + return var song_list_fetched: Callable = func song_list_fetched(songs: Array[SongInfo]) -> void: + if Settings.is_offline(): + return Playlist.clear_song_list() for info: SongInfo in songs: var new_song_object: SongObject = SongObject.new() @@ -43,6 +49,9 @@ func load_song_list() -> void: ## Starts downloading one more song, which keeps downloading until the cache is full. func fetch_song() -> void: GameLog.debug("fetch_song") + if Settings.is_offline(): + _add_folder_songs() + return Playlist.is_song_downloading = true _download_random_song(SongObject.new()) @@ -65,6 +74,9 @@ func fill_cache() -> void: func _download_random_song(new_song_object: SongObject) -> void: var song_downloaded: Callable = func song_downloaded(body: PackedByteArray) -> void: GameLog.debug("song_downloaded") + # A music folder was picked while the song was downloading + if Settings.is_offline(): + return Playlist.store_song(new_song_object, body) _add_song_to_queue(new_song_object) MusicServerClient.fetch_random_song(Settings.use_low_played_mode, song_downloaded, @@ -79,6 +91,8 @@ func _add_song_to_queue(new_song_object: SongObject) -> void: func _fetch_song_info(new_song_object: SongObject) -> void: var song_info_fetched: Callable = func song_info_fetched(info: SongInfo) -> void: GameLog.debug("song_info_fetched") + if Settings.is_offline(): + return new_song_object.song_number = info.song_no new_song_object.soundtrack_title = info.soundtrack new_song_object.song_title = info.song @@ -95,4 +109,29 @@ func _fetch_song_info(new_song_object: SongObject) -> void: func _retry_later(step: Callable) -> void: GameLog.warning("Song download failed, retrying in %s seconds" % retry_seconds) - get_tree().create_timer(retry_seconds).timeout.connect(step) + get_tree().create_timer(retry_seconds).timeout.connect(func() -> void: + if !Settings.is_offline(): + step.call()) + + +# A music folder has no playlist on a server, so the game starts with an empty one +func _start_folder_playlist() -> void: + Playlist.clear_song_list() + Playlist.update_last_played_song() + Playlist.game_start_song = 0 + Playlist.is_song_downloading = false + song_list_loaded.emit() + fetch_song_if_idle() + + +# Picks songs from the music folder until enough are ready. A prepared quiz runs out +# when every song is in the playlist. +func _add_folder_songs() -> void: + Playlist.is_song_downloading = false + while Playlist.should_preload_more_songs(): + var song_object: SongObject = Settings.music_folder.next_song() + if song_object == null: + return + song_object.song_number = Playlist.song_object_array.size() + Playlist.add_to_playlist(song_object) + song_added.emit() diff --git a/tests/data/layout_baseline.json b/tests/data/layout_baseline.json index 1b93e88..02a676d 100644 --- a/tests/data/layout_baseline.json +++ b/tests/data/layout_baseline.json @@ -792,15 +792,6 @@ ], "visible": false }, - "Quiz/FileDialog": { - "rect": [ - 0.0, - 0.0, - 616.0, - 400.0 - ], - "visible": false - }, "Quiz/InspirationList": { "rect": [ 1216.0, @@ -1170,15 +1161,6 @@ ], "visible": true }, - "Quiz/Open": { - "rect": [ - 1296.0, - 66.0, - 50.0, - 31.0 - ], - "visible": false - }, "Quiz/PlayersPanel": { "rect": [ 1216.0, @@ -2073,34 +2055,43 @@ "SettingsPopup": { "rect": [ 700.0, - 419.0, + 405.0, 1920.0, 1080.0 ], "visible": false }, + "SettingsPopup/FolderDialog": { + "rect": [ + 0.0, + 0.0, + 800.0, + 500.0 + ], + "visible": false + }, "SettingsPopup/SettingsPanel": { "rect": [ 700.0, - 419.0, + 405.0, 520.0, - 242.0 + 269.0 ], "visible": false }, "SettingsPopup/SettingsPanel/VBoxContainer": { "rect": [ 720.0, - 431.0, + 417.0, 480.0, - 218.0 + 245.0 ], "visible": false }, "SettingsPopup/SettingsPanel/VBoxContainer/AddToDatabaseCheckButton": { "rect": [ 720.0, - 431.0, + 417.0, 231.0, 31.0 ], @@ -2109,7 +2100,7 @@ "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow": { "rect": [ 720.0, - 431.0, + 417.0, 331.0, 31.0 ], @@ -2118,7 +2109,7 @@ "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow/CacheLabel": { "rect": [ 720.0, - 431.0, + 417.0, 48.0, 23.0 ], @@ -2127,7 +2118,7 @@ "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow/CacheTitleLabel": { "rect": [ 720.0, - 431.0, + 417.0, 211.0, 31.0 ], @@ -2136,7 +2127,7 @@ "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow/IncreaseCacheButton": { "rect": [ 720.0, - 431.0, + 417.0, 30.0, 31.0 ], @@ -2145,16 +2136,70 @@ "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow/LowerCacheButton": { "rect": [ 720.0, - 431.0, + 417.0, 30.0, 31.0 ], "visible": false }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderErrorLabel": { + "rect": [ + 720.0, + 417.0, + 1.0, + 26.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow": { + "rect": [ + 720.0, + 417.0, + 195.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow/ChooseFolderButton": { + "rect": [ + 720.0, + 417.0, + 78.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow/FolderLabel": { + "rect": [ + 720.0, + 417.0, + 1.0, + 23.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow/Title": { + "rect": [ + 720.0, + 417.0, + 108.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow/UseServerButton": { + "rect": [ + 720.0, + 417.0, + 90.0, + 31.0 + ], + "visible": false + }, "SettingsPopup/SettingsPanel/VBoxContainer/FullscreenButton": { "rect": [ 720.0, - 431.0, + 417.0, 127.0, 31.0 ], @@ -2163,35 +2208,53 @@ "SettingsPopup/SettingsPanel/VBoxContainer/Label": { "rect": [ 720.0, - 431.0, + 417.0, 97.0, 35.0 ], "visible": false }, - "SettingsPopup/SettingsPanel/VBoxContainer/LocalButton": { - "rect": [ - 720.0, - 431.0, - 89.0, - 31.0 - ], - "visible": false - }, "SettingsPopup/SettingsPanel/VBoxContainer/LowPlayedCheckButton": { "rect": [ 720.0, - 431.0, + 417.0, 285.0, 31.0 ], "visible": false }, + "SettingsPopup/SettingsPanel/VBoxContainer/PlayOrderRow": { + "rect": [ + 720.0, + 417.0, + 314.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/PlayOrderRow/PlayOrderButton": { + "rect": [ + 720.0, + 417.0, + 123.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/PlayOrderRow/Title": { + "rect": [ + 720.0, + 417.0, + 187.0, + 31.0 + ], + "visible": false + }, "SettingsPopup/SettingsPanel/VBoxContainer/ServerRow": { "rect": [ 720.0, - 431.0, - 106.0, + 417.0, + 333.0, 31.0 ], "visible": false @@ -2199,8 +2262,8 @@ "SettingsPopup/SettingsPanel/VBoxContainer/ServerRow/SelectServerButton": { "rect": [ 720.0, - 431.0, - 40.0, + 417.0, + 267.0, 20.0 ], "visible": false @@ -2208,7 +2271,7 @@ "SettingsPopup/SettingsPanel/VBoxContainer/ServerRow/Title": { "rect": [ 720.0, - 431.0, + 417.0, 62.0, 31.0 ], @@ -3330,6 +3393,5498 @@ ], "visible": true }, + "TopBar/OfflineLabel": { + "rect": [ + 1096.0, + 12.0, + 336.0, + 38.0 + ], + "visible": false + }, + "TopBar/QRButton": { + "rect": [ + 1802.0, + 8.0, + 70.0, + 40.0 + ], + "visible": true + }, + "TopBar/QuizTab": { + "rect": [ + 840.0, + 8.0, + 115.0, + 40.0 + ], + "visible": true + }, + "TopBar/SettingsButton": { + "rect": [ + 1538.0, + 8.0, + 70.0, + 40.0 + ], + "visible": true + }, + "TopBar/ShortcutsButton": { + "rect": [ + 1626.0, + 8.0, + 83.0, + 40.0 + ], + "visible": true + }, + "TopBar/StatisticsTab": { + "rect": [ + 965.0, + 8.0, + 115.0, + 40.0 + ], + "visible": true + }, + "TopBar/SyncButton": { + "rect": [ + 1450.0, + 8.0, + 70.0, + 40.0 + ], + "visible": true + }, + "TopBar/Title": { + "rect": [ + 64.0, + 12.0, + 305.0, + 38.0 + ], + "visible": true + }, + "TopBar/TopPanelContainer": { + "rect": [ + 0.0, + 0.0, + 1920.0, + 56.0 + ], + "visible": true + }, + "TopBar/UpdateLabel": { + "rect": [ + 1096.0, + 12.0, + 336.0, + 38.0 + ], + "visible": false + } + }, + "offline": { + ".": { + "rect": [ + 0.0, + 0.0, + 1920.0, + 1080.0 + ], + "visible": true + }, + "AboutPopup": { + "rect": [ + 360.0, + 220.0, + 1920.0, + 1080.0 + ], + "visible": false + }, + "AboutPopup/AboutPanel": { + "rect": [ + 360.0, + 220.0, + 1200.0, + 640.0 + ], + "visible": false + }, + "AboutPopup/AboutPanel/Columns": { + "rect": [ + 380.0, + 316.0, + 1160.0, + 534.0 + ], + "visible": false + }, + "AboutPopup/AboutPanel/Columns/ScrollContainer": { + "rect": [ + 380.0, + 316.0, + 560.0, + 534.0 + ], + "visible": false + }, + "AboutPopup/AboutPanel/Columns/ScrollContainer/ContentVBox": { + "rect": [ + 380.0, + 316.0, + 552.0, + 2032.0 + ], + "visible": false + }, + "AboutPopup/AboutPanel/Columns/ScrollContainer/ContentVBox/PlayerHeader": { + "rect": [ + 380.0, + 316.0, + 552.0, + 28.0 + ], + "visible": false + }, + "AboutPopup/AboutPanel/Columns/ScrollContainer/ContentVBox/VersionLabel": { + "rect": [ + 380.0, + 348.0, + 552.0, + 23.0 + ], + "visible": false + }, + "AboutPopup/AboutPanel/Columns/ScrollContainer/ContentVBox/WhatsNewLabel": { + "rect": [ + 380.0, + 375.0, + 552.0, + 1973.0 + ], + "visible": false + }, + "AboutPopup/AboutPanel/Columns/ServerScrollContainer": { + "rect": [ + 980.0, + 316.0, + 560.0, + 534.0 + ], + "visible": false + }, + "AboutPopup/AboutPanel/Columns/ServerScrollContainer/ServerContentVBox": { + "rect": [ + 980.0, + 316.0, + 560.0, + 534.0 + ], + "visible": false + }, + "AboutPopup/AboutPanel/Columns/ServerScrollContainer/ServerContentVBox/ServerHeader": { + "rect": [ + 980.0, + 316.0, + 560.0, + 28.0 + ], + "visible": false + }, + "AboutPopup/AboutPanel/Columns/ServerScrollContainer/ServerContentVBox/ServerHistoryLabel": { + "rect": [ + 980.0, + 375.0, + 560.0, + 309.0 + ], + "visible": false + }, + "AboutPopup/AboutPanel/Columns/ServerScrollContainer/ServerContentVBox/ServerVersionLabel": { + "rect": [ + 980.0, + 348.0, + 560.0, + 23.0 + ], + "visible": false + }, + "AboutPopup/AboutPanel/DescriptionLabel": { + "rect": [ + 380.0, + 270.0, + 1160.0, + 30.0 + ], + "visible": false + }, + "AboutPopup/AboutPanel/TitleLabel": { + "rect": [ + 360.0, + 220.0, + 1200.0, + 50.0 + ], + "visible": false + }, + "Background": { + "rect": [ + 0.0, + 0.0, + 1920.0, + 1080.0 + ], + "visible": true + }, + "LogPopup": { + "rect": [ + 610.0, + 190.0, + 2620.0, + 1780.0 + ], + "visible": false + }, + "LogPopup/LogPanel": { + "rect": [ + 610.0, + 190.0, + 700.0, + 700.0 + ], + "visible": false + }, + "LogPopup/LogPanel/EmptyLabel": { + "rect": [ + 630.0, + 270.0, + 660.0, + 30.0 + ], + "visible": false + }, + "LogPopup/LogPanel/LogScrollContainer": { + "rect": [ + 630.0, + 246.0, + 660.0, + 628.0 + ], + "visible": false + }, + "LogPopup/LogPanel/LogScrollContainer/LogVBoxContainer": { + "rect": [ + 630.0, + 246.0, + 660.0, + 98.0 + ], + "visible": false + }, + "LogPopup/LogPanel/LogScrollContainer/LogVBoxContainer/#0": { + "rect": [ + 630.0, + 246.0, + 660.0, + 32.0 + ], + "visible": false + }, + "LogPopup/LogPanel/LogScrollContainer/LogVBoxContainer/#0/#0": { + "rect": [ + 638.0, + 248.0, + 644.0, + 28.0 + ], + "visible": false + }, + "LogPopup/LogPanel/LogScrollContainer/LogVBoxContainer/#1": { + "rect": [ + 630.0, + 279.0, + 660.0, + 32.0 + ], + "visible": false + }, + "LogPopup/LogPanel/LogScrollContainer/LogVBoxContainer/#1/#0": { + "rect": [ + 638.0, + 281.0, + 644.0, + 28.0 + ], + "visible": false + }, + "LogPopup/LogPanel/LogScrollContainer/LogVBoxContainer/#2": { + "rect": [ + 630.0, + 312.0, + 660.0, + 32.0 + ], + "visible": false + }, + "LogPopup/LogPanel/LogScrollContainer/LogVBoxContainer/#2/#0": { + "rect": [ + 638.0, + 314.0, + 644.0, + 28.0 + ], + "visible": false + }, + "LogPopup/LogPanel/TitleLabel": { + "rect": [ + 610.0, + 198.0, + 700.0, + 40.0 + ], + "visible": false + }, + "PopupBackdrop": { + "rect": [ + 0.0, + 0.0, + 1920.0, + 1080.0 + ], + "visible": false + }, + "QRPopup": { + "rect": [ + 735.0, + 270.0, + 1920.0, + 1080.0 + ], + "visible": false + }, + "QRPopup/QRPanel": { + "rect": [ + 735.0, + 270.0, + 450.0, + 540.0 + ], + "visible": false + }, + "QRPopup/QRPanel/QRCodeRect": { + "rect": [ + 755.0, + 330.0, + 410.0, + 410.0 + ], + "visible": false + }, + "QRPopup/QRPanel/TitleLabel": { + "rect": [ + 735.0, + 278.0, + 450.0, + 48.0 + ], + "visible": false + }, + "QRPopup/QRPanel/URLLabel": { + "rect": [ + 755.0, + 752.0, + 410.0, + 40.0 + ], + "visible": false + }, + "Quiz": { + "rect": [ + 0.0, + 0.0, + 1920.0, + 1080.0 + ], + "visible": true + }, + "Quiz/CharacterSelectPopup": { + "rect": [ + 170.0, + 180.0, + 1580.0, + 720.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel": { + "rect": [ + 170.0, + 180.0, + 1580.0, + 720.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer": { + "rect": [ + 194.0, + 240.0, + 1540.0, + 644.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap": { + "rect": [ + 194.0, + 240.0, + 1532.0, + 474.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer": { + "rect": [ + 194.0, + 240.0, + 1500.0, + 474.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#1": { + "rect": [ + 194.0, + 240.0, + 240.0, + 150.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#1/VBoxContainer": { + "rect": [ + 204.0, + 250.0, + 220.0, + 134.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#1/VBoxContainer/CharacterName": { + "rect": [ + 204.0, + 250.0, + 1.0, + 23.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#1/VBoxContainer/CharacterTextureRect": { + "rect": [ + 204.0, + 250.0, + 200.0, + 100.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#10": { + "rect": [ + 194.0, + 240.0, + 240.0, + 150.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#10/VBoxContainer": { + "rect": [ + 204.0, + 250.0, + 220.0, + 134.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#10/VBoxContainer/CharacterName": { + "rect": [ + 204.0, + 250.0, + 1.0, + 23.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#10/VBoxContainer/CharacterTextureRect": { + "rect": [ + 204.0, + 250.0, + 200.0, + 100.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#11": { + "rect": [ + 194.0, + 240.0, + 240.0, + 150.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#11/VBoxContainer": { + "rect": [ + 204.0, + 250.0, + 220.0, + 134.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#11/VBoxContainer/CharacterName": { + "rect": [ + 204.0, + 250.0, + 1.0, + 23.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#11/VBoxContainer/CharacterTextureRect": { + "rect": [ + 204.0, + 250.0, + 200.0, + 100.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#12": { + "rect": [ + 194.0, + 240.0, + 240.0, + 150.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#12/VBoxContainer": { + "rect": [ + 204.0, + 250.0, + 220.0, + 134.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#12/VBoxContainer/CharacterName": { + "rect": [ + 204.0, + 250.0, + 1.0, + 23.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#12/VBoxContainer/CharacterTextureRect": { + "rect": [ + 204.0, + 250.0, + 200.0, + 100.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#2": { + "rect": [ + 194.0, + 240.0, + 240.0, + 150.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#2/VBoxContainer": { + "rect": [ + 204.0, + 250.0, + 220.0, + 134.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#2/VBoxContainer/CharacterName": { + "rect": [ + 204.0, + 250.0, + 1.0, + 23.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#2/VBoxContainer/CharacterTextureRect": { + "rect": [ + 204.0, + 250.0, + 200.0, + 100.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#3": { + "rect": [ + 194.0, + 240.0, + 240.0, + 150.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#3/VBoxContainer": { + "rect": [ + 204.0, + 250.0, + 220.0, + 134.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#3/VBoxContainer/CharacterName": { + "rect": [ + 204.0, + 250.0, + 1.0, + 23.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#3/VBoxContainer/CharacterTextureRect": { + "rect": [ + 204.0, + 250.0, + 200.0, + 100.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#4": { + "rect": [ + 194.0, + 240.0, + 240.0, + 150.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#4/VBoxContainer": { + "rect": [ + 204.0, + 250.0, + 220.0, + 134.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#4/VBoxContainer/CharacterName": { + "rect": [ + 204.0, + 250.0, + 1.0, + 23.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#4/VBoxContainer/CharacterTextureRect": { + "rect": [ + 204.0, + 250.0, + 200.0, + 100.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#5": { + "rect": [ + 194.0, + 240.0, + 240.0, + 150.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#5/VBoxContainer": { + "rect": [ + 204.0, + 250.0, + 220.0, + 134.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#5/VBoxContainer/CharacterName": { + "rect": [ + 204.0, + 250.0, + 1.0, + 23.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#5/VBoxContainer/CharacterTextureRect": { + "rect": [ + 204.0, + 250.0, + 200.0, + 100.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#6": { + "rect": [ + 194.0, + 240.0, + 240.0, + 150.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#6/VBoxContainer": { + "rect": [ + 204.0, + 250.0, + 220.0, + 134.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#6/VBoxContainer/CharacterName": { + "rect": [ + 204.0, + 250.0, + 1.0, + 23.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#6/VBoxContainer/CharacterTextureRect": { + "rect": [ + 204.0, + 250.0, + 200.0, + 100.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#7": { + "rect": [ + 194.0, + 240.0, + 240.0, + 150.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#7/VBoxContainer": { + "rect": [ + 204.0, + 250.0, + 220.0, + 134.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#7/VBoxContainer/CharacterName": { + "rect": [ + 204.0, + 250.0, + 1.0, + 23.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#7/VBoxContainer/CharacterTextureRect": { + "rect": [ + 204.0, + 250.0, + 200.0, + 100.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#8": { + "rect": [ + 194.0, + 240.0, + 240.0, + 150.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#8/VBoxContainer": { + "rect": [ + 204.0, + 250.0, + 220.0, + 134.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#8/VBoxContainer/CharacterName": { + "rect": [ + 204.0, + 250.0, + 1.0, + 23.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#8/VBoxContainer/CharacterTextureRect": { + "rect": [ + 204.0, + 250.0, + 200.0, + 100.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#9": { + "rect": [ + 194.0, + 240.0, + 240.0, + 150.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#9/VBoxContainer": { + "rect": [ + 204.0, + 250.0, + 220.0, + 134.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#9/VBoxContainer/CharacterName": { + "rect": [ + 204.0, + 250.0, + 1.0, + 23.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/#9/VBoxContainer/CharacterTextureRect": { + "rect": [ + 204.0, + 250.0, + 200.0, + 100.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/CharacterCard": { + "rect": [ + 194.0, + 240.0, + 240.0, + 150.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/CharacterCard/VBoxContainer": { + "rect": [ + 204.0, + 250.0, + 220.0, + 134.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/CharacterCard/VBoxContainer/CharacterName": { + "rect": [ + 204.0, + 250.0, + 1.0, + 23.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/ScrollContainer/ScrollbarGap/CharacterGridContainer/CharacterCard/VBoxContainer/CharacterTextureRect": { + "rect": [ + 204.0, + 250.0, + 200.0, + 100.0 + ], + "visible": false + }, + "Quiz/CharacterSelectPopup/CharacterPanel/TitleLabel": { + "rect": [ + 170.0, + 180.0, + 1580.0, + 56.0 + ], + "visible": false + }, + "Quiz/InspirationList": { + "rect": [ + 1216.0, + 128.0, + 641.0, + 384.0 + ], + "visible": true + }, + "Quiz/InspirationList/VBoxContainer": { + "rect": [ + 1216.0, + 128.0, + 641.0, + 384.0 + ], + "visible": true + }, + "Quiz/InspirationList/VBoxContainer/#0": { + "rect": [ + 1216.0, + 128.0, + 641.0, + 32.0 + ], + "visible": true + }, + "Quiz/InspirationList/VBoxContainer/#0/#0": { + "rect": [ + 1224.0, + 130.0, + 625.0, + 28.0 + ], + "visible": true + }, + "Quiz/InspirationList/VBoxContainer/#1": { + "rect": [ + 1216.0, + 164.0, + 641.0, + 32.0 + ], + "visible": true + }, + "Quiz/InspirationList/VBoxContainer/#1/#0": { + "rect": [ + 1224.0, + 166.0, + 625.0, + 28.0 + ], + "visible": true + }, + "Quiz/InspirationList/VBoxContainer/#2": { + "rect": [ + 1216.0, + 200.0, + 641.0, + 32.0 + ], + "visible": true + }, + "Quiz/InspirationList/VBoxContainer/#2/#0": { + "rect": [ + 1224.0, + 202.0, + 625.0, + 28.0 + ], + "visible": true + }, + "Quiz/MusicListPanel": { + "rect": [ + 64.0, + 384.0, + 1088.0, + 512.0 + ], + "visible": true + }, + "Quiz/MusicListPanel/ScrollContainer": { + "rect": [ + 64.0, + 384.0, + 1088.0, + 512.0 + ], + "visible": true + }, + "Quiz/MusicListPanel/ScrollContainer/MusicList": { + "rect": [ + 64.0, + 384.0, + 1088.0, + 512.0 + ], + "visible": true + }, + "Quiz/MusicListPanel/ScrollContainer/MusicList/#1": { + "rect": [ + 64.0, + 422.0, + 1088.0, + 28.0 + ], + "visible": true + }, + "Quiz/MusicListPanel/ScrollContainer/MusicList/#1/HBoxContainer": { + "rect": [ + 64.0, + 422.0, + 1088.0, + 28.0 + ], + "visible": true + }, + "Quiz/MusicListPanel/ScrollContainer/MusicList/#1/HBoxContainer/Panel": { + "rect": [ + 1068.0, + 422.0, + 80.0, + 0.0 + ], + "visible": true + }, + "Quiz/MusicListPanel/ScrollContainer/MusicList/#1/HBoxContainer/SongListLabel": { + "rect": [ + 64.0, + 422.0, + 1000.0, + 28.0 + ], + "visible": true + }, + "Quiz/MusicListPanel/ScrollContainer/MusicList/#1/HBoxContainer/SongListPlayers": { + "rect": [ + 1152.0, + 422.0, + 0.0, + 0.0 + ], + "visible": true + }, + "Quiz/MusicListPanel/ScrollContainer/MusicList/SongRow": { + "rect": [ + 64.0, + 384.0, + 1088.0, + 28.0 + ], + "visible": true + }, + "Quiz/MusicListPanel/ScrollContainer/MusicList/SongRow/HBoxContainer": { + "rect": [ + 64.0, + 384.0, + 1088.0, + 28.0 + ], + "visible": true + }, + "Quiz/MusicListPanel/ScrollContainer/MusicList/SongRow/HBoxContainer/Panel": { + "rect": [ + 1068.0, + 384.0, + 80.0, + 0.0 + ], + "visible": true + }, + "Quiz/MusicListPanel/ScrollContainer/MusicList/SongRow/HBoxContainer/SongListLabel": { + "rect": [ + 64.0, + 384.0, + 1000.0, + 28.0 + ], + "visible": true + }, + "Quiz/MusicListPanel/ScrollContainer/MusicList/SongRow/HBoxContainer/SongListPlayers": { + "rect": [ + 1152.0, + 384.0, + 0.0, + 0.0 + ], + "visible": true + }, + "Quiz/MusicPlayer": { + "rect": [ + 64.0, + 992.0, + 1792.0, + 71.0 + ], + "visible": true + }, + "Quiz/MusicPlayer/PlayerBar": { + "rect": [ + 65.0, + 992.0, + 1791.0, + 32.0 + ], + "visible": true + }, + "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer": { + "rect": [ + 65.0, + 992.0, + 1791.0, + 32.0 + ], + "visible": true + }, + "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/MusicPlayerSlider": { + "rect": [ + 137.0, + 1000.0, + 1514.0, + 16.0 + ], + "visible": true + }, + "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/MusicTimeLabel": { + "rect": [ + 1655.0, + 996.0, + 97.0, + 23.0 + ], + "visible": true + }, + "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/PlayTextureButton": { + "rect": [ + 65.0, + 992.0, + 32.0, + 32.0 + ], + "visible": true + }, + "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/RestartTextureButton": { + "rect": [ + 101.0, + 992.0, + 32.0, + 32.0 + ], + "visible": true + }, + "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/VolumeSlider": { + "rect": [ + 1756.0, + 1000.0, + 100.0, + 16.0 + ], + "visible": true + }, + "Quiz/NowPlaying": { + "rect": [ + 64.0, + 64.0, + 1088.0, + 320.0 + ], + "visible": true + }, + "Quiz/NowPlaying/SongLabel": { + "rect": [ + 64.0, + 137.0, + 1088.0, + 49.0 + ], + "visible": true + }, + "Quiz/NowPlaying/SoundtrackLabel": { + "rect": [ + 64.0, + 64.0, + 1088.0, + 69.0 + ], + "visible": true + }, + "Quiz/PlayersPanel": { + "rect": [ + 1216.0, + 576.0, + 641.0, + 401.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer": { + "rect": [ + 1216.0, + 576.0, + 641.0, + 401.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/AddPlayerContainer": { + "rect": [ + 1216.0, + 620.0, + 641.0, + 40.0 + ], + "visible": false + }, + "Quiz/PlayersPanel/VBoxContainer/AddPlayerContainer/AddPlayerButton": { + "rect": [ + 1617.0, + 620.0, + 39.0, + 40.0 + ], + "visible": false + }, + "Quiz/PlayersPanel/VBoxContainer/AddPlayerContainer/Panel": { + "rect": [ + 1216.0, + 620.0, + 196.0, + 40.0 + ], + "visible": false + }, + "Quiz/PlayersPanel/VBoxContainer/AddPlayerContainer/Panel2": { + "rect": [ + 1660.0, + 620.0, + 197.0, + 40.0 + ], + "visible": false + }, + "Quiz/PlayersPanel/VBoxContainer/AddPlayerContainer/PlayerNameField": { + "rect": [ + 1416.0, + 620.0, + 197.0, + 40.0 + ], + "visible": false + }, + "Quiz/PlayersPanel/VBoxContainer/HBoxContainer": { + "rect": [ + 1216.0, + 576.0, + 641.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/HBoxContainer/AddPlayersButton": { + "rect": [ + 1821.0, + 576.0, + 36.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/HBoxContainer/Label": { + "rect": [ + 1216.0, + 576.0, + 601.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer": { + "rect": [ + 1216.0, + 620.0, + 641.0, + 357.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer": { + "rect": [ + 1216.0, + 620.0, + 641.0, + 357.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#1": { + "rect": [ + 1216.0, + 664.0, + 641.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#1/HBoxContainer": { + "rect": [ + 1216.0, + 664.0, + 641.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#1/HBoxContainer/AddPoint": { + "rect": [ + 1732.0, + 664.0, + 40.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#1/HBoxContainer/Character": { + "rect": [ + 1614.0, + 664.0, + 80.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#1/HBoxContainer/Name": { + "rect": [ + 1225.0, + 664.0, + 365.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#1/HBoxContainer/Panel": { + "rect": [ + 1827.0, + 664.0, + 30.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#1/HBoxContainer/Panel2": { + "rect": [ + 1698.0, + 664.0, + 30.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#1/HBoxContainer/Panel3": { + "rect": [ + 1594.0, + 664.0, + 16.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#1/HBoxContainer/Panel4": { + "rect": [ + 1216.0, + 664.0, + 5.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#1/HBoxContainer/Panel5": { + "rect": [ + 1776.0, + 664.0, + 3.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#1/HBoxContainer/RemovePlayer": { + "rect": [ + 1817.0, + 664.0, + 40.0, + 40.0 + ], + "visible": false + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#1/HBoxContainer/RemovePoint": { + "rect": [ + 1783.0, + 664.0, + 40.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#2": { + "rect": [ + 1216.0, + 708.0, + 641.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#2/HBoxContainer": { + "rect": [ + 1216.0, + 708.0, + 641.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#2/HBoxContainer/AddPoint": { + "rect": [ + 1732.0, + 708.0, + 40.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#2/HBoxContainer/Character": { + "rect": [ + 1614.0, + 708.0, + 80.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#2/HBoxContainer/Name": { + "rect": [ + 1225.0, + 708.0, + 365.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#2/HBoxContainer/Panel": { + "rect": [ + 1827.0, + 708.0, + 30.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#2/HBoxContainer/Panel2": { + "rect": [ + 1698.0, + 708.0, + 30.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#2/HBoxContainer/Panel3": { + "rect": [ + 1594.0, + 708.0, + 16.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#2/HBoxContainer/Panel4": { + "rect": [ + 1216.0, + 708.0, + 5.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#2/HBoxContainer/Panel5": { + "rect": [ + 1776.0, + 708.0, + 3.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#2/HBoxContainer/RemovePlayer": { + "rect": [ + 1817.0, + 708.0, + 40.0, + 40.0 + ], + "visible": false + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#2/HBoxContainer/RemovePoint": { + "rect": [ + 1783.0, + 708.0, + 40.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#3": { + "rect": [ + 1216.0, + 752.0, + 641.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#3/HBoxContainer": { + "rect": [ + 1216.0, + 752.0, + 641.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#3/HBoxContainer/AddPoint": { + "rect": [ + 1732.0, + 752.0, + 40.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#3/HBoxContainer/Character": { + "rect": [ + 1614.0, + 752.0, + 80.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#3/HBoxContainer/Name": { + "rect": [ + 1225.0, + 752.0, + 365.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#3/HBoxContainer/Panel": { + "rect": [ + 1827.0, + 752.0, + 30.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#3/HBoxContainer/Panel2": { + "rect": [ + 1698.0, + 752.0, + 30.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#3/HBoxContainer/Panel3": { + "rect": [ + 1594.0, + 752.0, + 16.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#3/HBoxContainer/Panel4": { + "rect": [ + 1216.0, + 752.0, + 5.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#3/HBoxContainer/Panel5": { + "rect": [ + 1776.0, + 752.0, + 3.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#3/HBoxContainer/RemovePlayer": { + "rect": [ + 1817.0, + 752.0, + 40.0, + 40.0 + ], + "visible": false + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#3/HBoxContainer/RemovePoint": { + "rect": [ + 1783.0, + 752.0, + 40.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#4": { + "rect": [ + 1216.0, + 796.0, + 641.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#4/HBoxContainer": { + "rect": [ + 1216.0, + 796.0, + 641.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#4/HBoxContainer/AddPoint": { + "rect": [ + 1732.0, + 796.0, + 40.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#4/HBoxContainer/Character": { + "rect": [ + 1614.0, + 796.0, + 80.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#4/HBoxContainer/Name": { + "rect": [ + 1225.0, + 796.0, + 365.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#4/HBoxContainer/Panel": { + "rect": [ + 1827.0, + 796.0, + 30.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#4/HBoxContainer/Panel2": { + "rect": [ + 1698.0, + 796.0, + 30.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#4/HBoxContainer/Panel3": { + "rect": [ + 1594.0, + 796.0, + 16.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#4/HBoxContainer/Panel4": { + "rect": [ + 1216.0, + 796.0, + 5.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#4/HBoxContainer/Panel5": { + "rect": [ + 1776.0, + 796.0, + 3.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#4/HBoxContainer/RemovePlayer": { + "rect": [ + 1817.0, + 796.0, + 40.0, + 40.0 + ], + "visible": false + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/#4/HBoxContainer/RemovePoint": { + "rect": [ + 1783.0, + 796.0, + 40.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/PlayerRow": { + "rect": [ + 1216.0, + 620.0, + 641.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/PlayerRow/HBoxContainer": { + "rect": [ + 1216.0, + 620.0, + 641.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/PlayerRow/HBoxContainer/AddPoint": { + "rect": [ + 1732.0, + 620.0, + 40.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/PlayerRow/HBoxContainer/Character": { + "rect": [ + 1614.0, + 620.0, + 80.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/PlayerRow/HBoxContainer/Name": { + "rect": [ + 1225.0, + 620.0, + 365.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/PlayerRow/HBoxContainer/Panel": { + "rect": [ + 1827.0, + 620.0, + 30.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/PlayerRow/HBoxContainer/Panel2": { + "rect": [ + 1698.0, + 620.0, + 30.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/PlayerRow/HBoxContainer/Panel3": { + "rect": [ + 1594.0, + 620.0, + 16.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/PlayerRow/HBoxContainer/Panel4": { + "rect": [ + 1216.0, + 620.0, + 5.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/PlayerRow/HBoxContainer/Panel5": { + "rect": [ + 1776.0, + 620.0, + 3.0, + 40.0 + ], + "visible": true + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/PlayerRow/HBoxContainer/RemovePlayer": { + "rect": [ + 1817.0, + 620.0, + 40.0, + 40.0 + ], + "visible": false + }, + "Quiz/PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer/PlayerRow/HBoxContainer/RemovePoint": { + "rect": [ + 1783.0, + 620.0, + 40.0, + 40.0 + ], + "visible": true + }, + "Quiz/PopupBackdrop": { + "rect": [ + 0.0, + 0.0, + 1920.0, + 1080.0 + ], + "visible": false + }, + "Quiz/QuizSettingsButton": { + "rect": [ + 64.0, + 1030.0, + 160.0, + 36.0 + ], + "visible": true + }, + "Quiz/QuizSettingsCard": { + "rect": [ + 64.0, + 984.0, + 0.0, + 0.0 + ], + "visible": false + }, + "Quiz/QuizSettingsCard/CardPanel": { + "rect": [ + 64.0, + 645.0, + 440.0, + 339.0 + ], + "visible": false + }, + "Quiz/QuizSettingsCard/CardPanel/VBox": { + "rect": [ + 80.0, + 657.0, + 408.0, + 315.0 + ], + "visible": false + }, + "Quiz/QuizSettingsCard/CardPanel/VBox/AutoRepeatCheckButton": { + "rect": [ + 80.0, + 696.0, + 408.0, + 31.0 + ], + "visible": false + }, + "Quiz/QuizSettingsCard/CardPanel/VBox/HideBeginningCheckButton": { + "rect": [ + 80.0, + 801.0, + 408.0, + 31.0 + ], + "visible": false + }, + "Quiz/QuizSettingsCard/CardPanel/VBox/HideForSecondsRow": { + "rect": [ + 80.0, + 657.0, + 254.0, + 31.0 + ], + "visible": false + }, + "Quiz/QuizSettingsCard/CardPanel/VBox/HideForSecondsRow/HideForSecondsLabel": { + "rect": [ + 80.0, + 657.0, + 48.0, + 23.0 + ], + "visible": false + }, + "Quiz/QuizSettingsCard/CardPanel/VBox/HideForSecondsRow/IncreaseHideForSecondsButton": { + "rect": [ + 80.0, + 657.0, + 30.0, + 31.0 + ], + "visible": false + }, + "Quiz/QuizSettingsCard/CardPanel/VBox/HideForSecondsRow/LowerHideForSecondsButton": { + "rect": [ + 80.0, + 657.0, + 30.0, + 31.0 + ], + "visible": false + }, + "Quiz/QuizSettingsCard/CardPanel/VBox/HideForSecondsRow/Title": { + "rect": [ + 80.0, + 657.0, + 134.0, + 31.0 + ], + "visible": false + }, + "Quiz/QuizSettingsCard/CardPanel/VBox/HideLengthCheckButton": { + "rect": [ + 80.0, + 836.0, + 408.0, + 31.0 + ], + "visible": false + }, + "Quiz/QuizSettingsCard/CardPanel/VBox/HideNextTrackCheckButton": { + "rect": [ + 80.0, + 766.0, + 408.0, + 31.0 + ], + "visible": false + }, + "Quiz/QuizSettingsCard/CardPanel/VBox/HideTicksCheckButton": { + "rect": [ + 80.0, + 871.0, + 408.0, + 31.0 + ], + "visible": false + }, + "Quiz/QuizSettingsCard/CardPanel/VBox/InspirationRow": { + "rect": [ + 80.0, + 941.0, + 408.0, + 31.0 + ], + "visible": false + }, + "Quiz/QuizSettingsCard/CardPanel/VBox/InspirationRow/IncreaseInspirationSpeedButton": { + "rect": [ + 458.0, + 941.0, + 30.0, + 31.0 + ], + "visible": false + }, + "Quiz/QuizSettingsCard/CardPanel/VBox/InspirationRow/InspirationListSpeedLabel": { + "rect": [ + 372.0, + 945.0, + 48.0, + 23.0 + ], + "visible": false + }, + "Quiz/QuizSettingsCard/CardPanel/VBox/InspirationRow/LowerInspirationSpeedButton": { + "rect": [ + 424.0, + 941.0, + 30.0, + 31.0 + ], + "visible": false + }, + "Quiz/QuizSettingsCard/CardPanel/VBox/InspirationRow/Title": { + "rect": [ + 80.0, + 941.0, + 288.0, + 31.0 + ], + "visible": false + }, + "Quiz/QuizSettingsCard/CardPanel/VBox/StopAfterCurrentCheckButton": { + "rect": [ + 80.0, + 731.0, + 408.0, + 31.0 + ], + "visible": false + }, + "Quiz/QuizSettingsCard/CardPanel/VBox/TitleLabel": { + "rect": [ + 80.0, + 657.0, + 408.0, + 35.0 + ], + "visible": false + }, + "Quiz/QuizSettingsCard/CardPanel/VBox/WinningScoreRow": { + "rect": [ + 80.0, + 906.0, + 408.0, + 31.0 + ], + "visible": false + }, + "Quiz/QuizSettingsCard/CardPanel/VBox/WinningScoreRow/IncreaseButton": { + "rect": [ + 458.0, + 906.0, + 30.0, + 31.0 + ], + "visible": false + }, + "Quiz/QuizSettingsCard/CardPanel/VBox/WinningScoreRow/LowerButton": { + "rect": [ + 424.0, + 906.0, + 30.0, + 31.0 + ], + "visible": false + }, + "Quiz/QuizSettingsCard/CardPanel/VBox/WinningScoreRow/ScoreLabel": { + "rect": [ + 372.0, + 910.0, + 48.0, + 23.0 + ], + "visible": false + }, + "Quiz/QuizSettingsCard/CardPanel/VBox/WinningScoreRow/Title": { + "rect": [ + 80.0, + 906.0, + 288.0, + 31.0 + ], + "visible": false + }, + "Quiz/QuizToolbar": { + "rect": [ + 1360.0, + 64.0, + 495.0, + 31.0 + ], + "visible": true + }, + "Quiz/QuizToolbar/ResetPlaylistButton": { + "rect": [ + 1488.0, + 64.0, + 111.0, + 31.0 + ], + "visible": true + }, + "Quiz/QuizToolbar/ResetPointsButton": { + "rect": [ + 1616.0, + 64.0, + 111.0, + 31.0 + ], + "visible": true + }, + "Quiz/QuizToolbar/SearchButton": { + "rect": [ + 1360.0, + 64.0, + 111.0, + 31.0 + ], + "visible": true + }, + "Quiz/QuizToolbar/SoundTestButton": { + "rect": [ + 1744.0, + 64.0, + 111.0, + 31.0 + ], + "visible": true + }, + "Quiz/SongControls": { + "rect": [ + 65.0, + 944.0, + 303.0, + 31.0 + ], + "visible": true + }, + "Quiz/SongControls/NextButton": { + "rect": [ + 193.0, + 944.0, + 175.0, + 31.0 + ], + "visible": true + }, + "Quiz/SongControls/ShowAnswerButton": { + "rect": [ + 65.0, + 944.0, + 111.0, + 31.0 + ], + "visible": true + }, + "Quiz/WinnerPopup": { + "rect": [ + 160.0, + 80.0, + 1920.0, + 1080.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel": { + "rect": [ + 160.0, + 80.0, + 1600.0, + 920.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn": { + "rect": [ + 184.0, + 100.0, + 1552.0, + 880.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Header": { + "rect": [ + 184.0, + 100.0, + 1552.0, + 180.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Header/WinnerInfo": { + "rect": [ + 896.0, + 100.0, + 520.0, + 180.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Header/WinnerInfo/PlayersList": { + "rect": [ + 896.0, + 164.0, + 520.0, + 104.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Header/WinnerInfo/PlayersList/#0": { + "rect": [ + 896.0, + 164.0, + 520.0, + 23.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Header/WinnerInfo/PlayersList/#1": { + "rect": [ + 896.0, + 191.0, + 520.0, + 23.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Header/WinnerInfo/PlayersList/#2": { + "rect": [ + 896.0, + 218.0, + 520.0, + 23.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Header/WinnerInfo/PlayersList/#3": { + "rect": [ + 896.0, + 245.0, + 520.0, + 23.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Header/WinnerInfo/WinnerLabel": { + "rect": [ + 896.0, + 111.0, + 520.0, + 49.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Header/WinnerPicture": { + "rect": [ + 504.0, + 100.0, + 360.0, + 180.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Legend": { + "rect": [ + 184.0, + 292.0, + 1552.0, + 23.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Legend/#0": { + "rect": [ + 712.0, + 292.0, + 88.0, + 23.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Legend/#0/#0": { + "rect": [ + 712.0, + 296.0, + 14.0, + 14.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Legend/#0/#1": { + "rect": [ + 730.0, + 292.0, + 70.0, + 23.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Legend/#1": { + "rect": [ + 820.0, + 292.0, + 86.0, + 23.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Legend/#1/#0": { + "rect": [ + 820.0, + 296.0, + 14.0, + 14.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Legend/#1/#1": { + "rect": [ + 838.0, + 292.0, + 68.0, + 23.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Legend/#2": { + "rect": [ + 926.0, + 292.0, + 85.0, + 23.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Legend/#2/#0": { + "rect": [ + 926.0, + 296.0, + 14.0, + 14.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Legend/#2/#1": { + "rect": [ + 944.0, + 292.0, + 67.0, + 23.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Legend/#3": { + "rect": [ + 1031.0, + 292.0, + 75.0, + 23.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Legend/#3/#0": { + "rect": [ + 1031.0, + 296.0, + 14.0, + 14.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Legend/#3/#1": { + "rect": [ + 1049.0, + 292.0, + 57.0, + 23.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Legend/#4": { + "rect": [ + 1126.0, + 292.0, + 81.0, + 23.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Legend/#4/#0": { + "rect": [ + 1126.0, + 296.0, + 14.0, + 14.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Legend/#4/#1": { + "rect": [ + 1144.0, + 292.0, + 63.0, + 23.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/ScoreGraph": { + "rect": [ + 184.0, + 327.0, + 1552.0, + 557.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Summary": { + "rect": [ + 184.0, + 896.0, + 1552.0, + 84.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Summary/SharedBox": { + "rect": [ + 1226.0, + 896.0, + 510.0, + 84.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Summary/SharedBox/VBox": { + "rect": [ + 1234.0, + 898.0, + 494.0, + 80.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Summary/SharedBox/VBox/Header": { + "rect": [ + 1234.0, + 898.0, + 494.0, + 26.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Summary/SharedBox/VBox/SharedLabel": { + "rect": [ + 1234.0, + 928.0, + 494.0, + 23.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Summary/StandstillBox": { + "rect": [ + 705.0, + 896.0, + 509.0, + 84.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Summary/StandstillBox/VBox": { + "rect": [ + 713.0, + 898.0, + 493.0, + 80.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Summary/StandstillBox/VBox/Header": { + "rect": [ + 713.0, + 898.0, + 493.0, + 26.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Summary/StandstillBox/VBox/StandstillLabel": { + "rect": [ + 713.0, + 928.0, + 493.0, + 23.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Summary/StreakBox": { + "rect": [ + 184.0, + 896.0, + 509.0, + 84.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Summary/StreakBox/VBox": { + "rect": [ + 192.0, + 898.0, + 493.0, + 80.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Summary/StreakBox/VBox/Header": { + "rect": [ + 192.0, + 898.0, + 493.0, + 26.0 + ], + "visible": false + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Summary/StreakBox/VBox/StreakLabel": { + "rect": [ + 192.0, + 928.0, + 493.0, + 23.0 + ], + "visible": false + }, + "SearchPopup": { + "rect": [ + 384.0, + 100.0, + 1920.0, + 1080.0 + ], + "visible": false + }, + "SearchPopup/SearchPanel": { + "rect": [ + 384.0, + 100.0, + 1152.0, + 880.0 + ], + "visible": false + }, + "SearchPopup/SearchPanel/CountLabel": { + "rect": [ + 408.0, + 190.0, + 1104.0, + 26.0 + ], + "visible": false + }, + "SearchPopup/SearchPanel/EmptyLabel": { + "rect": [ + 408.0, + 260.0, + 1104.0, + 40.0 + ], + "visible": false + }, + "SearchPopup/SearchPanel/ScrollContainer": { + "rect": [ + 408.0, + 224.0, + 1104.0, + 732.0 + ], + "visible": false + }, + "SearchPopup/SearchPanel/ScrollContainer/SearchResults": { + "rect": [ + 408.0, + 224.0, + 1104.0, + 386.0 + ], + "visible": false + }, + "SearchPopup/SearchPanel/ScrollContainer/SearchResults/#0": { + "rect": [ + 408.0, + 224.0, + 1104.0, + 35.0 + ], + "visible": false + }, + "SearchPopup/SearchPanel/ScrollContainer/SearchResults/#0/#0": { + "rect": [ + 416.0, + 226.0, + 1088.0, + 31.0 + ], + "visible": false + }, + "SearchPopup/SearchPanel/ScrollContainer/SearchResults/#1": { + "rect": [ + 408.0, + 263.0, + 1104.0, + 35.0 + ], + "visible": false + }, + "SearchPopup/SearchPanel/ScrollContainer/SearchResults/#1/#0": { + "rect": [ + 416.0, + 265.0, + 1088.0, + 31.0 + ], + "visible": false + }, + "SearchPopup/SearchPanel/ScrollContainer/SearchResults/#2": { + "rect": [ + 408.0, + 302.0, + 1104.0, + 35.0 + ], + "visible": false + }, + "SearchPopup/SearchPanel/ScrollContainer/SearchResults/#2/#0": { + "rect": [ + 416.0, + 304.0, + 1088.0, + 31.0 + ], + "visible": false + }, + "SearchPopup/SearchPanel/TopRow": { + "rect": [ + 408.0, + 124.0, + 1104.0, + 56.0 + ], + "visible": false + }, + "SearchPopup/SearchPanel/TopRow/ClearButton": { + "rect": [ + 1372.0, + 124.0, + 140.0, + 56.0 + ], + "visible": false + }, + "SearchPopup/SearchPanel/TopRow/SearchField": { + "rect": [ + 408.0, + 124.0, + 948.0, + 56.0 + ], + "visible": false + }, + "SettingsPopup": { + "rect": [ + 700.0, + 405.0, + 1920.0, + 1080.0 + ], + "visible": false + }, + "SettingsPopup/FolderDialog": { + "rect": [ + 0.0, + 0.0, + 800.0, + 500.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel": { + "rect": [ + 700.0, + 405.0, + 520.0, + 269.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer": { + "rect": [ + 720.0, + 417.0, + 480.0, + 245.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/AddToDatabaseCheckButton": { + "rect": [ + 720.0, + 631.0, + 480.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow": { + "rect": [ + 720.0, + 561.0, + 480.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow/CacheLabel": { + "rect": [ + 1084.0, + 565.0, + 48.0, + 23.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow/CacheTitleLabel": { + "rect": [ + 720.0, + 561.0, + 360.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow/IncreaseCacheButton": { + "rect": [ + 1170.0, + 561.0, + 30.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow/LowerCacheButton": { + "rect": [ + 1136.0, + 561.0, + 30.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderErrorLabel": { + "rect": [ + 720.0, + 417.0, + 1.0, + 26.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow": { + "rect": [ + 720.0, + 491.0, + 480.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow/ChooseFolderButton": { + "rect": [ + 1122.0, + 491.0, + 78.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow/FolderLabel": { + "rect": [ + 832.0, + 495.0, + 286.0, + 23.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow/Title": { + "rect": [ + 720.0, + 491.0, + 108.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow/UseServerButton": { + "rect": [ + 720.0, + 491.0, + 90.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FullscreenButton": { + "rect": [ + 720.0, + 526.0, + 480.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/Label": { + "rect": [ + 720.0, + 417.0, + 480.0, + 35.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/LowPlayedCheckButton": { + "rect": [ + 720.0, + 596.0, + 480.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/PlayOrderRow": { + "rect": [ + 720.0, + 417.0, + 314.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/PlayOrderRow/PlayOrderButton": { + "rect": [ + 720.0, + 417.0, + 123.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/PlayOrderRow/Title": { + "rect": [ + 720.0, + 417.0, + 187.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/ServerRow": { + "rect": [ + 720.0, + 456.0, + 480.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/ServerRow/SelectServerButton": { + "rect": [ + 786.0, + 456.0, + 414.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/ServerRow/Title": { + "rect": [ + 720.0, + 456.0, + 62.0, + 31.0 + ], + "visible": false + }, + "ShortcutsPopup": { + "rect": [ + 610.0, + 187.0, + 1920.0, + 1080.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel": { + "rect": [ + 610.0, + 187.0, + 700.0, + 706.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer": { + "rect": [ + 634.0, + 247.0, + 652.0, + 630.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox": { + "rect": [ + 634.0, + 247.0, + 652.0, + 628.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#0": { + "rect": [ + 634.0, + 247.0, + 652.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#1": { + "rect": [ + 634.0, + 279.0, + 652.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#1/#0": { + "rect": [ + 634.0, + 279.0, + 652.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#1/#0/#0": { + "rect": [ + 634.0, + 279.0, + 324.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#1/#0/#1": { + "rect": [ + 962.0, + 279.0, + 324.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#10": { + "rect": [ + 634.0, + 547.0, + 652.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#10/#0": { + "rect": [ + 634.0, + 547.0, + 652.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#10/#0/#0": { + "rect": [ + 634.0, + 547.0, + 324.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#10/#0/#1": { + "rect": [ + 962.0, + 547.0, + 324.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#11": { + "rect": [ + 634.0, + 579.0, + 652.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#11/#0": { + "rect": [ + 634.0, + 579.0, + 652.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#11/#0/#0": { + "rect": [ + 634.0, + 579.0, + 324.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#11/#0/#1": { + "rect": [ + 962.0, + 579.0, + 324.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#12": { + "rect": [ + 634.0, + 611.0, + 652.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#12/#0": { + "rect": [ + 634.0, + 611.0, + 652.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#12/#0/#0": { + "rect": [ + 634.0, + 611.0, + 324.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#12/#0/#1": { + "rect": [ + 962.0, + 611.0, + 324.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#13": { + "rect": [ + 634.0, + 643.0, + 652.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#13/#0": { + "rect": [ + 634.0, + 643.0, + 652.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#13/#0/#0": { + "rect": [ + 634.0, + 643.0, + 324.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#13/#0/#1": { + "rect": [ + 962.0, + 643.0, + 324.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#14": { + "rect": [ + 634.0, + 675.0, + 652.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#14/#0": { + "rect": [ + 634.0, + 675.0, + 652.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#14/#0/#0": { + "rect": [ + 634.0, + 675.0, + 324.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#14/#0/#1": { + "rect": [ + 962.0, + 675.0, + 324.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#15": { + "rect": [ + 634.0, + 707.0, + 652.0, + 8.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#16": { + "rect": [ + 634.0, + 719.0, + 652.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#17": { + "rect": [ + 634.0, + 751.0, + 652.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#17/#0": { + "rect": [ + 634.0, + 751.0, + 652.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#17/#0/#0": { + "rect": [ + 634.0, + 751.0, + 324.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#17/#0/#1": { + "rect": [ + 962.0, + 751.0, + 324.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#18": { + "rect": [ + 634.0, + 783.0, + 652.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#18/#0": { + "rect": [ + 634.0, + 783.0, + 652.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#18/#0/#0": { + "rect": [ + 634.0, + 783.0, + 324.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#18/#0/#1": { + "rect": [ + 962.0, + 783.0, + 324.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#19": { + "rect": [ + 634.0, + 815.0, + 652.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#19/#0": { + "rect": [ + 634.0, + 815.0, + 652.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#19/#0/#0": { + "rect": [ + 634.0, + 815.0, + 324.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#19/#0/#1": { + "rect": [ + 962.0, + 815.0, + 324.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#2": { + "rect": [ + 634.0, + 311.0, + 652.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#2/#0": { + "rect": [ + 634.0, + 311.0, + 652.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#2/#0/#0": { + "rect": [ + 634.0, + 311.0, + 324.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#2/#0/#1": { + "rect": [ + 962.0, + 311.0, + 324.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#20": { + "rect": [ + 634.0, + 847.0, + 652.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#20/#0": { + "rect": [ + 634.0, + 847.0, + 652.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#20/#0/#0": { + "rect": [ + 634.0, + 847.0, + 324.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#20/#0/#1": { + "rect": [ + 962.0, + 847.0, + 324.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#3": { + "rect": [ + 634.0, + 343.0, + 652.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#3/#0": { + "rect": [ + 634.0, + 343.0, + 652.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#3/#0/#0": { + "rect": [ + 634.0, + 343.0, + 324.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#3/#0/#1": { + "rect": [ + 962.0, + 343.0, + 324.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#4": { + "rect": [ + 634.0, + 375.0, + 652.0, + 8.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#5": { + "rect": [ + 634.0, + 387.0, + 652.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#6": { + "rect": [ + 634.0, + 419.0, + 652.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#6/#0": { + "rect": [ + 634.0, + 419.0, + 652.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#6/#0/#0": { + "rect": [ + 634.0, + 419.0, + 324.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#6/#0/#1": { + "rect": [ + 962.0, + 419.0, + 324.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#7": { + "rect": [ + 634.0, + 451.0, + 652.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#7/#0": { + "rect": [ + 634.0, + 451.0, + 652.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#7/#0/#0": { + "rect": [ + 634.0, + 451.0, + 324.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#7/#0/#1": { + "rect": [ + 962.0, + 451.0, + 324.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#8": { + "rect": [ + 634.0, + 483.0, + 652.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#8/#0": { + "rect": [ + 634.0, + 483.0, + 652.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#8/#0/#0": { + "rect": [ + 634.0, + 483.0, + 324.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#8/#0/#1": { + "rect": [ + 962.0, + 483.0, + 324.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#9": { + "rect": [ + 634.0, + 515.0, + 652.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#9/#0": { + "rect": [ + 634.0, + 515.0, + 652.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#9/#0/#0": { + "rect": [ + 634.0, + 515.0, + 324.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/ScrollContainer/shortcutsVBox/#9/#0/#1": { + "rect": [ + 962.0, + 515.0, + 324.0, + 28.0 + ], + "visible": false + }, + "ShortcutsPopup/ShortcutsPanel/TitleLabel": { + "rect": [ + 610.0, + 187.0, + 700.0, + 56.0 + ], + "visible": false + }, + "SoundtrackStatsPopup": { + "rect": [ + 610.0, + 140.0, + 1920.0, + 1080.0 + ], + "visible": false + }, + "SoundtrackStatsPopup/StatsPanel": { + "rect": [ + 610.0, + 140.0, + 700.0, + 800.0 + ], + "visible": false + }, + "SoundtrackStatsPopup/StatsPanel/ScrollContainer": { + "rect": [ + 630.0, + 232.0, + 660.0, + 692.0 + ], + "visible": false + }, + "SoundtrackStatsPopup/StatsPanel/ScrollContainer/SongList": { + "rect": [ + 630.0, + 232.0, + 660.0, + 176.0 + ], + "visible": false + }, + "SoundtrackStatsPopup/StatsPanel/ScrollContainer/SongList/#0": { + "rect": [ + 630.0, + 232.0, + 660.0, + 32.0 + ], + "visible": false + }, + "SoundtrackStatsPopup/StatsPanel/ScrollContainer/SongList/#0/#0": { + "rect": [ + 638.0, + 234.0, + 644.0, + 28.0 + ], + "visible": false + }, + "SoundtrackStatsPopup/StatsPanel/ScrollContainer/SongList/#1": { + "rect": [ + 630.0, + 268.0, + 660.0, + 32.0 + ], + "visible": false + }, + "SoundtrackStatsPopup/StatsPanel/ScrollContainer/SongList/#1/#0": { + "rect": [ + 638.0, + 270.0, + 644.0, + 28.0 + ], + "visible": false + }, + "SoundtrackStatsPopup/StatsPanel/ScrollContainer/SongList/#2": { + "rect": [ + 630.0, + 304.0, + 660.0, + 32.0 + ], + "visible": false + }, + "SoundtrackStatsPopup/StatsPanel/ScrollContainer/SongList/#2/#0": { + "rect": [ + 638.0, + 306.0, + 644.0, + 28.0 + ], + "visible": false + }, + "SoundtrackStatsPopup/StatsPanel/ScrollContainer/SongList/#3": { + "rect": [ + 630.0, + 340.0, + 660.0, + 32.0 + ], + "visible": false + }, + "SoundtrackStatsPopup/StatsPanel/ScrollContainer/SongList/#3/#0": { + "rect": [ + 638.0, + 342.0, + 644.0, + 28.0 + ], + "visible": false + }, + "SoundtrackStatsPopup/StatsPanel/ScrollContainer/SongList/#4": { + "rect": [ + 630.0, + 376.0, + 660.0, + 32.0 + ], + "visible": false + }, + "SoundtrackStatsPopup/StatsPanel/ScrollContainer/SongList/#4/#0": { + "rect": [ + 638.0, + 378.0, + 644.0, + 28.0 + ], + "visible": false + }, + "SoundtrackStatsPopup/StatsPanel/SummaryLabel": { + "rect": [ + 630.0, + 194.0, + 660.0, + 26.0 + ], + "visible": false + }, + "SoundtrackStatsPopup/StatsPanel/TitleLabel": { + "rect": [ + 630.0, + 152.0, + 660.0, + 38.0 + ], + "visible": false + }, + "StatisticsView": { + "rect": [ + 0.0, + 56.0, + 1920.0, + 1024.0 + ], + "visible": false + }, + "StatisticsView/RetryButton": { + "rect": [ + 910.0, + 576.0, + 100.0, + 40.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer": { + "rect": [ + 32.0, + 128.0, + 1856.0, + 936.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid": { + "rect": [ + 32.0, + 128.0, + 290.0, + 340.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#0": { + "rect": [ + 644.0, + 484.0, + 290.0, + 340.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#0/VBox": { + "rect": [ + 656.0, + 494.0, + 266.0, + 320.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#0/VBox/LastPlayedLabel": { + "rect": [ + 656.0, + 587.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#0/VBox/LeastPlayedHeader": { + "rect": [ + 656.0, + 725.0, + 266.0, + 26.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#0/VBox/LeastPlayedList": { + "rect": [ + 656.0, + 755.0, + 266.0, + 50.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#0/VBox/LeastPlayedList/#0": { + "rect": [ + 656.0, + 755.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#0/VBox/LeastPlayedList/#1": { + "rect": [ + 656.0, + 782.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#0/VBox/MostPlayedHeader": { + "rect": [ + 656.0, + 614.0, + 266.0, + 26.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#0/VBox/MostPlayedList": { + "rect": [ + 656.0, + 644.0, + 266.0, + 77.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#0/VBox/MostPlayedList/#0": { + "rect": [ + 656.0, + 644.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#0/VBox/MostPlayedList/#1": { + "rect": [ + 656.0, + 671.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#0/VBox/MostPlayedList/#2": { + "rect": [ + 656.0, + 698.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#0/VBox/NameLabel": { + "rect": [ + 656.0, + 494.0, + 266.0, + 62.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#0/VBox/PlayedLabel": { + "rect": [ + 656.0, + 560.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#1": { + "rect": [ + 950.0, + 484.0, + 290.0, + 340.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#1/VBox": { + "rect": [ + 962.0, + 494.0, + 266.0, + 320.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#1/VBox/LastPlayedLabel": { + "rect": [ + 962.0, + 587.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#1/VBox/LeastPlayedHeader": { + "rect": [ + 962.0, + 725.0, + 266.0, + 26.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#1/VBox/LeastPlayedList": { + "rect": [ + 962.0, + 755.0, + 266.0, + 50.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#1/VBox/LeastPlayedList/#0": { + "rect": [ + 962.0, + 755.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#1/VBox/LeastPlayedList/#1": { + "rect": [ + 962.0, + 782.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#1/VBox/MostPlayedHeader": { + "rect": [ + 962.0, + 614.0, + 266.0, + 26.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#1/VBox/MostPlayedList": { + "rect": [ + 962.0, + 644.0, + 266.0, + 77.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#1/VBox/MostPlayedList/#0": { + "rect": [ + 962.0, + 644.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#1/VBox/MostPlayedList/#1": { + "rect": [ + 962.0, + 671.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#1/VBox/MostPlayedList/#2": { + "rect": [ + 962.0, + 698.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#1/VBox/NameLabel": { + "rect": [ + 962.0, + 494.0, + 266.0, + 62.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#1/VBox/PlayedLabel": { + "rect": [ + 962.0, + 560.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#2": { + "rect": [ + 1256.0, + 128.0, + 290.0, + 340.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#2/VBox": { + "rect": [ + 1268.0, + 138.0, + 266.0, + 320.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#2/VBox/LastPlayedLabel": { + "rect": [ + 1268.0, + 231.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#2/VBox/LeastPlayedHeader": { + "rect": [ + 1268.0, + 369.0, + 266.0, + 26.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#2/VBox/LeastPlayedList": { + "rect": [ + 1268.0, + 399.0, + 266.0, + 50.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#2/VBox/LeastPlayedList/#0": { + "rect": [ + 1268.0, + 399.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#2/VBox/LeastPlayedList/#1": { + "rect": [ + 1268.0, + 426.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#2/VBox/MostPlayedHeader": { + "rect": [ + 1268.0, + 258.0, + 266.0, + 26.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#2/VBox/MostPlayedList": { + "rect": [ + 1268.0, + 288.0, + 266.0, + 77.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#2/VBox/MostPlayedList/#0": { + "rect": [ + 1268.0, + 288.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#2/VBox/MostPlayedList/#1": { + "rect": [ + 1268.0, + 315.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#2/VBox/MostPlayedList/#2": { + "rect": [ + 1268.0, + 342.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#2/VBox/NameLabel": { + "rect": [ + 1268.0, + 138.0, + 266.0, + 62.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#2/VBox/PlayedLabel": { + "rect": [ + 1268.0, + 204.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#3": { + "rect": [ + 950.0, + 128.0, + 290.0, + 340.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#3/VBox": { + "rect": [ + 962.0, + 138.0, + 266.0, + 320.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#3/VBox/LastPlayedLabel": { + "rect": [ + 962.0, + 231.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#3/VBox/LeastPlayedHeader": { + "rect": [ + 962.0, + 369.0, + 266.0, + 26.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#3/VBox/LeastPlayedList": { + "rect": [ + 962.0, + 399.0, + 266.0, + 50.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#3/VBox/LeastPlayedList/#0": { + "rect": [ + 962.0, + 399.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#3/VBox/LeastPlayedList/#1": { + "rect": [ + 962.0, + 426.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#3/VBox/MostPlayedHeader": { + "rect": [ + 962.0, + 258.0, + 266.0, + 26.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#3/VBox/MostPlayedList": { + "rect": [ + 962.0, + 288.0, + 266.0, + 77.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#3/VBox/MostPlayedList/#0": { + "rect": [ + 962.0, + 288.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#3/VBox/MostPlayedList/#1": { + "rect": [ + 962.0, + 315.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#3/VBox/MostPlayedList/#2": { + "rect": [ + 962.0, + 342.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#3/VBox/NameLabel": { + "rect": [ + 962.0, + 138.0, + 266.0, + 62.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#3/VBox/PlayedLabel": { + "rect": [ + 962.0, + 204.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#4": { + "rect": [ + 644.0, + 128.0, + 290.0, + 340.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#4/VBox": { + "rect": [ + 656.0, + 138.0, + 266.0, + 320.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#4/VBox/LastPlayedLabel": { + "rect": [ + 656.0, + 231.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#4/VBox/LeastPlayedHeader": { + "rect": [ + 656.0, + 369.0, + 266.0, + 26.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#4/VBox/LeastPlayedList": { + "rect": [ + 656.0, + 399.0, + 266.0, + 50.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#4/VBox/LeastPlayedList/#0": { + "rect": [ + 656.0, + 399.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#4/VBox/LeastPlayedList/#1": { + "rect": [ + 656.0, + 426.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#4/VBox/MostPlayedHeader": { + "rect": [ + 656.0, + 258.0, + 266.0, + 26.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#4/VBox/MostPlayedList": { + "rect": [ + 656.0, + 288.0, + 266.0, + 77.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#4/VBox/MostPlayedList/#0": { + "rect": [ + 656.0, + 288.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#4/VBox/MostPlayedList/#1": { + "rect": [ + 656.0, + 315.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#4/VBox/MostPlayedList/#2": { + "rect": [ + 656.0, + 342.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#4/VBox/NameLabel": { + "rect": [ + 656.0, + 138.0, + 266.0, + 62.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#4/VBox/PlayedLabel": { + "rect": [ + 656.0, + 204.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#5": { + "rect": [ + 338.0, + 128.0, + 290.0, + 340.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#5/VBox": { + "rect": [ + 350.0, + 138.0, + 266.0, + 320.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#5/VBox/LastPlayedLabel": { + "rect": [ + 350.0, + 231.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#5/VBox/LeastPlayedHeader": { + "rect": [ + 350.0, + 369.0, + 266.0, + 26.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#5/VBox/LeastPlayedList": { + "rect": [ + 350.0, + 399.0, + 266.0, + 50.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#5/VBox/LeastPlayedList/#0": { + "rect": [ + 350.0, + 399.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#5/VBox/LeastPlayedList/#1": { + "rect": [ + 350.0, + 426.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#5/VBox/MostPlayedHeader": { + "rect": [ + 350.0, + 258.0, + 266.0, + 26.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#5/VBox/MostPlayedList": { + "rect": [ + 350.0, + 288.0, + 266.0, + 77.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#5/VBox/MostPlayedList/#0": { + "rect": [ + 350.0, + 288.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#5/VBox/MostPlayedList/#1": { + "rect": [ + 350.0, + 315.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#5/VBox/MostPlayedList/#2": { + "rect": [ + 350.0, + 342.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#5/VBox/NameLabel": { + "rect": [ + 350.0, + 138.0, + 266.0, + 62.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#5/VBox/PlayedLabel": { + "rect": [ + 350.0, + 204.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#6": { + "rect": [ + 32.0, + 484.0, + 290.0, + 340.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#6/VBox": { + "rect": [ + 44.0, + 494.0, + 266.0, + 320.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#6/VBox/LastPlayedLabel": { + "rect": [ + 44.0, + 587.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#6/VBox/LeastPlayedHeader": { + "rect": [ + 44.0, + 725.0, + 266.0, + 26.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#6/VBox/LeastPlayedList": { + "rect": [ + 44.0, + 755.0, + 266.0, + 50.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#6/VBox/LeastPlayedList/#0": { + "rect": [ + 44.0, + 755.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#6/VBox/LeastPlayedList/#1": { + "rect": [ + 44.0, + 782.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#6/VBox/MostPlayedHeader": { + "rect": [ + 44.0, + 614.0, + 266.0, + 26.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#6/VBox/MostPlayedList": { + "rect": [ + 44.0, + 644.0, + 266.0, + 77.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#6/VBox/MostPlayedList/#0": { + "rect": [ + 44.0, + 644.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#6/VBox/MostPlayedList/#1": { + "rect": [ + 44.0, + 671.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#6/VBox/MostPlayedList/#2": { + "rect": [ + 44.0, + 698.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#6/VBox/NameLabel": { + "rect": [ + 44.0, + 494.0, + 266.0, + 62.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#6/VBox/PlayedLabel": { + "rect": [ + 44.0, + 560.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#7": { + "rect": [ + 338.0, + 484.0, + 290.0, + 340.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#7/VBox": { + "rect": [ + 350.0, + 494.0, + 266.0, + 320.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#7/VBox/LastPlayedLabel": { + "rect": [ + 350.0, + 587.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#7/VBox/LeastPlayedHeader": { + "rect": [ + 350.0, + 725.0, + 266.0, + 26.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#7/VBox/LeastPlayedList": { + "rect": [ + 350.0, + 755.0, + 266.0, + 50.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#7/VBox/LeastPlayedList/#0": { + "rect": [ + 350.0, + 755.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#7/VBox/LeastPlayedList/#1": { + "rect": [ + 350.0, + 782.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#7/VBox/MostPlayedHeader": { + "rect": [ + 350.0, + 614.0, + 266.0, + 26.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#7/VBox/MostPlayedList": { + "rect": [ + 350.0, + 644.0, + 266.0, + 77.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#7/VBox/MostPlayedList/#0": { + "rect": [ + 350.0, + 644.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#7/VBox/MostPlayedList/#1": { + "rect": [ + 350.0, + 671.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#7/VBox/MostPlayedList/#2": { + "rect": [ + 350.0, + 698.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#7/VBox/NameLabel": { + "rect": [ + 350.0, + 494.0, + 266.0, + 62.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#7/VBox/PlayedLabel": { + "rect": [ + 350.0, + 560.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#8": { + "rect": [ + 1562.0, + 128.0, + 290.0, + 340.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#8/VBox": { + "rect": [ + 1574.0, + 138.0, + 266.0, + 320.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#8/VBox/LastPlayedLabel": { + "rect": [ + 1574.0, + 231.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#8/VBox/LeastPlayedHeader": { + "rect": [ + 1574.0, + 369.0, + 266.0, + 26.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#8/VBox/LeastPlayedList": { + "rect": [ + 1574.0, + 399.0, + 266.0, + 50.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#8/VBox/LeastPlayedList/#0": { + "rect": [ + 1574.0, + 399.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#8/VBox/LeastPlayedList/#1": { + "rect": [ + 1574.0, + 426.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#8/VBox/MostPlayedHeader": { + "rect": [ + 1574.0, + 258.0, + 266.0, + 26.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#8/VBox/MostPlayedList": { + "rect": [ + 1574.0, + 288.0, + 266.0, + 77.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#8/VBox/MostPlayedList/#0": { + "rect": [ + 1574.0, + 288.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#8/VBox/MostPlayedList/#1": { + "rect": [ + 1574.0, + 315.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#8/VBox/MostPlayedList/#2": { + "rect": [ + 1574.0, + 342.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#8/VBox/NameLabel": { + "rect": [ + 1574.0, + 138.0, + 266.0, + 62.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/#8/VBox/PlayedLabel": { + "rect": [ + 1574.0, + 204.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/SoundtrackCard": { + "rect": [ + 32.0, + 128.0, + 290.0, + 340.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/SoundtrackCard/VBox": { + "rect": [ + 44.0, + 138.0, + 266.0, + 320.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/SoundtrackCard/VBox/LastPlayedLabel": { + "rect": [ + 44.0, + 138.0, + 1.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/SoundtrackCard/VBox/LeastPlayedHeader": { + "rect": [ + 44.0, + 342.0, + 266.0, + 26.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/SoundtrackCard/VBox/LeastPlayedList": { + "rect": [ + 44.0, + 372.0, + 266.0, + 50.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/SoundtrackCard/VBox/LeastPlayedList/#0": { + "rect": [ + 44.0, + 372.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/SoundtrackCard/VBox/LeastPlayedList/#1": { + "rect": [ + 44.0, + 399.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/SoundtrackCard/VBox/MostPlayedHeader": { + "rect": [ + 44.0, + 231.0, + 266.0, + 26.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/SoundtrackCard/VBox/MostPlayedList": { + "rect": [ + 44.0, + 261.0, + 266.0, + 77.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/SoundtrackCard/VBox/MostPlayedList/#0": { + "rect": [ + 44.0, + 261.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/SoundtrackCard/VBox/MostPlayedList/#1": { + "rect": [ + 44.0, + 288.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/SoundtrackCard/VBox/MostPlayedList/#2": { + "rect": [ + 44.0, + 315.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/SoundtrackCard/VBox/NameLabel": { + "rect": [ + 44.0, + 138.0, + 266.0, + 62.0 + ], + "visible": false + }, + "StatisticsView/ScrollContainer/CardGrid/SoundtrackCard/VBox/PlayedLabel": { + "rect": [ + 44.0, + 204.0, + 266.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/StatusLabel": { + "rect": [ + 0.0, + 528.0, + 1920.0, + 36.0 + ], + "visible": false + }, + "StatisticsView/Toolbar": { + "rect": [ + 32.0, + 72.0, + 1856.0, + 40.0 + ], + "visible": false + }, + "StatisticsView/Toolbar/ClearButton": { + "rect": [ + 524.0, + 72.0, + 49.0, + 40.0 + ], + "visible": false + }, + "StatisticsView/Toolbar/CountLabel": { + "rect": [ + 585.0, + 80.0, + 861.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/Toolbar/SearchField": { + "rect": [ + 32.0, + 72.0, + 480.0, + 40.0 + ], + "visible": false + }, + "StatisticsView/Toolbar/ShowLabel": { + "rect": [ + 1458.0, + 80.0, + 42.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/Toolbar/ShowOption": { + "rect": [ + 1512.0, + 72.0, + 160.0, + 40.0 + ], + "visible": false + }, + "StatisticsView/Toolbar/SortLabel": { + "rect": [ + 1684.0, + 80.0, + 32.0, + 23.0 + ], + "visible": false + }, + "StatisticsView/Toolbar/SortOption": { + "rect": [ + 1728.0, + 72.0, + 160.0, + 40.0 + ], + "visible": false + }, + "SyncPopup": { + "rect": [ + 210.0, + 220.0, + 1920.0, + 1080.0 + ], + "visible": false + }, + "SyncPopup/SyncPanel": { + "rect": [ + 210.0, + 220.0, + 1500.0, + 640.0 + ], + "visible": false + }, + "SyncPopup/SyncPanel/ScrollContainer": { + "rect": [ + 230.0, + 228.0, + 1460.0, + 624.0 + ], + "visible": false + }, + "SyncPopup/SyncPanel/ScrollContainer/SyncVBoxContainer": { + "rect": [ + 230.0, + 228.0, + 1460.0, + 294.0 + ], + "visible": false + }, + "SyncPopup/SyncPanel/ScrollContainer/SyncVBoxContainer/CaughtErrorsVBox": { + "rect": [ + 230.0, + 228.0, + 111.0, + 23.0 + ], + "visible": false + }, + "SyncPopup/SyncPanel/ScrollContainer/SyncVBoxContainer/CaughtErrorsVBox/CaughtErrorsLabel": { + "rect": [ + 230.0, + 228.0, + 111.0, + 23.0 + ], + "visible": false + }, + "SyncPopup/SyncPanel/ScrollContainer/SyncVBoxContainer/NoChangesLabel": { + "rect": [ + 230.0, + 228.0, + 319.0, + 35.0 + ], + "visible": false + }, + "SyncPopup/SyncPanel/ScrollContainer/SyncVBoxContainer/ProgressHBoxContainer": { + "rect": [ + 230.0, + 310.0, + 1460.0, + 27.0 + ], + "visible": false + }, + "SyncPopup/SyncPanel/ScrollContainer/SyncVBoxContainer/ProgressHBoxContainer/SyncProgressBar": { + "rect": [ + 310.0, + 310.0, + 1300.0, + 27.0 + ], + "visible": false + }, + "SyncPopup/SyncPanel/ScrollContainer/SyncVBoxContainer/RestartSyncButton": { + "rect": [ + 860.0, + 442.0, + 200.0, + 80.0 + ], + "visible": false + }, + "SyncPopup/SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer": { + "rect": [ + 230.0, + 310.0, + 1460.0, + 120.0 + ], + "visible": false + }, + "SyncPopup/SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/SoundtracksAddedSeparator": { + "rect": [ + 584.0, + 310.0, + 4.0, + 120.0 + ], + "visible": false + }, + "SyncPopup/SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/SoundtracksAddedVBox": { + "rect": [ + 230.0, + 310.0, + 338.0, + 120.0 + ], + "visible": false + }, + "SyncPopup/SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/SoundtracksAddedVBox/#0": { + "rect": [ + 230.0, + 310.0, + 338.0, + 28.0 + ], + "visible": false + }, + "SyncPopup/SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/SoundtracksAddedVBox/#1": { + "rect": [ + 230.0, + 342.0, + 338.0, + 30.0 + ], + "visible": false + }, + "SyncPopup/SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/SoundtracksAddedVBox/#1/#0": { + "rect": [ + 238.0, + 344.0, + 322.0, + 26.0 + ], + "visible": false + }, + "SyncPopup/SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/SoundtracksAddedVBox/#2": { + "rect": [ + 230.0, + 376.0, + 338.0, + 30.0 + ], + "visible": false + }, + "SyncPopup/SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/SoundtracksAddedVBox/#2/#0": { + "rect": [ + 238.0, + 378.0, + 322.0, + 26.0 + ], + "visible": false + }, + "SyncPopup/SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/SoundtracksChangedContentSeparator": { + "rect": [ + 1332.0, + 310.0, + 4.0, + 120.0 + ], + "visible": false + }, + "SyncPopup/SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/SoundtracksChangedContentVBox": { + "rect": [ + 978.0, + 310.0, + 338.0, + 120.0 + ], + "visible": false + }, + "SyncPopup/SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/SoundtracksChangedContentVBox/#0": { + "rect": [ + 978.0, + 310.0, + 338.0, + 28.0 + ], + "visible": false + }, + "SyncPopup/SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/SoundtracksChangedContentVBox/#1": { + "rect": [ + 978.0, + 342.0, + 338.0, + 30.0 + ], + "visible": false + }, + "SyncPopup/SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/SoundtracksChangedContentVBox/#1/#0": { + "rect": [ + 986.0, + 344.0, + 322.0, + 26.0 + ], + "visible": false + }, + "SyncPopup/SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/SoundtracksChangedTitleSeparator": { + "rect": [ + 958.0, + 310.0, + 4.0, + 120.0 + ], + "visible": false + }, + "SyncPopup/SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/SoundtracksChangedTitleVBox": { + "rect": [ + 604.0, + 310.0, + 338.0, + 120.0 + ], + "visible": false + }, + "SyncPopup/SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/SoundtracksChangedTitleVBox/#0": { + "rect": [ + 604.0, + 310.0, + 338.0, + 28.0 + ], + "visible": false + }, + "SyncPopup/SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/SoundtracksChangedTitleVBox/#1": { + "rect": [ + 604.0, + 342.0, + 338.0, + 88.0 + ], + "visible": false + }, + "SyncPopup/SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/SoundtracksChangedTitleVBox/#1/#0": { + "rect": [ + 612.0, + 344.0, + 322.0, + 84.0 + ], + "visible": false + }, + "SyncPopup/SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/SoundtracksReAddedSeparator": { + "rect": [ + 230.0, + 310.0, + 4.0, + 3.0 + ], + "visible": false + }, + "SyncPopup/SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/SoundtracksReAddedVBox": { + "rect": [ + 230.0, + 310.0, + 166.0, + 23.0 + ], + "visible": false + }, + "SyncPopup/SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/SoundtracksReAddedVBox/SoundtracksReAddedLabel": { + "rect": [ + 230.0, + 310.0, + 166.0, + 23.0 + ], + "visible": false + }, + "SyncPopup/SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/SoundtracksRemovedSeparator": { + "rect": [ + 230.0, + 310.0, + 4.0, + 3.0 + ], + "visible": false + }, + "SyncPopup/SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/SoundtracksRemovedVBox": { + "rect": [ + 1352.0, + 310.0, + 338.0, + 120.0 + ], + "visible": false + }, + "SyncPopup/SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/SoundtracksRemovedVBox/#0": { + "rect": [ + 1352.0, + 310.0, + 338.0, + 28.0 + ], + "visible": false + }, + "SyncPopup/SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/SoundtracksRemovedVBox/#1": { + "rect": [ + 1352.0, + 342.0, + 338.0, + 30.0 + ], + "visible": false + }, + "SyncPopup/SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/SoundtracksRemovedVBox/#1/#0": { + "rect": [ + 1360.0, + 344.0, + 322.0, + 26.0 + ], + "visible": false + }, + "SyncPopup/SyncPanel/ScrollContainer/SyncVBoxContainer/SyncTime": { + "rect": [ + 230.0, + 275.0, + 1460.0, + 23.0 + ], + "visible": false + }, + "SyncPopup/SyncPanel/ScrollContainer/SyncVBoxContainer/SyncTitleLabel": { + "rect": [ + 230.0, + 228.0, + 1460.0, + 35.0 + ], + "visible": false + }, + "TopBar": { + "rect": [ + 0.0, + 0.0, + 1920.0, + 56.0 + ], + "visible": true + }, + "TopBar/AboutButton": { + "rect": [ + 1722.0, + 8.0, + 70.0, + 40.0 + ], + "visible": true + }, + "TopBar/DebugLabel": { + "rect": [ + 384.0, + 12.0, + 86.0, + 38.0 + ], + "visible": true + }, + "TopBar/OfflineLabel": { + "rect": [ + 1096.0, + 12.0, + 336.0, + 38.0 + ], + "visible": true + }, "TopBar/QRButton": { "rect": [ 1802.0, @@ -4205,15 +9760,6 @@ ], "visible": false }, - "Quiz/FileDialog": { - "rect": [ - 0.0, - 0.0, - 616.0, - 400.0 - ], - "visible": false - }, "Quiz/InspirationList": { "rect": [ 1216.0, @@ -4583,15 +10129,6 @@ ], "visible": true }, - "Quiz/Open": { - "rect": [ - 1296.0, - 66.0, - 50.0, - 31.0 - ], - "visible": false - }, "Quiz/PlayersPanel": { "rect": [ 1216.0, @@ -5702,34 +11239,43 @@ "SettingsPopup": { "rect": [ 700.0, - 419.0, + 405.0, 1920.0, 1080.0 ], "visible": false }, + "SettingsPopup/FolderDialog": { + "rect": [ + 0.0, + 0.0, + 800.0, + 500.0 + ], + "visible": false + }, "SettingsPopup/SettingsPanel": { "rect": [ 700.0, - 419.0, + 405.0, 520.0, - 242.0 + 269.0 ], "visible": false }, "SettingsPopup/SettingsPanel/VBoxContainer": { "rect": [ 720.0, - 431.0, + 417.0, 480.0, - 218.0 + 245.0 ], "visible": false }, "SettingsPopup/SettingsPanel/VBoxContainer/AddToDatabaseCheckButton": { "rect": [ 720.0, - 431.0, + 417.0, 231.0, 31.0 ], @@ -5738,7 +11284,7 @@ "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow": { "rect": [ 720.0, - 431.0, + 417.0, 331.0, 31.0 ], @@ -5747,7 +11293,7 @@ "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow/CacheLabel": { "rect": [ 720.0, - 431.0, + 417.0, 48.0, 23.0 ], @@ -5756,7 +11302,7 @@ "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow/CacheTitleLabel": { "rect": [ 720.0, - 431.0, + 417.0, 211.0, 31.0 ], @@ -5765,7 +11311,7 @@ "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow/IncreaseCacheButton": { "rect": [ 720.0, - 431.0, + 417.0, 30.0, 31.0 ], @@ -5774,16 +11320,70 @@ "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow/LowerCacheButton": { "rect": [ 720.0, - 431.0, + 417.0, 30.0, 31.0 ], "visible": false }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderErrorLabel": { + "rect": [ + 720.0, + 417.0, + 1.0, + 26.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow": { + "rect": [ + 720.0, + 417.0, + 195.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow/ChooseFolderButton": { + "rect": [ + 720.0, + 417.0, + 78.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow/FolderLabel": { + "rect": [ + 720.0, + 417.0, + 1.0, + 23.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow/Title": { + "rect": [ + 720.0, + 417.0, + 108.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow/UseServerButton": { + "rect": [ + 720.0, + 417.0, + 90.0, + 31.0 + ], + "visible": false + }, "SettingsPopup/SettingsPanel/VBoxContainer/FullscreenButton": { "rect": [ 720.0, - 431.0, + 417.0, 127.0, 31.0 ], @@ -5792,35 +11392,53 @@ "SettingsPopup/SettingsPanel/VBoxContainer/Label": { "rect": [ 720.0, - 431.0, + 417.0, 97.0, 35.0 ], "visible": false }, - "SettingsPopup/SettingsPanel/VBoxContainer/LocalButton": { - "rect": [ - 720.0, - 431.0, - 89.0, - 31.0 - ], - "visible": false - }, "SettingsPopup/SettingsPanel/VBoxContainer/LowPlayedCheckButton": { "rect": [ 720.0, - 431.0, + 417.0, 285.0, 31.0 ], "visible": false }, + "SettingsPopup/SettingsPanel/VBoxContainer/PlayOrderRow": { + "rect": [ + 720.0, + 417.0, + 314.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/PlayOrderRow/PlayOrderButton": { + "rect": [ + 720.0, + 417.0, + 123.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/PlayOrderRow/Title": { + "rect": [ + 720.0, + 417.0, + 187.0, + 31.0 + ], + "visible": false + }, "SettingsPopup/SettingsPanel/VBoxContainer/ServerRow": { "rect": [ 720.0, - 431.0, - 106.0, + 417.0, + 333.0, 31.0 ], "visible": false @@ -5828,8 +11446,8 @@ "SettingsPopup/SettingsPanel/VBoxContainer/ServerRow/SelectServerButton": { "rect": [ 720.0, - 431.0, - 40.0, + 417.0, + 267.0, 20.0 ], "visible": false @@ -5837,7 +11455,7 @@ "SettingsPopup/SettingsPanel/VBoxContainer/ServerRow/Title": { "rect": [ 720.0, - 431.0, + 417.0, 62.0, 31.0 ], @@ -6959,6 +12577,15 @@ ], "visible": true }, + "TopBar/OfflineLabel": { + "rect": [ + 1096.0, + 12.0, + 336.0, + 38.0 + ], + "visible": false + }, "TopBar/QRButton": { "rect": [ 1802.0, @@ -7888,15 +13515,6 @@ ], "visible": false }, - "Quiz/FileDialog": { - "rect": [ - 0.0, - 0.0, - 616.0, - 400.0 - ], - "visible": false - }, "Quiz/InspirationList": { "rect": [ 1216.0, @@ -8329,15 +13947,6 @@ ], "visible": true }, - "Quiz/Open": { - "rect": [ - 1296.0, - 66.0, - 50.0, - 31.0 - ], - "visible": false - }, "Quiz/PlayersPanel": { "rect": [ 1216.0, @@ -9448,34 +15057,43 @@ "SettingsPopup": { "rect": [ 700.0, - 419.0, + 405.0, 1920.0, 1080.0 ], "visible": false }, + "SettingsPopup/FolderDialog": { + "rect": [ + 0.0, + 0.0, + 800.0, + 500.0 + ], + "visible": false + }, "SettingsPopup/SettingsPanel": { "rect": [ 700.0, - 419.0, + 405.0, 520.0, - 242.0 + 269.0 ], "visible": false }, "SettingsPopup/SettingsPanel/VBoxContainer": { "rect": [ 720.0, - 431.0, + 417.0, 480.0, - 218.0 + 245.0 ], "visible": false }, "SettingsPopup/SettingsPanel/VBoxContainer/AddToDatabaseCheckButton": { "rect": [ 720.0, - 431.0, + 417.0, 231.0, 31.0 ], @@ -9484,7 +15102,7 @@ "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow": { "rect": [ 720.0, - 431.0, + 417.0, 331.0, 31.0 ], @@ -9493,7 +15111,7 @@ "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow/CacheLabel": { "rect": [ 720.0, - 431.0, + 417.0, 48.0, 23.0 ], @@ -9502,7 +15120,7 @@ "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow/CacheTitleLabel": { "rect": [ 720.0, - 431.0, + 417.0, 211.0, 31.0 ], @@ -9511,7 +15129,7 @@ "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow/IncreaseCacheButton": { "rect": [ 720.0, - 431.0, + 417.0, 30.0, 31.0 ], @@ -9520,16 +15138,70 @@ "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow/LowerCacheButton": { "rect": [ 720.0, - 431.0, + 417.0, 30.0, 31.0 ], "visible": false }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderErrorLabel": { + "rect": [ + 720.0, + 417.0, + 1.0, + 26.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow": { + "rect": [ + 720.0, + 417.0, + 195.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow/ChooseFolderButton": { + "rect": [ + 720.0, + 417.0, + 78.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow/FolderLabel": { + "rect": [ + 720.0, + 417.0, + 1.0, + 23.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow/Title": { + "rect": [ + 720.0, + 417.0, + 108.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow/UseServerButton": { + "rect": [ + 720.0, + 417.0, + 90.0, + 31.0 + ], + "visible": false + }, "SettingsPopup/SettingsPanel/VBoxContainer/FullscreenButton": { "rect": [ 720.0, - 431.0, + 417.0, 127.0, 31.0 ], @@ -9538,35 +15210,53 @@ "SettingsPopup/SettingsPanel/VBoxContainer/Label": { "rect": [ 720.0, - 431.0, + 417.0, 97.0, 35.0 ], "visible": false }, - "SettingsPopup/SettingsPanel/VBoxContainer/LocalButton": { - "rect": [ - 720.0, - 431.0, - 89.0, - 31.0 - ], - "visible": false - }, "SettingsPopup/SettingsPanel/VBoxContainer/LowPlayedCheckButton": { "rect": [ 720.0, - 431.0, + 417.0, 285.0, 31.0 ], "visible": false }, + "SettingsPopup/SettingsPanel/VBoxContainer/PlayOrderRow": { + "rect": [ + 720.0, + 417.0, + 314.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/PlayOrderRow/PlayOrderButton": { + "rect": [ + 720.0, + 417.0, + 123.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/PlayOrderRow/Title": { + "rect": [ + 720.0, + 417.0, + 187.0, + 31.0 + ], + "visible": false + }, "SettingsPopup/SettingsPanel/VBoxContainer/ServerRow": { "rect": [ 720.0, - 431.0, - 106.0, + 417.0, + 333.0, 31.0 ], "visible": false @@ -9574,8 +15264,8 @@ "SettingsPopup/SettingsPanel/VBoxContainer/ServerRow/SelectServerButton": { "rect": [ 720.0, - 431.0, - 40.0, + 417.0, + 267.0, 20.0 ], "visible": false @@ -9583,7 +15273,7 @@ "SettingsPopup/SettingsPanel/VBoxContainer/ServerRow/Title": { "rect": [ 720.0, - 431.0, + 417.0, 62.0, 31.0 ], @@ -10705,6 +16395,15 @@ ], "visible": true }, + "TopBar/OfflineLabel": { + "rect": [ + 1096.0, + 12.0, + 336.0, + 38.0 + ], + "visible": false + }, "TopBar/QRButton": { "rect": [ 1802.0, @@ -12362,34 +18061,43 @@ "SettingsPopup": { "rect": [ 700.0, - 419.0, + 405.0, 1920.0, 1080.0 ], "visible": true }, + "SettingsPopup/FolderDialog": { + "rect": [ + 0.0, + 0.0, + 800.0, + 500.0 + ], + "visible": false + }, "SettingsPopup/SettingsPanel": { "rect": [ 700.0, - 419.0, + 405.0, 520.0, - 242.0 + 269.0 ], "visible": true }, "SettingsPopup/SettingsPanel/VBoxContainer": { "rect": [ 720.0, - 431.0, + 417.0, 480.0, - 218.0 + 245.0 ], "visible": true }, "SettingsPopup/SettingsPanel/VBoxContainer/AddToDatabaseCheckButton": { "rect": [ 720.0, - 610.0, + 631.0, 480.0, 31.0 ], @@ -12398,7 +18106,7 @@ "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow": { "rect": [ 720.0, - 540.0, + 561.0, 480.0, 31.0 ], @@ -12407,7 +18115,7 @@ "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow/CacheLabel": { "rect": [ 1084.0, - 544.0, + 565.0, 48.0, 23.0 ], @@ -12416,7 +18124,7 @@ "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow/CacheTitleLabel": { "rect": [ 720.0, - 540.0, + 561.0, 360.0, 31.0 ], @@ -12425,7 +18133,7 @@ "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow/IncreaseCacheButton": { "rect": [ 1170.0, - 540.0, + 561.0, 30.0, 31.0 ], @@ -12434,16 +18142,70 @@ "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow/LowerCacheButton": { "rect": [ 1136.0, - 540.0, + 561.0, 30.0, 31.0 ], "visible": true }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderErrorLabel": { + "rect": [ + 720.0, + 417.0, + 1.0, + 26.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow": { + "rect": [ + 720.0, + 491.0, + 480.0, + 31.0 + ], + "visible": true + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow/ChooseFolderButton": { + "rect": [ + 1122.0, + 491.0, + 78.0, + 31.0 + ], + "visible": true + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow/FolderLabel": { + "rect": [ + 832.0, + 495.0, + 286.0, + 23.0 + ], + "visible": true + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow/Title": { + "rect": [ + 720.0, + 491.0, + 108.0, + 31.0 + ], + "visible": true + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow/UseServerButton": { + "rect": [ + 720.0, + 491.0, + 90.0, + 31.0 + ], + "visible": false + }, "SettingsPopup/SettingsPanel/VBoxContainer/FullscreenButton": { "rect": [ 720.0, - 505.0, + 526.0, 480.0, 31.0 ], @@ -12452,34 +18214,52 @@ "SettingsPopup/SettingsPanel/VBoxContainer/Label": { "rect": [ 720.0, - 431.0, + 417.0, 480.0, 35.0 ], "visible": true }, - "SettingsPopup/SettingsPanel/VBoxContainer/LocalButton": { - "rect": [ - 720.0, - 431.0, - 89.0, - 31.0 - ], - "visible": false - }, "SettingsPopup/SettingsPanel/VBoxContainer/LowPlayedCheckButton": { "rect": [ 720.0, - 575.0, + 596.0, 480.0, 31.0 ], "visible": true }, + "SettingsPopup/SettingsPanel/VBoxContainer/PlayOrderRow": { + "rect": [ + 720.0, + 417.0, + 314.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/PlayOrderRow/PlayOrderButton": { + "rect": [ + 720.0, + 417.0, + 123.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/PlayOrderRow/Title": { + "rect": [ + 720.0, + 417.0, + 187.0, + 31.0 + ], + "visible": false + }, "SettingsPopup/SettingsPanel/VBoxContainer/ServerRow": { "rect": [ 720.0, - 470.0, + 456.0, 480.0, 31.0 ], @@ -12488,7 +18268,7 @@ "SettingsPopup/SettingsPanel/VBoxContainer/ServerRow/SelectServerButton": { "rect": [ 786.0, - 470.0, + 456.0, 414.0, 31.0 ], @@ -12497,7 +18277,234 @@ "SettingsPopup/SettingsPanel/VBoxContainer/ServerRow/Title": { "rect": [ 720.0, - 470.0, + 456.0, + 62.0, + 31.0 + ], + "visible": true + } + }, + "popup_SettingsWindow_offline": { + "SettingsPopup": { + "rect": [ + 700.0, + 405.0, + 1920.0, + 1080.0 + ], + "visible": true + }, + "SettingsPopup/FolderDialog": { + "rect": [ + 0.0, + 0.0, + 800.0, + 500.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel": { + "rect": [ + 700.0, + 388.0, + 520.0, + 304.0 + ], + "visible": true + }, + "SettingsPopup/SettingsPanel/VBoxContainer": { + "rect": [ + 720.0, + 400.0, + 480.0, + 280.0 + ], + "visible": true + }, + "SettingsPopup/SettingsPanel/VBoxContainer/AddToDatabaseCheckButton": { + "rect": [ + 720.0, + 649.0, + 480.0, + 31.0 + ], + "visible": true + }, + "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow": { + "rect": [ + 720.0, + 579.0, + 480.0, + 31.0 + ], + "visible": true + }, + "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow/CacheLabel": { + "rect": [ + 1084.0, + 583.0, + 48.0, + 23.0 + ], + "visible": true + }, + "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow/CacheTitleLabel": { + "rect": [ + 720.0, + 579.0, + 360.0, + 31.0 + ], + "visible": true + }, + "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow/IncreaseCacheButton": { + "rect": [ + 1170.0, + 579.0, + 30.0, + 31.0 + ], + "visible": true + }, + "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow/LowerCacheButton": { + "rect": [ + 1136.0, + 579.0, + 30.0, + 31.0 + ], + "visible": true + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderErrorLabel": { + "rect": [ + 720.0, + 400.0, + 1.0, + 26.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow": { + "rect": [ + 720.0, + 474.0, + 480.0, + 31.0 + ], + "visible": true + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow/ChooseFolderButton": { + "rect": [ + 1028.0, + 474.0, + 78.0, + 31.0 + ], + "visible": true + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow/FolderLabel": { + "rect": [ + 832.0, + 478.0, + 192.0, + 23.0 + ], + "visible": true + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow/Title": { + "rect": [ + 720.0, + 474.0, + 108.0, + 31.0 + ], + "visible": true + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow/UseServerButton": { + "rect": [ + 1110.0, + 474.0, + 90.0, + 31.0 + ], + "visible": true + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FullscreenButton": { + "rect": [ + 720.0, + 544.0, + 480.0, + 31.0 + ], + "visible": true + }, + "SettingsPopup/SettingsPanel/VBoxContainer/Label": { + "rect": [ + 720.0, + 400.0, + 480.0, + 35.0 + ], + "visible": true + }, + "SettingsPopup/SettingsPanel/VBoxContainer/LowPlayedCheckButton": { + "rect": [ + 720.0, + 614.0, + 480.0, + 31.0 + ], + "visible": true + }, + "SettingsPopup/SettingsPanel/VBoxContainer/PlayOrderRow": { + "rect": [ + 720.0, + 509.0, + 480.0, + 31.0 + ], + "visible": true + }, + "SettingsPopup/SettingsPanel/VBoxContainer/PlayOrderRow/PlayOrderButton": { + "rect": [ + 1077.0, + 509.0, + 123.0, + 31.0 + ], + "visible": true + }, + "SettingsPopup/SettingsPanel/VBoxContainer/PlayOrderRow/Title": { + "rect": [ + 720.0, + 509.0, + 353.0, + 31.0 + ], + "visible": true + }, + "SettingsPopup/SettingsPanel/VBoxContainer/ServerRow": { + "rect": [ + 720.0, + 439.0, + 480.0, + 31.0 + ], + "visible": true + }, + "SettingsPopup/SettingsPanel/VBoxContainer/ServerRow/SelectServerButton": { + "rect": [ + 786.0, + 439.0, + 414.0, + 31.0 + ], + "visible": true + }, + "SettingsPopup/SettingsPanel/VBoxContainer/ServerRow/Title": { + "rect": [ + 720.0, + 439.0, 62.0, 31.0 ], @@ -14224,6 +20231,386 @@ "visible": true } }, + "popup_WinnerWindow_prepared_quiz": { + "Quiz/WinnerPopup": { + "rect": [ + 160.0, + 80.0, + 1920.0, + 1080.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel": { + "rect": [ + 160.0, + 80.0, + 1600.0, + 920.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn": { + "rect": [ + 184.0, + 100.0, + 1552.0, + 880.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Header": { + "rect": [ + 184.0, + 100.0, + 1552.0, + 182.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Header/#1": { + "rect": [ + 700.0, + 100.0, + 364.0, + 182.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Header/WinnerInfo": { + "rect": [ + 1096.0, + 100.0, + 520.0, + 182.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Header/WinnerInfo/PlayersList": { + "rect": [ + 1096.0, + 205.0, + 520.0, + 77.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Header/WinnerInfo/PlayersList/#0": { + "rect": [ + 1096.0, + 205.0, + 520.0, + 23.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Header/WinnerInfo/PlayersList/#1": { + "rect": [ + 1096.0, + 232.0, + 520.0, + 23.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Header/WinnerInfo/PlayersList/#2": { + "rect": [ + 1096.0, + 259.0, + 520.0, + 23.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Header/WinnerInfo/WinnerLabel": { + "rect": [ + 1096.0, + 100.0, + 520.0, + 101.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Header/WinnerPicture": { + "rect": [ + 304.0, + 100.0, + 364.0, + 182.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Legend": { + "rect": [ + 184.0, + 294.0, + 1552.0, + 23.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Legend/#0": { + "rect": [ + 735.0, + 294.0, + 79.0, + 23.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Legend/#0/#0": { + "rect": [ + 735.0, + 298.0, + 14.0, + 14.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Legend/#0/#1": { + "rect": [ + 753.0, + 294.0, + 61.0, + 23.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Legend/#1": { + "rect": [ + 834.0, + 294.0, + 76.0, + 23.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Legend/#1/#0": { + "rect": [ + 834.0, + 298.0, + 14.0, + 14.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Legend/#1/#1": { + "rect": [ + 852.0, + 294.0, + 58.0, + 23.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Legend/#2": { + "rect": [ + 930.0, + 294.0, + 76.0, + 23.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Legend/#2/#0": { + "rect": [ + 930.0, + 298.0, + 14.0, + 14.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Legend/#2/#1": { + "rect": [ + 948.0, + 294.0, + 58.0, + 23.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Legend/#3": { + "rect": [ + 1026.0, + 294.0, + 66.0, + 23.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Legend/#3/#0": { + "rect": [ + 1026.0, + 298.0, + 14.0, + 14.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Legend/#3/#1": { + "rect": [ + 1044.0, + 294.0, + 48.0, + 23.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Legend/#4": { + "rect": [ + 1112.0, + 294.0, + 72.0, + 23.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Legend/#4/#0": { + "rect": [ + 1112.0, + 298.0, + 14.0, + 14.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Legend/#4/#1": { + "rect": [ + 1130.0, + 294.0, + 54.0, + 23.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/ScoreGraph": { + "rect": [ + 184.0, + 329.0, + 1552.0, + 555.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Summary": { + "rect": [ + 184.0, + 896.0, + 1552.0, + 84.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Summary/SharedBox": { + "rect": [ + 1226.0, + 896.0, + 510.0, + 84.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Summary/SharedBox/VBox": { + "rect": [ + 1234.0, + 898.0, + 494.0, + 80.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Summary/SharedBox/VBox/Header": { + "rect": [ + 1234.0, + 898.0, + 494.0, + 26.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Summary/SharedBox/VBox/SharedLabel": { + "rect": [ + 1234.0, + 928.0, + 494.0, + 23.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Summary/StandstillBox": { + "rect": [ + 705.0, + 896.0, + 509.0, + 84.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Summary/StandstillBox/VBox": { + "rect": [ + 713.0, + 898.0, + 493.0, + 80.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Summary/StandstillBox/VBox/Header": { + "rect": [ + 713.0, + 898.0, + 493.0, + 26.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Summary/StandstillBox/VBox/StandstillLabel": { + "rect": [ + 713.0, + 928.0, + 493.0, + 23.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Summary/StreakBox": { + "rect": [ + 184.0, + 896.0, + 509.0, + 84.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Summary/StreakBox/VBox": { + "rect": [ + 192.0, + 898.0, + 493.0, + 80.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Summary/StreakBox/VBox/Header": { + "rect": [ + 192.0, + 898.0, + 493.0, + 26.0 + ], + "visible": true + }, + "Quiz/WinnerPopup/WinnerPanel/GameColumn/Summary/StreakBox/VBox/StreakLabel": { + "rect": [ + 192.0, + 928.0, + 493.0, + 23.0 + ], + "visible": true + } + }, "startup": { ".": { "rect": [ @@ -15017,15 +21404,6 @@ ], "visible": false }, - "Quiz/FileDialog": { - "rect": [ - 0.0, - 0.0, - 616.0, - 400.0 - ], - "visible": false - }, "Quiz/InspirationList": { "rect": [ 1216.0, @@ -15395,15 +21773,6 @@ ], "visible": true }, - "Quiz/Open": { - "rect": [ - 1296.0, - 66.0, - 50.0, - 31.0 - ], - "visible": false - }, "Quiz/PlayersPanel": { "rect": [ 1216.0, @@ -16298,34 +22667,43 @@ "SettingsPopup": { "rect": [ 700.0, - 419.0, + 405.0, 1920.0, 1080.0 ], "visible": false }, + "SettingsPopup/FolderDialog": { + "rect": [ + 0.0, + 0.0, + 800.0, + 500.0 + ], + "visible": false + }, "SettingsPopup/SettingsPanel": { "rect": [ 700.0, - 419.0, + 405.0, 520.0, - 242.0 + 269.0 ], "visible": false }, "SettingsPopup/SettingsPanel/VBoxContainer": { "rect": [ 720.0, - 431.0, + 417.0, 480.0, - 218.0 + 245.0 ], "visible": false }, "SettingsPopup/SettingsPanel/VBoxContainer/AddToDatabaseCheckButton": { "rect": [ 720.0, - 431.0, + 417.0, 231.0, 31.0 ], @@ -16334,7 +22712,7 @@ "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow": { "rect": [ 720.0, - 431.0, + 417.0, 331.0, 31.0 ], @@ -16343,7 +22721,7 @@ "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow/CacheLabel": { "rect": [ 720.0, - 431.0, + 417.0, 48.0, 23.0 ], @@ -16352,7 +22730,7 @@ "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow/CacheTitleLabel": { "rect": [ 720.0, - 431.0, + 417.0, 211.0, 31.0 ], @@ -16361,7 +22739,7 @@ "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow/IncreaseCacheButton": { "rect": [ 720.0, - 431.0, + 417.0, 30.0, 31.0 ], @@ -16370,16 +22748,70 @@ "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow/LowerCacheButton": { "rect": [ 720.0, - 431.0, + 417.0, 30.0, 31.0 ], "visible": false }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderErrorLabel": { + "rect": [ + 720.0, + 417.0, + 1.0, + 26.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow": { + "rect": [ + 720.0, + 417.0, + 195.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow/ChooseFolderButton": { + "rect": [ + 720.0, + 417.0, + 78.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow/FolderLabel": { + "rect": [ + 720.0, + 417.0, + 1.0, + 23.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow/Title": { + "rect": [ + 720.0, + 417.0, + 108.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow/UseServerButton": { + "rect": [ + 720.0, + 417.0, + 90.0, + 31.0 + ], + "visible": false + }, "SettingsPopup/SettingsPanel/VBoxContainer/FullscreenButton": { "rect": [ 720.0, - 431.0, + 417.0, 127.0, 31.0 ], @@ -16388,35 +22820,53 @@ "SettingsPopup/SettingsPanel/VBoxContainer/Label": { "rect": [ 720.0, - 431.0, + 417.0, 97.0, 35.0 ], "visible": false }, - "SettingsPopup/SettingsPanel/VBoxContainer/LocalButton": { - "rect": [ - 720.0, - 431.0, - 89.0, - 31.0 - ], - "visible": false - }, "SettingsPopup/SettingsPanel/VBoxContainer/LowPlayedCheckButton": { "rect": [ 720.0, - 431.0, + 417.0, 285.0, 31.0 ], "visible": false }, + "SettingsPopup/SettingsPanel/VBoxContainer/PlayOrderRow": { + "rect": [ + 720.0, + 417.0, + 314.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/PlayOrderRow/PlayOrderButton": { + "rect": [ + 720.0, + 417.0, + 123.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/PlayOrderRow/Title": { + "rect": [ + 720.0, + 417.0, + 187.0, + 31.0 + ], + "visible": false + }, "SettingsPopup/SettingsPanel/VBoxContainer/ServerRow": { "rect": [ 720.0, - 431.0, - 106.0, + 417.0, + 333.0, 31.0 ], "visible": false @@ -16424,8 +22874,8 @@ "SettingsPopup/SettingsPanel/VBoxContainer/ServerRow/SelectServerButton": { "rect": [ 720.0, - 431.0, - 40.0, + 417.0, + 267.0, 20.0 ], "visible": false @@ -16433,7 +22883,7 @@ "SettingsPopup/SettingsPanel/VBoxContainer/ServerRow/Title": { "rect": [ 720.0, - 431.0, + 417.0, 62.0, 31.0 ], @@ -17555,6 +24005,15 @@ ], "visible": true }, + "TopBar/OfflineLabel": { + "rect": [ + 1096.0, + 12.0, + 336.0, + 38.0 + ], + "visible": false + }, "TopBar/QRButton": { "rect": [ 1802.0, @@ -22621,15 +29080,6 @@ ], "visible": false }, - "Quiz/FileDialog": { - "rect": [ - 0.0, - 0.0, - 616.0, - 400.0 - ], - "visible": false - }, "Quiz/InspirationList": { "rect": [ 1216.0, @@ -23062,15 +29512,6 @@ ], "visible": true }, - "Quiz/Open": { - "rect": [ - 1296.0, - 66.0, - 50.0, - 31.0 - ], - "visible": false - }, "Quiz/PlayersPanel": { "rect": [ 1216.0, @@ -24244,34 +30685,43 @@ "SettingsPopup": { "rect": [ 700.0, - 419.0, + 405.0, 1920.0, 1080.0 ], "visible": false }, + "SettingsPopup/FolderDialog": { + "rect": [ + 0.0, + 0.0, + 800.0, + 500.0 + ], + "visible": false + }, "SettingsPopup/SettingsPanel": { "rect": [ 700.0, - 419.0, + 405.0, 520.0, - 242.0 + 269.0 ], "visible": false }, "SettingsPopup/SettingsPanel/VBoxContainer": { "rect": [ 720.0, - 431.0, + 417.0, 480.0, - 218.0 + 245.0 ], "visible": false }, "SettingsPopup/SettingsPanel/VBoxContainer/AddToDatabaseCheckButton": { "rect": [ 720.0, - 610.0, + 631.0, 480.0, 31.0 ], @@ -24280,7 +30730,7 @@ "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow": { "rect": [ 720.0, - 540.0, + 561.0, 480.0, 31.0 ], @@ -24289,7 +30739,7 @@ "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow/CacheLabel": { "rect": [ 1084.0, - 544.0, + 565.0, 48.0, 23.0 ], @@ -24298,7 +30748,7 @@ "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow/CacheTitleLabel": { "rect": [ 720.0, - 540.0, + 561.0, 360.0, 31.0 ], @@ -24307,7 +30757,7 @@ "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow/IncreaseCacheButton": { "rect": [ 1170.0, - 540.0, + 561.0, 30.0, 31.0 ], @@ -24316,16 +30766,70 @@ "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow/LowerCacheButton": { "rect": [ 1136.0, - 540.0, + 561.0, 30.0, 31.0 ], "visible": false }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderErrorLabel": { + "rect": [ + 720.0, + 417.0, + 1.0, + 26.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow": { + "rect": [ + 720.0, + 491.0, + 480.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow/ChooseFolderButton": { + "rect": [ + 1122.0, + 491.0, + 78.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow/FolderLabel": { + "rect": [ + 832.0, + 495.0, + 286.0, + 23.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow/Title": { + "rect": [ + 720.0, + 491.0, + 108.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow/UseServerButton": { + "rect": [ + 720.0, + 491.0, + 90.0, + 31.0 + ], + "visible": false + }, "SettingsPopup/SettingsPanel/VBoxContainer/FullscreenButton": { "rect": [ 720.0, - 505.0, + 526.0, 480.0, 31.0 ], @@ -24334,34 +30838,52 @@ "SettingsPopup/SettingsPanel/VBoxContainer/Label": { "rect": [ 720.0, - 431.0, + 417.0, 480.0, 35.0 ], "visible": false }, - "SettingsPopup/SettingsPanel/VBoxContainer/LocalButton": { - "rect": [ - 720.0, - 431.0, - 89.0, - 31.0 - ], - "visible": false - }, "SettingsPopup/SettingsPanel/VBoxContainer/LowPlayedCheckButton": { "rect": [ 720.0, - 575.0, + 596.0, 480.0, 31.0 ], "visible": false }, + "SettingsPopup/SettingsPanel/VBoxContainer/PlayOrderRow": { + "rect": [ + 720.0, + 417.0, + 314.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/PlayOrderRow/PlayOrderButton": { + "rect": [ + 720.0, + 417.0, + 123.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/PlayOrderRow/Title": { + "rect": [ + 720.0, + 417.0, + 187.0, + 31.0 + ], + "visible": false + }, "SettingsPopup/SettingsPanel/VBoxContainer/ServerRow": { "rect": [ 720.0, - 470.0, + 456.0, 480.0, 31.0 ], @@ -24370,7 +30892,7 @@ "SettingsPopup/SettingsPanel/VBoxContainer/ServerRow/SelectServerButton": { "rect": [ 786.0, - 470.0, + 456.0, 414.0, 31.0 ], @@ -24379,7 +30901,7 @@ "SettingsPopup/SettingsPanel/VBoxContainer/ServerRow/Title": { "rect": [ 720.0, - 470.0, + 456.0, 62.0, 31.0 ], @@ -25501,6 +32023,15 @@ ], "visible": true }, + "TopBar/OfflineLabel": { + "rect": [ + 1096.0, + 12.0, + 336.0, + 38.0 + ], + "visible": false + }, "TopBar/QRButton": { "rect": [ 1802.0, @@ -27028,15 +33559,6 @@ ], "visible": false }, - "Quiz/FileDialog": { - "rect": [ - 0.0, - 0.0, - 616.0, - 400.0 - ], - "visible": false - }, "Quiz/InspirationList": { "rect": [ 1216.0, @@ -27406,15 +33928,6 @@ ], "visible": true }, - "Quiz/Open": { - "rect": [ - 1296.0, - 66.0, - 50.0, - 31.0 - ], - "visible": false - }, "Quiz/PlayersPanel": { "rect": [ 1216.0, @@ -28588,34 +35101,43 @@ "SettingsPopup": { "rect": [ 700.0, - 419.0, + 405.0, 1920.0, 1080.0 ], "visible": false }, + "SettingsPopup/FolderDialog": { + "rect": [ + 0.0, + 0.0, + 800.0, + 500.0 + ], + "visible": false + }, "SettingsPopup/SettingsPanel": { "rect": [ 700.0, - 419.0, + 405.0, 520.0, - 242.0 + 269.0 ], "visible": false }, "SettingsPopup/SettingsPanel/VBoxContainer": { "rect": [ 720.0, - 431.0, + 417.0, 480.0, - 218.0 + 245.0 ], "visible": false }, "SettingsPopup/SettingsPanel/VBoxContainer/AddToDatabaseCheckButton": { "rect": [ 720.0, - 610.0, + 631.0, 480.0, 31.0 ], @@ -28624,7 +35146,7 @@ "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow": { "rect": [ 720.0, - 540.0, + 561.0, 480.0, 31.0 ], @@ -28633,7 +35155,7 @@ "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow/CacheLabel": { "rect": [ 1084.0, - 544.0, + 565.0, 48.0, 23.0 ], @@ -28642,7 +35164,7 @@ "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow/CacheTitleLabel": { "rect": [ 720.0, - 540.0, + 561.0, 360.0, 31.0 ], @@ -28651,7 +35173,7 @@ "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow/IncreaseCacheButton": { "rect": [ 1170.0, - 540.0, + 561.0, 30.0, 31.0 ], @@ -28660,16 +35182,70 @@ "SettingsPopup/SettingsPanel/VBoxContainer/CacheRow/LowerCacheButton": { "rect": [ 1136.0, - 540.0, + 561.0, 30.0, 31.0 ], "visible": false }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderErrorLabel": { + "rect": [ + 720.0, + 417.0, + 1.0, + 26.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow": { + "rect": [ + 720.0, + 491.0, + 480.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow/ChooseFolderButton": { + "rect": [ + 1122.0, + 491.0, + 78.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow/FolderLabel": { + "rect": [ + 832.0, + 495.0, + 286.0, + 23.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow/Title": { + "rect": [ + 720.0, + 491.0, + 108.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/FolderRow/UseServerButton": { + "rect": [ + 720.0, + 491.0, + 90.0, + 31.0 + ], + "visible": false + }, "SettingsPopup/SettingsPanel/VBoxContainer/FullscreenButton": { "rect": [ 720.0, - 505.0, + 526.0, 480.0, 31.0 ], @@ -28678,34 +35254,52 @@ "SettingsPopup/SettingsPanel/VBoxContainer/Label": { "rect": [ 720.0, - 431.0, + 417.0, 480.0, 35.0 ], "visible": false }, - "SettingsPopup/SettingsPanel/VBoxContainer/LocalButton": { - "rect": [ - 720.0, - 431.0, - 89.0, - 31.0 - ], - "visible": false - }, "SettingsPopup/SettingsPanel/VBoxContainer/LowPlayedCheckButton": { "rect": [ 720.0, - 575.0, + 596.0, 480.0, 31.0 ], "visible": false }, + "SettingsPopup/SettingsPanel/VBoxContainer/PlayOrderRow": { + "rect": [ + 720.0, + 417.0, + 314.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/PlayOrderRow/PlayOrderButton": { + "rect": [ + 720.0, + 417.0, + 123.0, + 31.0 + ], + "visible": false + }, + "SettingsPopup/SettingsPanel/VBoxContainer/PlayOrderRow/Title": { + "rect": [ + 720.0, + 417.0, + 187.0, + 31.0 + ], + "visible": false + }, "SettingsPopup/SettingsPanel/VBoxContainer/ServerRow": { "rect": [ 720.0, - 470.0, + 456.0, 480.0, 31.0 ], @@ -28714,7 +35308,7 @@ "SettingsPopup/SettingsPanel/VBoxContainer/ServerRow/SelectServerButton": { "rect": [ 786.0, - 470.0, + 456.0, 414.0, 31.0 ], @@ -28723,7 +35317,7 @@ "SettingsPopup/SettingsPanel/VBoxContainer/ServerRow/Title": { "rect": [ 720.0, - 470.0, + 456.0, 62.0, 31.0 ], @@ -29935,6 +36529,15 @@ ], "visible": true }, + "TopBar/OfflineLabel": { + "rect": [ + 1096.0, + 12.0, + 336.0, + 38.0 + ], + "visible": false + }, "TopBar/QRButton": { "rect": [ 1802.0, diff --git a/tests/data/short.ogg b/tests/data/short.ogg new file mode 100644 index 0000000000000000000000000000000000000000..4a6e8060acb05ab4868bde13ac5d950947fde8a8 GIT binary patch literal 22615 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 literal 0 HcmV?d00001 diff --git a/tests/data/short.wav b/tests/data/short.wav new file mode 100644 index 0000000000000000000000000000000000000000..cb1d4f3bee737f5b7998c3b396cf2f908fd22e04 GIT binary patch literal 44244 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 void: winner_popup.close() await _settle() + # Playing from a music folder: a soundtrack folder, then a prepared quiz in Settings + var music: String = _make_music_folders() + var settings_popup: SettingsPopup = _node("SettingsPopup") + # Songs are picked at random; the same picks every time keep the pictures the same + seed(1) + settings_popup.use_music_folder(music.path_join("Soundtracks")) + await _settle() + _call("play_next_song") + # A playing song is stopped first, which takes half a second + await _main.get_tree().create_timer(0.7).timeout + await _settle() + _call("show_answer_pressed") + await _settle() + await on_state.call("offline", null) + settings_popup.use_music_folder(music.path_join("Musiktävling")) + settings_popup.open() + await _settle() + await on_state.call("popup_SettingsWindow_offline", settings_popup) + settings_popup.close() + # The end of a prepared quiz, where two players share the most points + var scores: Array[int] = [7, 12, 12, 3, 9] + for index: int in Settings.player_array.size(): + Settings.player_array[index].player_score = scores[index % scores.size()] + _call("show_final_winners") + var final_popup: WinnerPopup = _node("WinnerPopup") + await _settle() + await on_state.call("popup_WinnerWindow_prepared_quiz", final_popup) + final_popup.close() + settings_popup.use_server() + await _settle() + + +# Writes a soundtrack folder and a prepared quiz with copies of the test song, and +# returns the folder they are in +static func _make_music_folders() -> String: + var root: String = OS.get_user_data_dir().path_join("ui_tour_music") + var song: PackedByteArray = FileAccess.get_file_as_bytes("res://tests/data/short.mp3") + var files: Array[String] = [ + "Soundtracks/Chrono Trigger/Corridors of Time.mp3", + "Soundtracks/Final Fantasy VI/Dancing Mad.mp3", + "Soundtracks/Secret of Mana/Into the Thick of It.mp3", + "Musiktävling/01. Opening.mp3", + "Musiktävling/02. Ace Combat 7 - Daredevil.mp3", + ] + for file: String in files: + var path: String = root.path_join(file) + DirAccess.make_dir_recursive_absolute(path.get_base_dir()) + FileAccess.open(path, FileAccess.WRITE).store_buffer(song) + return root + ## Returns where every Control and Window in [param subtree] is on screen, keyed by its ## path from [param main]: [code]{"rect": [x, y, width, height], "visible": bool}[/code]. diff --git a/tests/integration/test_inspiration_list.gd b/tests/integration/test_inspiration_list.gd index 31e8890..25926f2 100644 --- a/tests/integration/test_inspiration_list.gd +++ b/tests/integration/test_inspiration_list.gd @@ -33,15 +33,15 @@ func _texts() -> Array: return _rows().map(func(row: ListRow) -> String: return row.text) -func test_load_from_server_shows_every_soundtrack_in_order() -> void: - _list.load_from_server() +func test_load_soundtracks_shows_every_soundtrack_in_order() -> void: + _list.load_soundtracks() assert_array(_texts()).contains_exactly(SOUNDTRACKS) assert_array(_list.soundtracks).contains_exactly(SOUNDTRACKS) func test_rows_alternate_backgrounds() -> void: - _list.load_from_server() + _list.load_soundtracks() var rows: Array[Node] = _rows() assert_that((rows[0] as ListRow).get_theme_stylebox("panel")) \ @@ -51,19 +51,19 @@ func test_rows_alternate_backgrounds() -> void: func test_loading_again_replaces_the_rows() -> void: - _list.load_from_server() + _list.load_soundtracks() _server.respond_json("/music/all", ["Terranigma"]) - _list.load_from_server() + _list.load_soundtracks() assert_array(_texts()).contains_exactly(["Terranigma"]) func test_failed_load_keeps_the_rows() -> void: - _list.load_from_server() + _list.load_soundtracks() _server.respond_bytes("/music/all", PackedByteArray(), 500) - _list.load_from_server() + _list.load_soundtracks() assert_array(_texts()).contains_exactly(SOUNDTRACKS) diff --git a/tests/integration/test_offline_mode.gd b/tests/integration/test_offline_mode.gd new file mode 100644 index 0000000..9eea745 --- /dev/null +++ b/tests/integration/test_offline_mode.gd @@ -0,0 +1,339 @@ +extends MainWindowTestSuite +## Playing from a music folder instead of the server: soundtrack folders and prepared +## quizzes, with nothing but the characters asked from the server. + +var _root: String +var _settings: SettingsPopup +var _top_bar: TopBar + + +func before_test() -> void: + _root = create_temp_dir("offline_%d" % randi()) + super() + _settings = _window.get_node("SettingsPopup") + _top_bar = _window.get_node("TopBar") + + +func _song(relative_path: String) -> void: + var path: String = _root.path_join(relative_path) + DirAccess.make_dir_recursive_absolute(path.get_base_dir()) + FileAccess.open(path, FileAccess.WRITE).store_buffer( + FileAccess.get_file_as_bytes("res://tests/data/short.mp3")) + + +func _soundtrack_folder() -> String: + _song("Music/Chrono Trigger/Corridors of Time.mp3") + _song("Music/Final Fantasy VI/Dancing Mad.mp3") + return _root.path_join("Music") + + +func _prepared_quiz() -> String: + for file_name: String in ["02. Ace Combat 7 - Daredevil.mp3", "01. Opening.mp3", + "10. Banjo Tooie - Cloud Race.mp3"]: + _song("Quiz/" + file_name) + return _root.path_join("Quiz") + + +# Requests to the server other than for characters +func _server_requests() -> Array[String]: + return _server.requested_paths.filter( + func(path: String) -> bool: return !path.begins_with("/character")) + + +func _use_folder(path: String) -> void: + _settings.use_music_folder(path) + await _runner.simulate_frames(2) + + +# Presses Next and waits until the song plays. Another playing song is stopped first, +# which takes half a second. +func _next() -> void: + _press("SongControls/NextButton") + await await_millis(700) + + +func _now_playing() -> Array[String]: + var now_playing: Node = _quiz.get_node("NowPlaying") + return [(now_playing.get_node("SoundtrackLabel") as Label).text, + (now_playing.get_node("SongLabel") as Label).text] + + +func test_picking_a_folder_makes_no_server_requests() -> void: + var before: int = _server_requests().size() + + await _use_folder(_soundtrack_folder()) + await _play_first_song() + _press("SongControls/ShowAnswerButton") + _press("QuizToolbar/SoundTestButton") + _press("QuizToolbar/ResetPlaylistButton") + await _runner.simulate_frames(2) + (_window.get_node("%AboutPopup") as AboutPopup).open() + + assert_array(_server_requests().slice(before)).is_empty() + + +func test_next_plays_a_song_from_the_folder() -> void: + await _use_folder(_soundtrack_folder()) + + await _play_first_song() + + var playing: SongObject = Playlist.song_object_array[Playlist.currently_playing_song] + assert_str(playing.song_path).starts_with(_root) + assert_bool((_quiz.get_node("MusicPlayer") as MusicPlayer).audio_player.playing).is_true() + + +func test_show_answer_shows_the_subfolder_and_the_song() -> void: + await _use_folder(_soundtrack_folder()) + await _play_first_song() + + _press("SongControls/ShowAnswerButton") + + var song: SongObject = Playlist.song_object_array[Playlist.currently_playing_song] + assert_array(_now_playing()).contains_exactly([song.soundtrack_title, song.song_title]) + assert_array(["Chrono Trigger", "Final Fantasy VI"]).contains([song.soundtrack_title]) + assert_bool(song.song_title.ends_with(".mp3")).is_false() + + +func test_points_are_given_as_usual() -> void: + await _use_folder(_soundtrack_folder()) + _add_player("Crono") + await _play_first_song() + + await _give_point_with_keybind(1) + + assert_array(_scores()).contains_exactly([1]) + assert_array(Playlist.song_object_array[0].players_given_point).has_size(1) + + +func test_inspiration_list_and_search_show_the_subfolders() -> void: + await _use_folder(_soundtrack_folder()) + + assert_array(_quiz.inspiration_list.soundtracks) \ + .contains_exactly(["Chrono Trigger", "Final Fantasy VI"]) + assert_array((_window.get_node("SearchPopup") as SearchPopup).soundtracks) \ + .contains_exactly(["Chrono Trigger", "Final Fantasy VI"]) + + +func test_server_features_are_off_and_the_folder_is_shown() -> void: + await _use_folder(_soundtrack_folder()) + + assert_bool(_top_bar.sync_button.disabled).is_true() + assert_bool(_top_bar.qr_button.disabled).is_true() + assert_bool(_top_bar.statistics_tab.disabled).is_true() + assert_bool(_top_bar.offline_label.visible).is_true() + assert_str(_top_bar.offline_label.text).is_equal("Offline: Music") + assert_bool(_settings.select_server_button.disabled).is_true() + assert_bool(_settings.add_to_database_button.disabled).is_true() + + +func test_picking_a_folder_on_the_statistics_tab_goes_to_the_quiz() -> void: + _window.show_tab(&"statistics") + + await _use_folder(_soundtrack_folder()) + + assert_bool(_quiz.visible).is_true() + + +func test_reset_playlist_starts_an_empty_playlist() -> void: + await _use_folder(_soundtrack_folder()) + await _play_first_song() + await _next() + + _press("QuizToolbar/ResetPlaylistButton") + await _runner.simulate_frames(2) + + assert_int(Playlist.currently_playing_song).is_equal(-1) + assert_bool(Playlist.song_object_array.all( + func(song: SongObject) -> bool: return !song.has_played)).is_true() + + +func test_prepared_quiz_plays_in_order_and_shows_only_the_title() -> void: + await _use_folder(_prepared_quiz()) + assert_bool(_settings.play_order_row.visible).is_true() + + await _play_first_song() + _press("SongControls/ShowAnswerButton") + + assert_array(_now_playing()).contains_exactly(["", "01. Opening"]) + var row: SongRow = _quiz.music_list_panel.get_node("%MusicList").get_child(0) + assert_str(row.song_list_label.text).starts_with("1. 01. Opening") + assert_array(_quiz.inspiration_list.soundtracks).is_empty() + + +func test_prepared_quiz_stops_after_the_last_song() -> void: + await _use_folder(_prepared_quiz()) + var titles: Array[String] = [] + + for i: int in 3: + await _next() + titles.append(Playlist.song_object_array[Playlist.currently_playing_song].song_title) + + assert_array(titles).contains_exactly( + ["01. Opening", "02. Ace Combat 7 - Daredevil", "10. Banjo Tooie - Cloud Race"]) + assert_int(Playlist.song_object_array.size()).is_equal(3) + assert_str((_quiz.get_node("SongControls/NextButton") as Button).text) \ + .is_equal("Show winner") + + +func test_prepared_quiz_at_random_plays_every_song() -> void: + await _use_folder(_prepared_quiz()) + + _settings.play_order_button.select(MusicFolder.PlayOrder.RANDOM) + _settings.play_order_button.item_selected.emit(MusicFolder.PlayOrder.RANDOM) + await _runner.simulate_frames(2) + for i: int in 3: + await _next() + + assert_int(Settings.music_folder.play_order).is_equal(MusicFolder.PlayOrder.RANDOM) + var titles: Array = Playlist.song_object_array.map( + func(song: SongObject) -> String: return song.song_title) + assert_array(titles).contains_exactly_in_any_order( + ["01. Opening", "02. Ace Combat 7 - Daredevil", "10. Banjo Tooie - Cloud Race"]) + + +func test_play_order_is_only_shown_for_a_prepared_quiz() -> void: + await _use_folder(_soundtrack_folder()) + + assert_bool(_settings.play_order_row.visible).is_false() + + +func test_use_server_goes_back_to_the_server() -> void: + await _use_folder(_soundtrack_folder()) + var before: int = _server_requests().size() + + _settings.use_server() + await _runner.simulate_frames(2) + + assert_bool(Settings.is_offline()).is_false() + assert_int(_server_requests().size()).is_greater(before) + assert_bool(_top_bar.sync_button.disabled).is_false() + assert_bool(_top_bar.offline_label.visible).is_false() + assert_bool(_settings.use_server_button.visible).is_false() + + +func test_folder_without_songs_is_not_used() -> void: + DirAccess.make_dir_recursive_absolute(_root.path_join("Empty")) + + await _use_folder(_root.path_join("Empty")) + + assert_bool(Settings.is_offline()).is_false() + assert_bool(_settings.folder_error_label.visible).is_true() + assert_str(_settings.folder_error_label.text).is_equal("No songs found in Empty") + + +func test_settings_panel_fits_its_rows_and_stays_centred() -> void: + _settings.open() + await _use_folder(_prepared_quiz()) + await _runner.simulate_frames(1) + + var panel: Rect2 = _settings.settings_panel.get_global_rect() + assert_bool(panel.encloses(_settings.rows.get_global_rect())).is_true() + assert_float(_settings.rows.get_combined_minimum_size().y) \ + .is_less_equal(_settings.rows.size.y) + assert_float(panel.get_center().y).is_equal_approx(540.0, 1.0) + + +func test_characters_still_come_from_the_server() -> void: + var before: int = _server.count("/character/details") + + await _use_folder(_soundtrack_folder()) + + assert_int(_server.count("/character/details")).is_greater(before) + + +func _play_whole_quiz() -> void: + await _play_first_song() + for i: int in 2: + await _next() + + +func test_prepared_quiz_has_no_winning_score() -> void: + Settings.winning_score = 1 + var crono: PlayerObject = _add_player("Crono") + await _use_folder(_prepared_quiz()) + await _play_first_song() + + await _give_point_with_keybind(1) + await _give_point_with_keybind(1) + + assert_int(crono.player_score).is_equal(2) + assert_bool(_quiz.winner_popup.visible).is_false() + + +func test_next_shows_the_winner_after_the_last_song() -> void: + _add_player("Crono") + _add_player("Marle") + await _use_folder(_prepared_quiz()) + await _play_first_song() + await _give_point_with_keybind(2) + await _play_whole_quiz_rest() + + var next_button: Button = _quiz.get_node("SongControls/NextButton") + assert_str(next_button.text).is_equal("Show winner") + assert_bool(next_button.disabled).is_false() + next_button.pressed.emit() + + assert_bool(_quiz.winner_popup.visible).is_true() + assert_str(_quiz.winner_popup.winner_label.text).is_equal("Marle won with 1 point!!") + + +func _play_whole_quiz_rest() -> void: + for i: int in 2: + await _next() + + +func test_tied_players_all_win() -> void: + _add_player("Crono") + _add_player("Marle") + _add_player("Lucca") + await _use_folder(_prepared_quiz()) + await _play_first_song() + await _give_point_with_keybind(1) + await _give_point_with_keybind(3) + await _play_whole_quiz_rest() + + _press("SongControls/NextButton") + + assert_str(_quiz.winner_popup.winner_label.text).is_equal("Crono and Lucca won with 1 point!!") + var header: Node = _quiz.winner_popup.winner_picture.get_parent() + var pictures: Array = header.get_children().filter( + func(node: Node) -> bool: return node is TextureRect && node.visible) + assert_array(pictures).has_size(2) + var others: Array = _quiz.winner_popup.players_list.get_children().map( + func(label: Label) -> String: return label.text) + assert_array(others).contains_exactly(["3. Marle - 0 points"]) + + +func test_new_game_turns_show_winner_back_into_next() -> void: + await _use_folder(_prepared_quiz()) + await _play_whole_quiz() + + _press("QuizToolbar/ResetPlaylistButton") + await _runner.simulate_frames(2) + + var next_button: Button = _quiz.get_node("SongControls/NextButton") + assert_str(next_button.text).is_equal("Randomize new track") + await _play_first_song() + assert_str(Playlist.song_object_array[Playlist.currently_playing_song].song_title) \ + .is_equal("01. Opening") + + +func test_songs_playing_on_by_themselves_stop_at_the_end() -> void: + await _use_folder(_prepared_quiz()) + await _play_whole_quiz() + + _quiz.play_next_song() + await _runner.simulate_frames(2) + + assert_str(Playlist.song_object_array[Playlist.currently_playing_song].song_title) \ + .is_equal("10. Banjo Tooie - Cloud Race") + assert_str((_quiz.get_node("SongControls/NextButton") as Button).text) \ + .is_equal("Show winner") + + +func test_quiz_settings_hide_the_winning_score_for_a_prepared_quiz() -> void: + await _use_folder(_prepared_quiz()) + + _quiz.quiz_settings_card.open() + + assert_bool(_quiz.quiz_settings_card.winning_score_row.visible).is_false() diff --git a/tests/integration/test_offline_mode.gd.uid b/tests/integration/test_offline_mode.gd.uid new file mode 100644 index 0000000..f021f0c --- /dev/null +++ b/tests/integration/test_offline_mode.gd.uid @@ -0,0 +1 @@ +uid://bhg71e3byrwja diff --git a/tests/integration/test_scene_split.gd b/tests/integration/test_scene_split.gd index 37a8571..6a05846 100644 --- a/tests/integration/test_scene_split.gd +++ b/tests/integration/test_scene_split.gd @@ -29,7 +29,7 @@ const LOCKED_BY_SYNC: Array[String] = [ const TOP_BAR_CHILDREN: Array[String] = [ "TopPanelContainer", "Title", "SyncButton", "SettingsButton", "ShortcutsButton", - "DebugLabel", "UpdateLabel", + "DebugLabel", "UpdateLabel", "OfflineLabel", "AboutButton", "QRButton", "QuizTab", "StatisticsTab", ] diff --git a/tests/integration/test_settings_popup.gd b/tests/integration/test_settings_popup.gd index 1039e6c..fcc2ad6 100644 --- a/tests/integration/test_settings_popup.gd +++ b/tests/integration/test_settings_popup.gd @@ -52,4 +52,4 @@ func test_popup_fits_its_rows_and_is_centred() -> void: assert_bool(panel.encloses(rows.get_global_rect())).is_true() # The rows' own height fits, so the last toggle is not squeezed or cut off assert_float(rows.get_combined_minimum_size().y).is_less_equal(rows.size.y) - assert_vector(panel.get_center()).is_equal(Vector2(960, 540)) + assert_vector(panel.get_center()).is_equal_approx(Vector2(960, 540), Vector2(1, 1)) diff --git a/tests/integration/test_winner_popup.gd b/tests/integration/test_winner_popup.gd index 528afb2..b31d1f0 100644 --- a/tests/integration/test_winner_popup.gd +++ b/tests/integration/test_winner_popup.gd @@ -158,3 +158,20 @@ func test_long_game_with_five_players_stays_readable() -> void: # The whole popup fits in the window assert_bool(Rect2(Vector2.ZERO, Vector2(1920, 1080)).encloses( _popup.winner_panel.get_global_rect())).is_true() + + +func test_winners_text() -> void: + var crono: PlayerObject = PlayerObject.new("Crono") + var marle: PlayerObject = PlayerObject.new("Marle") + var lucca: PlayerObject = PlayerObject.new("Lucca") + crono.player_score = 1 + var one: Array[PlayerObject] = [crono] + var two: Array[PlayerObject] = [crono, marle] + var three: Array[PlayerObject] = [crono, marle, lucca] + var none: Array[PlayerObject] = [] + + assert_str(WinnerPopup.winners_text(one)).is_equal("Crono won with 1 point!!") + assert_str(WinnerPopup.winners_text(two)).is_equal("Crono and Marle won with 1 point!!") + assert_str(WinnerPopup.winners_text(three)) \ + .is_equal("Crono, Marle and Lucca won with 1 point!!") + assert_str(WinnerPopup.winners_text(none)).is_equal("The quiz is over!") diff --git a/tests/unit/test_audio_file.gd b/tests/unit/test_audio_file.gd new file mode 100644 index 0000000..6226775 --- /dev/null +++ b/tests/unit/test_audio_file.gd @@ -0,0 +1,45 @@ +extends GdUnitTestSuite +## Reading songs from files by their type. + + +@warning_ignore("unused_parameter") +func test_playable_types_load( + file_name: String, + type: GDScript, + test_parameters: Array = [ + ["short.mp3", AudioStreamMP3], ["short.ogg", AudioStreamOggVorbis], + ["short.wav", AudioStreamWAV], + ], +) -> void: + var stream: AudioStream = AudioFile.load("res://tests/data/" + file_name) + + assert_object(stream).is_not_null() + assert_bool(is_instance_of(stream, type)).is_true() + assert_float(stream.get_length()).is_greater(0.0) + + +func test_missing_file_gives_null() -> void: + assert_object(AudioFile.load("res://tests/data/missing.mp3")).is_null() + + +func test_other_types_give_null() -> void: + var path: String = create_temp_dir("audio_file").path_join("song.flac") + FileAccess.open(path, FileAccess.WRITE).store_buffer( + FileAccess.get_file_as_bytes("res://tests/data/short.mp3")) + + assert_object(AudioFile.load(path)).is_null() + + +func test_broken_file_gives_null() -> void: + var path: String = create_temp_dir("audio_file").path_join("broken.mp3") + FileAccess.open(path, FileAccess.WRITE).store_string("not a song") + + assert_object(AudioFile.load(path)).is_null() + + +func test_playable_types() -> void: + assert_bool(AudioFile.is_playable("a/b/Song.MP3")).is_true() + assert_bool(AudioFile.is_playable("Song.ogg")).is_true() + assert_bool(AudioFile.is_playable("Song.wav")).is_true() + assert_bool(AudioFile.is_playable("Song.flac")).is_false() + assert_bool(AudioFile.is_playable("cover.jpg")).is_false() diff --git a/tests/unit/test_audio_file.gd.uid b/tests/unit/test_audio_file.gd.uid new file mode 100644 index 0000000..93d070b --- /dev/null +++ b/tests/unit/test_audio_file.gd.uid @@ -0,0 +1 @@ +uid://crghfr0gnyngh diff --git a/tests/unit/test_character_cache.gd b/tests/unit/test_character_cache.gd index 20a304a..7a9767f 100644 --- a/tests/unit/test_character_cache.gd +++ b/tests/unit/test_character_cache.gd @@ -189,3 +189,26 @@ func test_broken_index_starts_over() -> void: assert_int(images.size()).is_equal(1) assert_int(_server.count(_image_path("Crono.png"))).is_equal(2) + + +func test_cached_pictures_are_shown_when_the_server_cannot_be_reached() -> void: + _serve({"Crono.png": 100, "Marle.png": 200}) + _load_all() + _server.respond_json("/character/details", {}, 503) + _server.respond_json("/character/list", {}, 503) + + var images: Array[Image] = _load_all() + + assert_int(images.size()).is_equal(2) + assert_int(_server.count(_image_path("Crono.png"))).is_equal(1) + assert_bool(FileAccess.file_exists(CharacterCache.folder().path_join("Marle.png"))).is_true() + + +func test_nothing_is_shown_without_a_server_or_cached_pictures() -> void: + var cache: CharacterCache = CharacterCache.new() + var listed: Array = [] + + cache.load_list(func(names: Array[String]) -> void: listed.append(names)) + + assert_array(listed).has_size(1) + assert_array(listed[0]).is_empty() diff --git a/tests/unit/test_music_folder.gd b/tests/unit/test_music_folder.gd new file mode 100644 index 0000000..059e839 --- /dev/null +++ b/tests/unit/test_music_folder.gd @@ -0,0 +1,202 @@ +extends GdUnitTestSuite +## Music folders: soundtrack folders, where songs are picked at random from subfolders, +## and prepared quizzes, where every song in the folder plays once. + +var _root: String +var _mp3: PackedByteArray + + +func before_test() -> void: + _root = create_temp_dir("music_folder_%d" % randi()) + _mp3 = FileAccess.get_file_as_bytes("res://tests/data/short.mp3") + + +func _song(relative_path: String) -> void: + _file(relative_path, _mp3) + + +func _file(relative_path: String, bytes: PackedByteArray) -> void: + var path: String = _root.path_join(relative_path) + DirAccess.make_dir_recursive_absolute(path.get_base_dir()) + FileAccess.open(path, FileAccess.WRITE).store_buffer(bytes) + + +func _folder(relative_path: String = "") -> MusicFolder: + return MusicFolder.scan(_root.path_join(relative_path)) + + +func test_folder_with_subfolders_is_a_soundtrack_folder() -> void: + _song("Chrono Trigger/Corridors of Time.mp3") + _song("Final Fantasy VI/Dancing Mad.mp3") + + var folder: MusicFolder = _folder() + + assert_int(folder.kind).is_equal(MusicFolder.Kind.SOUNDTRACKS) + assert_array(folder.soundtrack_names()) \ + .contains_exactly(["Chrono Trigger", "Final Fantasy VI"]) + + +func test_songs_at_the_top_of_a_soundtrack_folder_are_left_out() -> void: + _song("Chrono Trigger/Corridors of Time.mp3") + _song("Loose song.mp3") + + var folder: MusicFolder = _folder() + + for i: int in 10: + assert_str(folder.next_song().soundtrack_title).is_equal("Chrono Trigger") + + +func test_folder_with_only_songs_is_a_prepared_quiz() -> void: + _song("01. Opening.mp3") + _song("02. Ace Combat 7 - Daredevil.mp3") + + var folder: MusicFolder = _folder() + + assert_int(folder.kind).is_equal(MusicFolder.Kind.PREPARED_QUIZ) + assert_int(folder.quiz_size()).is_equal(2) + assert_array(folder.soundtrack_names()).is_empty() + + +func test_folder_without_songs_is_empty() -> void: + _file("notes.txt", "hello".to_utf8_buffer()) + _file("Chrono Trigger/cover.jpg", "jpg".to_utf8_buffer()) + + assert_int(_folder().kind).is_equal(MusicFolder.Kind.EMPTY) + + +func test_missing_folder_is_empty() -> void: + assert_int(_folder("missing").kind).is_equal(MusicFolder.Kind.EMPTY) + + +func test_hidden_folders_and_files_are_left_out() -> void: + _song(".sync/Archive/Old.mp3") + _song("Chrono Trigger/.hidden.mp3") + _song("Chrono Trigger/Corridors of Time.mp3") + + var folder: MusicFolder = _folder() + + assert_array(folder.soundtrack_names()).contains_exactly(["Chrono Trigger"]) + for i: int in 10: + assert_str(folder.next_song().song_title).is_equal("Corridors of Time") + + +func test_hidden_folder_alone_does_not_make_a_soundtrack_folder() -> void: + _song(".sync/Archive/Old.mp3") + _song("01. Opening.mp3") + + assert_int(_folder().kind).is_equal(MusicFolder.Kind.PREPARED_QUIZ) + + +func test_songs_in_deeper_folders_belong_to_their_soundtrack() -> void: + _song("Xenogears/Disc 2/Small Two of Pieces.mp3") + + var song: SongObject = _folder().next_song() + + assert_str(song.soundtrack_title).is_equal("Xenogears") + assert_str(song.song_title).is_equal("Small Two of Pieces") + + +func test_picked_song_has_its_file_and_names_without_the_file_type() -> void: + _song("Chrono Trigger/Corridors of Time.mp3") + + var song: SongObject = _folder().next_song() + + assert_str(song.song_path).is_equal(_root.path_join("Chrono Trigger/Corridors of Time.mp3")) + assert_str(song.soundtrack_title).is_equal("Chrono Trigger") + assert_str(song.song_title).is_equal("Corridors of Time") + + +func test_soundtrack_folder_can_play_a_song_again() -> void: + _song("Chrono Trigger/Corridors of Time.mp3") + var folder: MusicFolder = _folder() + + for i: int in 3: + assert_object(folder.next_song()).is_not_null() + + +func test_files_that_cannot_be_played_are_skipped() -> void: + _song("Chrono Trigger/Corridors of Time.mp3") + _file("Chrono Trigger/Lost.flac", _mp3) + _file("Chrono Trigger/Broken.mp3", "not a song".to_utf8_buffer()) + _file("Chrono Trigger/cover.jpg", "jpg".to_utf8_buffer()) + var folder: MusicFolder = _folder() + + for i: int in 10: + assert_str(folder.next_song().song_title).is_equal("Corridors of Time") + + +func test_soundtrack_with_only_unplayable_files_is_left_out() -> void: + _song("Chrono Trigger/Corridors of Time.mp3") + _file("Tunic/Subterra.flac", _mp3) + + assert_array(_folder().soundtrack_names()).contains_exactly(["Chrono Trigger"]) + + +func test_prepared_quiz_plays_in_natural_order_and_then_stops() -> void: + for file_name: String in ["10. Ten.mp3", "2. Two.mp3", "01. Warhammer.mp3", + "01. Opening.mp3"]: + _song(file_name) + var folder: MusicFolder = _folder() + + var titles: Array[String] = [] + for i: int in 4: + titles.append(folder.next_song().song_title) + + assert_array(titles).contains_exactly(["01. Opening", "01. Warhammer", "2. Two", "10. Ten"]) + assert_object(folder.next_song()).is_null() + + +func test_prepared_quiz_has_no_soundtrack() -> void: + _song("04. Ace Combat 7 - Daredevil.mp3") + + var song: SongObject = _folder().next_song() + + assert_str(song.soundtrack_title).is_empty() + assert_str(song.song_title).is_equal("04. Ace Combat 7 - Daredevil") + + +func test_random_prepared_quiz_plays_every_song_once() -> void: + for i: int in 8: + _song("%02d. Song.mp3" % i) + var folder: MusicFolder = _folder() + folder.set_play_order(MusicFolder.PlayOrder.RANDOM) + + var titles: Array[String] = [] + for i: int in 8: + titles.append(folder.next_song().song_title) + + assert_array(titles).has_size(8) + for i: int in 8: + assert_array(titles).contains(["%02d. Song" % i]) + assert_object(folder.next_song()).is_null() + + +func test_restart_plays_the_prepared_quiz_from_the_start() -> void: + _song("01. One.mp3") + _song("02. Two.mp3") + var folder: MusicFolder = _folder() + folder.next_song() + folder.next_song() + + folder.restart() + + assert_str(folder.next_song().song_title).is_equal("01. One") + + +func test_unplayable_file_in_a_prepared_quiz_is_skipped() -> void: + _song("01. One.mp3") + _file("02. Broken.mp3", "not a song".to_utf8_buffer()) + _song("03. Three.mp3") + var folder: MusicFolder = _folder() + + assert_str(folder.next_song().song_title).is_equal("01. One") + assert_str(folder.next_song().song_title).is_equal("03. Three") + assert_object(folder.next_song()).is_null() + + +func test_display_name_is_the_folder_name() -> void: + _song("Musiktävling 2025/01. Opening.mp3") + + var folder: MusicFolder = _folder("Musiktävling 2025/") + + assert_str(folder.display_name()).is_equal("Musiktävling 2025") diff --git a/tests/unit/test_music_folder.gd.uid b/tests/unit/test_music_folder.gd.uid new file mode 100644 index 0000000..29c846d --- /dev/null +++ b/tests/unit/test_music_folder.gd.uid @@ -0,0 +1 @@ +uid://ca61kypdrbj8j