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")