Files
MusicPlayer/main/main_window.gd
T
SansanandClaude Opus 5.5 c86dacb890
Build / test (pull_request) Successful in 2m3s
Build / build (pull_request) Skipped
Add a soundtrack by picking or dropping a folder (#50)
"Add soundtrack…" in the statistics toolbar, or a folder dropped anywhere on
the window, opens a popup that lists the folder's songs and uploads them to
the server, which adds the soundtrack at once (Sansan/MusicServer#33).

- The name comes from the folder and can be changed; characters the server
  refuses are replaced.
- Only MP3s are uploaded, as the server takes no other; other song files
  are listed greyed out as left out. Hidden files are skipped.
- Disc folders are flattened with the disc's number first ("1-01 Title.mp3"),
  as the server wants one folder. With one disc folder, names stay as
  they are.
- Songs are sent one at a time with a progress bar, reading one file at a
  time. A failed song is sent again twice, then the upload is cancelled
  and the server's reason shown. Closing the popup cancels it too.
- Not offered while the server syncs, offline, or without MP3s.
- Afterwards search, the inspiration list and the statistics reload.

The API types come from the server's spec (Upload, UploadFile,
UploadRequest).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 21:43:59 +02:00

210 lines
7.6 KiB
GDScript

class_name MainWindow
extends Control
## The main window: the top bar, the popups opened from it, and below it the quiz or the
## statistics, depending on the chosen tab.
##
## Everything that is not part of a quiz lives here.
## How far a tab slides in from the side when it is chosen.
const TAB_SLIDE: float = 60.0
## Dims the window behind an open popup and keeps clicks from reaching the buttons under it.
var popup_backdrop: PopupBackdrop
@onready var top_bar: TopBar = $TopBar
@onready var quiz: Quiz = $Quiz
@onready var search_popup: SearchPopup = $SearchPopup
@onready var sync_popup: SyncPopup = %SyncPopup
@onready var shortcuts_popup: ShortcutsPopup = %ShortcutsPopup
@onready var qr_popup: QRPopup = %QRPopup
@onready var about_popup: AboutPopup = %AboutPopup
@onready var log_popup: LogPopup = %LogPopup
@onready var settings_popup: SettingsPopup = %SettingsPopup
@onready var update_popup: UpdatePopup = %UpdatePopup
@onready var upload_popup: UploadPopup = %UploadPopup
@onready var statistics_view: StatisticsView = $StatisticsView
@onready var soundtrack_stats_popup: SoundtrackStatsPopup = $SoundtrackStatsPopup
func _ready() -> void:
popup_backdrop = PopupBackdrop.add_to(self, _popups())
# Every button grows a little under the mouse and shrinks when clicked
ButtonBounce.add_to_every_button(get_tree())
if OS.has_feature("macos"):
GameLog.debug("is_mac")
quiz.search_pressed.connect(show_search)
top_bar.sync_pressed.connect(sync_soundtracks)
top_bar.tab_selected.connect(show_tab)
top_bar.shortcuts_pressed.connect(show_shortcuts)
top_bar.about_pressed.connect(show_about)
top_bar.qr_pressed.connect(show_qr)
top_bar.settings_pressed.connect(settings_popup.open)
top_bar.update_pressed.connect(update_popup.show_update)
update_popup.restarting.connect(quiz.save_now)
statistics_view.add_soundtrack_pressed.connect(upload_popup.choose_folder)
upload_popup.soundtrack_added.connect(_on_soundtrack_added)
get_window().files_dropped.connect(on_files_dropped)
settings_popup.server_changed.connect(quiz.on_server_changed)
settings_popup.characters_changed.connect(quiz.character_select_popup.load_characters)
settings_popup.clear_cache_requested.connect(clear_cache)
settings_popup.visibility_changed.connect(_on_settings_popup_visibility_changed)
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(_on_music_source_changed)
quiz.event_logged.connect(log_popup.add_log_row)
statistics_view.soundtrack_selected.connect(soundtrack_stats_popup.show_soundtrack)
soundtrack_stats_popup.covers = statistics_view.covers
quiz.now_playing.covers = statistics_view.covers
search_popup.soundtracks_loaded.connect(_remember_cover_times)
# New alternative names are found by the statistics search and the Search popup
soundtrack_stats_popup.alternative_names_changed.connect(
statistics_view.update_alternative_names)
soundtrack_stats_popup.alternative_names_changed.connect(
func(_soundtrack: Soundtrack) -> void: search_popup.get_list_of_soundtracks())
soundtrack_stats_popup.cover_changed.connect(statistics_view.update_cover)
# An update leaves the old version next to the game, to delete once the new one runs
if !OS.has_feature("editor"):
Updater.remove_old_files(OS.get_executable_path())
# A saved quiz may have been played from a music folder
top_bar.update_offline_state()
search_popup.get_list_of_soundtracks()
show_about_if_new_version()
## Deletes the cached character pictures and covers. The characters are loaded again at
## once, so the character select has its pictures; covers come when they are shown.
func clear_cache() -> void:
quiz.character_select_popup.character_cache.clear()
statistics_view.covers.clear()
quiz.character_select_popup.load_characters()
## Offers to upload a folder dropped on the window as a new soundtrack. Dropped songs
## stand for the folder they are in.
func on_files_dropped(paths: PackedStringArray) -> void:
if paths.is_empty():
return
var folder: String = paths[0]
for path: String in paths:
if DirAccess.dir_exists_absolute(path):
folder = path
break
if !DirAccess.dir_exists_absolute(folder):
folder = folder.get_base_dir()
upload_popup.show_folder(folder)
# A new soundtrack is found by search, listed for inspiration and in the statistics
func _on_soundtrack_added(_soundtrack: SoundtrackWithSongList) -> void:
search_popup.get_list_of_soundtracks()
quiz.inspiration_list.load_soundtracks()
if statistics_view.visible:
statistics_view.refresh()
# The quiz finds a soundtrack's cover by the cover times in the soundtrack list
func _remember_cover_times(soundtracks: Array[Soundtrack]) -> void:
var times: Dictionary = {}
for soundtrack: Soundtrack in soundtracks:
times[soundtrack.uuid] = soundtrack.cover_modified
statistics_view.covers.set_cover_times(times)
# 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:
return
Settings.last_seen_version = Settings.version
Settings.save_settings()
show_about()
func _input(event: InputEvent) -> void:
if event is InputEventKey and event.pressed:
if event.alt_pressed && event.keycode == KEY_ENTER:
GameLog.debug("Alt + Enter pressed")
Settings.set_fullscreen(!Settings.fullscreen)
if event.alt_pressed && event.keycode == KEY_K:
shortcuts_popup.toggle()
if event.alt_pressed && event.keycode == KEY_L:
log_popup.toggle()
func _popups() -> Array[BasePopup]:
return [search_popup, sync_popup, qr_popup, shortcuts_popup, about_popup, log_popup,
soundtrack_stats_popup, settings_popup, update_popup, upload_popup]
# How many songs to download ahead can be changed in the settings. Waiting until the
# settings are closed avoids starting downloads while the number is clicked up and down.
func _on_settings_popup_visibility_changed() -> void:
if !settings_popup.visible:
quiz.song_loader.fill_cache()
func show_search() -> void:
GameLog.debug("show_search")
search_popup.toggle()
func show_about() -> void:
GameLog.debug("show_about")
about_popup.open()
func show_shortcuts() -> void:
GameLog.debug("show_shortcuts")
shortcuts_popup.open()
func show_qr() -> void:
GameLog.debug("show_qr")
qr_popup.open()
## Shows the quiz for [code]&"quiz"[/code] or the statistics for [code]&"statistics"[/code].
## A hidden quiz keeps playing its song.
func show_tab(tab: StringName) -> void:
GameLog.debug("show_tab ", tab)
top_bar.select_tab(tab)
var was_statistics: bool = statistics_view.visible
statistics_view.visible = tab == &"statistics"
quiz.visible = !statistics_view.visible
# The tab that comes in slides from its side: statistics from the right
if statistics_view.visible && !was_statistics:
Anim.slide_in(statistics_view, Vector2(TAB_SLIDE, 0))
elif quiz.visible && was_statistics:
Anim.slide_in(quiz, Vector2(-TAB_SLIDE, 0))
if statistics_view.visible:
statistics_view.refresh()
func sync_soundtracks() -> void:
GameLog.debug("sync_soundtracks")
sync_popup.start_sync()
func _on_sync_started() -> void:
GameLog.debug("_on_sync_started")
top_bar.sync_button.disabled = false
quiz.lock_for_sync()
func _on_sync_finished() -> void:
GameLog.debug("_on_sync_finished")
search_popup.get_list_of_soundtracks()
quiz.on_sync_finished()
if statistics_view.visible:
statistics_view.refresh()