Files
MusicPlayer/popups/settings_popup.gd
T
SansanandClaude Opus 5.5 c03c0e4efe
Build / test (push) Successful in 1m51s
Build / build (push) Successful in 1m14s
Add Clear cache to the settings
A new row, "Downloaded pictures and songs", shows how much space the cached
character pictures, covers and downloaded songs take, with a Clear cache
button. Clearing deletes them, but keeps the songs the current quiz uses,
the settings, config.ini and the saved quiz. The characters are loaded
again at once; covers come back when they are shown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 19:50:58 +02:00

292 lines
12 KiB
GDScript

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)