class_name SettingsPopup extends BasePopup ## The application settings: which MusicServer or music folder to play from, the theme, ## fullscreen, fewer animations, how many songs to download ahead, how random songs are ## picked and whether plays count in the statistics, and a button that opens the folder ## the settings are kept in. 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. ## A folder of characters (see [FolderCharacters]) is used the same way; without one, a ## music folder's own [code]characters[/code] folder is used, or else the server's. ## ## The quiz settings, such as the winning score, are in [QuizSettingsCard]. ## 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 ## Emitted when a folder of characters is picked or Use server is pressed for them. signal characters_changed ## Emitted when Clear cache is pressed, for the caches to delete their files. signal clear_cache_requested ## 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 @onready var reduce_animations_button: CheckButton = %ReduceAnimationsButton @onready var theme_button: OptionButton = %ThemeButton @onready var cache_label: Label = %CacheLabel @onready var lower_cache_button: Button = %LowerCacheButton @onready var increase_cache_button: Button = %IncreaseCacheButton @onready var random_mode_button: OptionButton = %RandomModeButton @onready var add_to_database_button: CheckButton = %AddToDatabaseCheckButton @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 @onready var characters_label: Label = %CharactersLabel @onready var choose_characters_button: Button = %ChooseCharactersButton @onready var use_server_characters_button: Button = %UseServerCharactersButton @onready var characters_error_label: Label = %CharactersErrorLabel @onready var character_folder_dialog: FileDialog = %CharacterFolderDialog @onready var open_config_folder_button: Button = %OpenConfigFolderButton @onready var cache_size_label: Label = %CacheSizeLabel @onready var clear_cache_button: Button = %ClearCacheButton ## Shows a folder in the system's file manager. The tests replace it, so no file manager ## opens while they run. var show_folder: Callable = _show_in_file_manager # 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: for server: String in Settings.servers: select_server_button.add_item(server) for entry: Dictionary in Settings.THEMES: theme_button.add_item(entry.name) theme_button.select(Settings.THEMES.find(Settings.theme_entry(Settings.theme_id))) theme_button.item_selected.connect(select_theme) fullscreen_button.button_pressed = Settings.fullscreen select_server_button.select(Settings.selected_server) cache_label.text = str(Playlist.number_of_tracks_to_preload) fullscreen_button.pressed.connect(fullscreen) reduce_animations_button.button_pressed = Settings.reduce_animations reduce_animations_button.toggled.connect(set_reduce_animations) choose_folder_button.pressed.connect(folder_dialog.popup_centered) folder_dialog.dir_selected.connect(use_music_folder) use_server_button.pressed.connect(use_server) choose_characters_button.pressed.connect(character_folder_dialog.popup_centered) character_folder_dialog.dir_selected.connect(use_character_folder) use_server_characters_button.pressed.connect(use_server_characters) open_config_folder_button.pressed.connect(open_config_folder) open_config_folder_button.tooltip_text = Settings.config_dir() clear_cache_button.pressed.connect(clear_cache) 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) random_mode_button.item_selected.connect( func(mode: int) -> void: Settings.random_mode = mode as Settings.RandomMode) add_to_database_button.toggled.connect(func(on: bool) -> void: Settings.add_to_stats = on) 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() ## Switches to the theme at [param index] in [constant Settings.THEMES] at once. func select_theme(index: int) -> void: Settings.apply_theme(Settings.THEMES[index].id) ## Turns every animation off or on, and saves it. func set_reduce_animations(on: bool) -> void: Settings.reduce_animations = on Settings.save_settings() func fullscreen() -> void: Settings.set_fullscreen(!Settings.fullscreen) ## 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() ## Shows the folder with the config file, the saved settings and the cached characters ## and songs in the system's file manager. func open_config_folder() -> void: var dir: String = Settings.config_dir() DirAccess.make_dir_recursive_absolute(dir) show_folder.call(dir) ## Deletes the downloaded character pictures, covers and songs, except the songs the ## quiz uses, and shows how much space that freed. func clear_cache() -> void: var freed: int = cache_size() clear_cache_requested.emit() Playlist.clear_song_files(Playlist.song_files_in_use()) cache_size_label.text = "Cleared " + size_text(freed) clear_cache_button.disabled = true ## Shows how much space the downloaded files take. func show_cache_size() -> void: var size: int = cache_size() cache_size_label.text = size_text(size) clear_cache_button.disabled = size == 0 ## How many bytes the downloaded character pictures, covers and songs take, leaving out ## the songs the quiz uses, which clearing keeps. static func cache_size() -> int: return FileCache.folder_size(CharacterCache.folder()) \ + FileCache.folder_size(CoverCache.folder()) \ + FileCache.folder_size(Playlist.songs_folder(), Playlist.song_files_in_use()) ## [param bytes] for people, e.g. "350 KB" or "14.2 MB". static func size_text(bytes: int) -> String: if bytes == 0: return "Nothing" if bytes < 1 << 20: return "%d KB" % ceili(bytes / 1024.0) return "%.1f MB" % (bytes / float(1 << 20)) ## Reads the characters from the folder at [param path] instead of the server. A folder ## without pictures is not used, and the popup says so. func use_character_folder(path: String) -> void: if FolderCharacters.picture_names(path).is_empty(): characters_error_label.text = "No pictures found in " + path.trim_suffix("/").get_file() characters_error_label.visible = true _fit_to_rows() return Settings.character_folder = path.trim_suffix("/") _show_music_source() characters_changed.emit() ## Uses the server's characters again, or the music folder's own if it has them. func use_server_characters() -> void: Settings.character_folder = "" _show_music_source() characters_changed.emit() func lower_cache() -> void: Playlist.number_of_tracks_to_preload = maxi(Playlist.number_of_tracks_to_preload - 1, 1) cache_label.text = str(Playlist.number_of_tracks_to_preload) func increase_cache() -> void: Playlist.number_of_tracks_to_preload += 1 cache_label.text = str(Playlist.number_of_tracks_to_preload) func select_server(new_server: int) -> void: GameLog.debug("select_server") _use_server(new_server) ## Picks [param new_server] in the list and uses it. func set_selected_server(new_server: int) -> void: GameLog.debug("set_selected_server") select_server_button.select(new_server) _use_server(new_server) func _use_server(new_server: int) -> void: Settings.default_path = Settings.servers[new_server] Settings.selected_server = new_server GameLog.debug("Settings.default_path: " + Settings.default_path) server_changed.emit() # Winning a game turns statistics off, so the toggles are refreshed every time this opens func _on_visibility_changed() -> void: if visible: _update_fullscreen_button() show_cache_size() random_mode_button.select(Settings.random_mode) add_to_database_button.set_pressed_no_signal(Settings.add_to_stats) else: folder_error_label.visible = false characters_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 characters_error_label.visible = false use_server_characters_button.visible = !Settings.character_folder.is_empty() characters_label.tooltip_text = Settings.characters_dir() if !Settings.character_folder.is_empty(): characters_label.text = Settings.character_folder.get_file() elif !Settings.characters_dir().is_empty(): characters_label.text = "From " + folder.display_name() else: characters_label.text = "From the server" for control: BaseButton in [select_server_button, random_mode_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 _show_in_file_manager(dir: String) -> void: var error: Error = OS.shell_show_in_file_manager(dir) if error != OK: # Not every platform has a file manager to show a folder in; open it instead error = OS.shell_open(dir) if error != OK: GameLog.warning("Could not open %s: %s" % [dir, error_string(error)]) func _update_fullscreen_button() -> void: fullscreen_button.set_pressed_no_signal(Settings.fullscreen)