"Version X is available" in the top bar is now a button that opens an Update popup: - Update and restart gets the download link from the server (the redirect is not followed, as the Gitea link needs no token), downloads the new game next to the running one with a progress bar, checks it (complete, and an MZ or ELF file), swaps it in and restarts. The quiz is saved first and continues. On Windows the running .exe is renamed to .old, put back if the swap fails, and deleted at the next start. - Download in browser opens the .exe, the Linux package, or the release page on other platforms. - From the editor, an unwritable folder or another platform, only the browser download is offered, with the reason. The message now fits between the tabs and the buttons in every theme; in Nord and Christmas it ran into Sync. The macOS export preset is removed, and the unused download_client_* functions are replaced by fetch_client_download_url. Closes #13 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
118 lines
4.8 KiB
GDScript
118 lines
4.8 KiB
GDScript
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
|
|
signal shortcuts_pressed
|
|
signal about_pressed
|
|
signal qr_pressed
|
|
## Emitted when a tab is chosen: [code]&"quiz"[/code] or [code]&"statistics"[/code].
|
|
signal tab_selected(tab: StringName)
|
|
## Emitted when "Version X is available" is clicked, with the newest version.
|
|
signal update_pressed(latest_version: String)
|
|
|
|
## Space between the message in the middle and the tabs and buttons around it.
|
|
const MIDDLE_GAP: float = 16.0
|
|
|
|
## The newest release the server knows of, once asked, or empty.
|
|
var latest_version: String = ""
|
|
|
|
@onready var sync_button: Button = %SyncButton
|
|
@onready var debug_label: Label = $DebugLabel
|
|
@onready var update_box: HBoxContainer = $UpdateBox
|
|
@onready var update_button: Button = %UpdateButton
|
|
@onready var offline_label: Label = $OfflineLabel
|
|
@onready var qr_button: Button = %QRButton
|
|
@onready var quiz_tab: Button = %QuizTab
|
|
@onready var statistics_tab: Button = %StatisticsTab
|
|
|
|
|
|
func _ready() -> void:
|
|
# Debug builds use the local server, see Settings
|
|
debug_label.visible = Settings.is_debug
|
|
sync_button.pressed.connect(sync_pressed.emit)
|
|
%SettingsButton.pressed.connect(settings_pressed.emit)
|
|
%ShortcutsButton.pressed.connect(shortcuts_pressed.emit)
|
|
%AboutButton.pressed.connect(about_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_button.pressed.connect(func() -> void: update_pressed.emit(latest_version))
|
|
# The buttons are as wide as the theme makes them, so the message fits between
|
|
statistics_tab.item_rect_changed.connect(_fit_middle)
|
|
sync_button.item_rect_changed.connect(_fit_middle)
|
|
_fit_middle()
|
|
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_box.visible = false
|
|
|
|
|
|
## Asks the server for the newest MusicPlayer release and tells the user when it is newer
|
|
## than this one. Not asked while a music folder is used.
|
|
func check_for_update() -> void:
|
|
if Settings.is_offline():
|
|
update_box.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) \
|
|
&& !Settings.is_offline()
|
|
update_box.visible = newer
|
|
if newer:
|
|
latest_version = latest
|
|
update_button.text = "Version %s is available" % latest
|
|
update_button.tooltip_text = "You have %s. Click to update." % Settings.version
|
|
|
|
|
|
# Fits "Version X is available" and the offline label between the tabs and the buttons
|
|
func _fit_middle() -> void:
|
|
var left: float = statistics_tab.position.x + statistics_tab.size.x + MIDDLE_GAP
|
|
var right: float = sync_button.global_position.x - global_position.x - MIDDLE_GAP
|
|
for middle: Control in [update_box, offline_label]:
|
|
middle.position.x = left
|
|
middle.size.x = maxf(right - left, 0.0)
|
|
|
|
|
|
## Whether version [param latest] is newer than [param current], comparing numbers such as
|
|
## [code]1.10.0[/code] part by part. A leading "v" is ignored, and missing parts count as 0.
|
|
static func is_newer_version(latest: String, current: String) -> bool:
|
|
var latest_parts: PackedStringArray = latest.strip_edges().trim_prefix("v").split(".")
|
|
var current_parts: PackedStringArray = current.strip_edges().trim_prefix("v").split(".")
|
|
for i: int in maxi(latest_parts.size(), current_parts.size()):
|
|
var a: int = latest_parts[i].to_int() if i < latest_parts.size() else 0
|
|
var b: int = current_parts[i].to_int() if i < current_parts.size() else 0
|
|
if a != b:
|
|
return a > b
|
|
return false
|
|
|
|
|
|
## Shows [param tab] as the chosen tab, without emitting [signal tab_selected].
|
|
func select_tab(tab: StringName) -> void:
|
|
quiz_tab.set_pressed_no_signal(tab == &"quiz")
|
|
statistics_tab.set_pressed_no_signal(tab == &"statistics")
|