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 0000000..4a6e806 Binary files /dev/null and b/tests/data/short.ogg differ diff --git a/tests/data/short.wav b/tests/data/short.wav new file mode 100644 index 0000000..cb1d4f3 Binary files /dev/null and b/tests/data/short.wav differ diff --git a/tests/fakes/ui_tour.gd b/tests/fakes/ui_tour.gd index d52c7b0..d96e23e 100644 --- a/tests/fakes/ui_tour.gd +++ b/tests/fakes/ui_tour.gd @@ -296,6 +296,56 @@ func run(on_state: Callable) -> 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