Split the main window into TopBar and Quiz scenes
The quiz (song, playlist, players, music player and the popups opened from it) moves into Quiz.tscn and the top panel into TopBar.tscn. The main window keeps only the top bar, the quiz and the popups opened from the top bar, so other views can be added next to the quiz later. Every node keeps its place on screen. Screenshots of every UI state before and after are pixel-identical, and a layout test checks each node against a baseline taken before the split.
This commit is contained in:
1 parent
75c59eb223
commit
3c7b4e5c9d
27 files changed
+3427
-975
No files matched your search
+52
-510
@@ -1,66 +1,21 @@
|
||||
extends Control
|
||||
|
||||
# 601 LOC 11/9 - 2023
|
||||
# 628 LOC 15/9 - 2023
|
||||
# 631 LOC 16/9 - 2023
|
||||
# 554 LOC 30/5 - 2024
|
||||
# 551 LOC 3/6 - 2024
|
||||
# 386 LOC 12/6 - 2024
|
||||
|
||||
##TODO
|
||||
# 15. Fix for local play
|
||||
## The main window: the top bar, the popups opened from it and the quiz below it.
|
||||
##
|
||||
## Everything that is not part of a quiz lives here, so other views can be added next
|
||||
## to the quiz later.
|
||||
|
||||
@onready
|
||||
var open_button: Button = $Open
|
||||
var top_bar: TopBar = $TopBar
|
||||
|
||||
@onready
|
||||
var fileDialog: FileDialog = $FileDialog
|
||||
var quiz: Quiz = $Quiz
|
||||
|
||||
@onready
|
||||
var inspiration_scroll: ScrollContainer = $ScrollContainer
|
||||
|
||||
@onready
|
||||
var inspiration_list: VBoxContainer = $ScrollContainer/VBoxContainer
|
||||
|
||||
@onready
|
||||
var soundtrack_label: Label = $VBoxContainer/SoundtrackLabel
|
||||
|
||||
@onready
|
||||
var song_label: Label = $VBoxContainer/SongLabel
|
||||
|
||||
@onready
|
||||
var add_player_container: HBoxContainer = $Players/VBoxContainer/AddPlayerContainer
|
||||
|
||||
@onready
|
||||
var add_players_button: TextureButton = $Players/VBoxContainer/HBoxContainer/AddPlayersButton
|
||||
|
||||
@onready
|
||||
var add_player_button: Button = $Players/VBoxContainer/AddPlayerContainer/AddPlayerButton
|
||||
|
||||
@onready
|
||||
var new_player_name_field: TextEdit = $Players/VBoxContainer/AddPlayerContainer/PlayerNameField
|
||||
|
||||
@onready
|
||||
var reset_playlist_button: Button = $ResetPlaylistButton
|
||||
|
||||
@onready
|
||||
var reset_points_button: Button = $ResetPointsButton
|
||||
|
||||
@onready
|
||||
var sound_test_button: Button = $SoundTestButton
|
||||
|
||||
@onready
|
||||
var sync_button: Button = $SyncButton
|
||||
var search_view: Control = $Search
|
||||
|
||||
@onready
|
||||
var sync_window: Control = %SyncWindow
|
||||
|
||||
@onready
|
||||
var settings_button: Button = $SettingsButton
|
||||
|
||||
@onready
|
||||
var settings_window: Control = $SettingsWindow
|
||||
|
||||
@onready
|
||||
var shortcuts_window: Control = %ShortcutsWindow
|
||||
|
||||
@@ -68,13 +23,7 @@ var shortcuts_window: Control = %ShortcutsWindow
|
||||
var qr_window: Control = %QRWindow
|
||||
|
||||
@onready
|
||||
var winner_window: Control = %WinnerWindow
|
||||
|
||||
@onready
|
||||
var qr_button: Button = $QRButton
|
||||
|
||||
@onready
|
||||
var statistics_button: Button = $StatisticsButton
|
||||
var about_window: Control = %AboutWindow
|
||||
|
||||
@onready
|
||||
var statistic_popup: PopupPanel = $StatisticsPopupPanel
|
||||
@@ -82,491 +31,84 @@ var statistic_popup: PopupPanel = $StatisticsPopupPanel
|
||||
@onready
|
||||
var statistic_label: Label = $StatisticsPopupPanel/StatisticsLabel
|
||||
|
||||
@onready
|
||||
var shortcuts_button: Button = $ShortcutsButton
|
||||
|
||||
@onready
|
||||
var about_button: Button = $AboutButton
|
||||
|
||||
@onready
|
||||
var about_window: Control = %AboutWindow
|
||||
|
||||
@onready
|
||||
var show_answer_button: Button = $ShowAnswerButton
|
||||
|
||||
@onready
|
||||
var next_button: Button = $NextButton
|
||||
|
||||
@onready
|
||||
var music_list_scroll: ScrollContainer = $MusicListPanel/ScrollContainer
|
||||
|
||||
@onready
|
||||
var music_list: VBoxContainer = $MusicListPanel/ScrollContainer/MusicList
|
||||
|
||||
@onready
|
||||
var player_container: VBoxContainer = $Players/VBoxContainer
|
||||
|
||||
@onready
|
||||
var players_list_container: VBoxContainer = $Players/VBoxContainer/ScrollContainer/PlayersListVBoxContainer
|
||||
|
||||
@onready
|
||||
var new_character_select: Control = %NewCharacterSelect
|
||||
|
||||
@onready
|
||||
var search_button: Button = $SearchButton
|
||||
|
||||
@onready
|
||||
var search_view: Control= $Search
|
||||
|
||||
@onready
|
||||
var auto_repeat_song_button: CheckButton = $RepeatSongCheckButton
|
||||
|
||||
@onready
|
||||
var music_player_container: PanelContainer = $MusicPlayer
|
||||
|
||||
@onready
|
||||
var log_window: Control = %Log
|
||||
|
||||
@onready
|
||||
var debug_label: Label = $DebugLabel
|
||||
|
||||
var player: PackedScene = preload("res://Player.tscn")
|
||||
var song_list_object: PackedScene = preload("res://song_list_object.tscn")
|
||||
|
||||
var songs: Array= []
|
||||
var soundtracks: Array = []
|
||||
var current_player: Node
|
||||
|
||||
## True while statistics are off because a player won, until points are reset.
|
||||
var stats_paused_by_win: bool = false
|
||||
|
||||
## Dims the window behind an open popup and keeps clicks from reaching the buttons under it.
|
||||
var popup_backdrop: ColorRect
|
||||
var popup_backdrop: PopupBackdrop
|
||||
|
||||
## Seconds to wait before retrying a song download step that failed.
|
||||
var song_fetch_retry_seconds: float = 5.0
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready() -> void:
|
||||
_setup_popup_backdrop()
|
||||
var debug: bool = OS.has_feature("debug")
|
||||
var is_mac: bool = OS.has_feature("macos")
|
||||
if debug:
|
||||
print("is_debug")
|
||||
debug_label.visible = true
|
||||
Settings.is_debug = true
|
||||
settings_window.set_winning_score(5)
|
||||
settings_window.set_selected_server(4)
|
||||
if is_mac:
|
||||
popup_backdrop = PopupBackdrop.add_to(self, _popups())
|
||||
if OS.has_feature("macos"):
|
||||
print("is_mac")
|
||||
next_button.pressed.connect(play_next_song)
|
||||
show_answer_button.pressed.connect(show_answer_pressed)
|
||||
search_button.pressed.connect(show_search)
|
||||
sync_button.pressed.connect(sync_soundtracks)
|
||||
sound_test_button.pressed.connect(music_player_container.get_sound_test_song)
|
||||
statistics_button.pressed.connect(get_statistics)
|
||||
shortcuts_button.pressed.connect(show_shortcuts)
|
||||
about_button.pressed.connect(show_about)
|
||||
settings_button.pressed.connect(show_settings)
|
||||
qr_button.pressed.connect(show_qr)
|
||||
reset_playlist_button.pressed.connect(reset_playlist)
|
||||
reset_points_button.pressed.connect(reset_points)
|
||||
top_bar.search_pressed.connect(show_search)
|
||||
top_bar.sync_pressed.connect(sync_soundtracks)
|
||||
top_bar.statistics_pressed.connect(get_statistics)
|
||||
top_bar.shortcuts_pressed.connect(show_shortcuts)
|
||||
top_bar.about_pressed.connect(show_about)
|
||||
top_bar.qr_pressed.connect(show_qr)
|
||||
|
||||
new_character_select.connect("new_new_character_selected", _on_new_character_selected)
|
||||
new_player_name_field.connect("enter_key_pressed", add_player)
|
||||
music_player_container.connect("play_next_song", play_next_song)
|
||||
music_player_container.connect("update_song_list", update_song_list)
|
||||
settings_window.connect("server_changed", server_updated)
|
||||
settings_window.visibility_changed.connect(_on_settings_window_visibility_changed)
|
||||
sync_window.connect("sync_started", _on_sync_started)
|
||||
sync_window.connect("sync_finished", _on_sync_finished)
|
||||
quiz.server_changed.connect(search_view.get_list_of_soundtracks)
|
||||
|
||||
|
||||
add_players_button.pressed.connect(add_players)
|
||||
add_player_button.pressed.connect(add_player)
|
||||
open_button.pressed.connect(open)
|
||||
auto_repeat_song_button.pressed.connect(repeat_song)
|
||||
|
||||
get_suggestion_list()
|
||||
fetch_song_list_at_start()
|
||||
search_view.get_list_of_soundtracks()
|
||||
new_character_select.load_characters()
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if event is InputEventKey and event.pressed:
|
||||
if event.alt_pressed && event.keycode == KEY_UP:
|
||||
print("Alt + UP pressed")
|
||||
music_player_container.change_volume(0.05)
|
||||
if event.alt_pressed && event.keycode == KEY_DOWN:
|
||||
print("Alt + DOWN pressed")
|
||||
music_player_container.change_volume(-0.05)
|
||||
if event.alt_pressed && event.ctrl_pressed && event.keycode == KEY_LEFT:
|
||||
print("Ctrl + Alt + LEFT pressed")
|
||||
music_player_container.restart()
|
||||
elif event.alt_pressed && event.keycode == KEY_LEFT:
|
||||
print("Alt + LEFT pressed")
|
||||
music_player_container.seek(-5)
|
||||
if event.alt_pressed && event.keycode == KEY_RIGHT:
|
||||
print("Alt + RIGHT pressed")
|
||||
music_player_container.seek(5)
|
||||
if event.alt_pressed && event.keycode == KEY_ENTER:
|
||||
print("Alt + Enter pressed")
|
||||
Settings.fullscreen = !Settings.fullscreen
|
||||
settings_window.fullscreen_button.button_pressed = Settings.fullscreen
|
||||
if Settings.fullscreen == true:
|
||||
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN)
|
||||
if Settings.fullscreen == false:
|
||||
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
|
||||
if event.alt_pressed && event.keycode == KEY_K:
|
||||
shortcuts_window.toggle()
|
||||
if event.alt_pressed && event.keycode == KEY_L:
|
||||
log_window.toggle()
|
||||
|
||||
func server_updated() -> void:
|
||||
print("server_updated")
|
||||
update_from_server()
|
||||
reset_points()
|
||||
|
||||
func update_from_server() -> void:
|
||||
print("update_from_server")
|
||||
search_view.get_list_of_soundtracks()
|
||||
get_suggestion_list()
|
||||
reset_playlist()
|
||||
new_character_select.load_characters()
|
||||
|
||||
func repeat_song() -> void:
|
||||
Settings.auto_repeat_song = !Settings.auto_repeat_song
|
||||
|
||||
func show_answer_pressed() -> void:
|
||||
print("show_answer_pressed")
|
||||
Playlist.song_is_answered()
|
||||
update_song_list()
|
||||
|
||||
# Loads the songs already in the server playlist, then starts preloading new ones.
|
||||
# Preloading waits for the list, because loading the list clears the playlist.
|
||||
func fetch_song_list_at_start() -> void:
|
||||
print("fetch_song_list_at_start")
|
||||
var fetch_song_list_at_start_done: Callable = func fetch_songs_at_start_done(data) -> void:
|
||||
Playlist.clear_song_list()
|
||||
if typeof(data) == TYPE_ARRAY:
|
||||
for d: Dictionary in data:
|
||||
var new_song_object: SongObject = SongObject.new()
|
||||
new_song_object.song_number = d.SongNo
|
||||
new_song_object.soundtrack_title = d.Soundtrack
|
||||
new_song_object.song_title = d.Song
|
||||
new_song_object.is_answered = true
|
||||
new_song_object.has_played = true
|
||||
Playlist.add_to_playlist(new_song_object)
|
||||
elif data != null:
|
||||
print("Unexpected data")
|
||||
Playlist.update_last_played_song()
|
||||
update_song_list()
|
||||
if !Playlist.is_song_downloading:
|
||||
fetch_song()
|
||||
MusicServerClient.fetch_song_list(fetch_song_list_at_start_done,
|
||||
_retry_later.bind(fetch_song_list_at_start))
|
||||
|
||||
func play_next_song() -> void:
|
||||
print("play_next_song ", Playlist.latest_played_song)
|
||||
if Playlist.first_song_played:
|
||||
Playlist.set_latest_played_song()
|
||||
Playlist.first_song_played = true
|
||||
music_player_container.play_song_object(Playlist.latest_played_song)
|
||||
if !(Playlist.get_preloaded_songs() > 0):
|
||||
next_button.disabled = true
|
||||
if !Playlist.is_song_downloading:
|
||||
fetch_song()
|
||||
|
||||
#not first "/music/addPlayed"
|
||||
#"/music/rand"
|
||||
#"/music/addQue"
|
||||
#"/music/info"
|
||||
func _popups() -> Array[PopupWindow]:
|
||||
return [search_view, sync_window, settings_window, qr_window, new_character_select,
|
||||
shortcuts_window, about_window, winner_window, log_window]
|
||||
return [search_view, sync_window, qr_window, shortcuts_window, about_window]
|
||||
|
||||
# The backdrop sits above the main window and below every popup. Popups close
|
||||
# themselves when clicked outside, and the backdrop keeps that click from reaching
|
||||
# the buttons behind them.
|
||||
func _setup_popup_backdrop() -> void:
|
||||
popup_backdrop = ColorRect.new()
|
||||
popup_backdrop.name = "PopupBackdrop"
|
||||
popup_backdrop.color = Color(0, 0, 0, 0.55)
|
||||
popup_backdrop.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
add_child(popup_backdrop)
|
||||
popup_backdrop.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
for popup: PopupWindow in _popups():
|
||||
move_child(popup, -1)
|
||||
popup.visibility_changed.connect(_update_popup_backdrop)
|
||||
_update_popup_backdrop()
|
||||
|
||||
func _update_popup_backdrop() -> void:
|
||||
popup_backdrop.visible = _popups().any(func(popup: PopupWindow) -> bool: return popup.visible)
|
||||
|
||||
# The cache size can be changed in the settings. Waiting until the settings are closed
|
||||
# avoids starting downloads while the user is still clicking the number up and down.
|
||||
func _on_settings_window_visibility_changed() -> void:
|
||||
if !settings_window.visible && !Playlist.is_song_downloading && Playlist.should_preload_more_songs():
|
||||
fetch_song()
|
||||
|
||||
func fetch_song() -> void:
|
||||
print("fetch_song")
|
||||
Playlist.is_song_downloading = true
|
||||
_download_random_song(SongObject.new())
|
||||
|
||||
# Fetching a song takes three requests. If one fails, only that step is retried, so
|
||||
# the server and the game keep the same songs in their playlists.
|
||||
func _download_random_song(new_song_object: SongObject) -> void:
|
||||
var song_downloaded: Callable = func song_downloaded(body: PackedByteArray) -> void:
|
||||
print("song_downloaded")
|
||||
var sound: AudioStream = AudioStreamMP3.new()
|
||||
sound.data = body
|
||||
new_song_object.song = sound
|
||||
_add_song_to_queue(new_song_object)
|
||||
MusicServerClient.fetch_random_song(Settings.use_low_played_mode, song_downloaded,
|
||||
_retry_later.bind(_download_random_song.bind(new_song_object)))
|
||||
|
||||
func _add_song_to_queue(new_song_object: SongObject) -> void:
|
||||
MusicServerClient.add_to_queue(_fetch_song_info.bind(new_song_object),
|
||||
_retry_later.bind(_add_song_to_queue.bind(new_song_object)))
|
||||
|
||||
func _fetch_song_info(new_song_object: SongObject) -> void:
|
||||
var song_info_fetched: Callable = func song_info_fetched(data_received: Dictionary) -> void:
|
||||
print("song_info_fetched")
|
||||
new_song_object.song_number = data_received.SongNo
|
||||
new_song_object.soundtrack_title = data_received.Soundtrack
|
||||
new_song_object.song_title = data_received.Song
|
||||
print("new_song_object: ", new_song_object.to_string())
|
||||
Playlist.add_to_playlist(new_song_object)
|
||||
next_button.disabled = false
|
||||
if Playlist.should_preload_more_songs():
|
||||
fetch_song()
|
||||
else:
|
||||
Playlist.is_song_downloading = false
|
||||
if Settings.is_debug:
|
||||
update_song_list()
|
||||
MusicServerClient.fetch_song_info(song_info_fetched,
|
||||
_retry_later.bind(_fetch_song_info.bind(new_song_object)))
|
||||
|
||||
func _retry_later(step: Callable) -> void:
|
||||
print("Song download failed, retrying in %s seconds" % song_fetch_retry_seconds)
|
||||
get_tree().create_timer(song_fetch_retry_seconds).timeout.connect(step)
|
||||
|
||||
func update_song_list() -> void:
|
||||
print("update_song_list")
|
||||
if Playlist.currently_playing_song >= 0:
|
||||
soundtrack_label.text = Playlist.get_current_soundtrack_title()
|
||||
song_label.text = Playlist.get_current_song_title()
|
||||
Settings.delete_children(music_list)
|
||||
for s: SongObject in Playlist.get_playlist():
|
||||
var new_song_list_object: SongListObject = song_list_object.instantiate()
|
||||
new_song_list_object.song_object = s
|
||||
new_song_list_object.gui_input.connect(song_object_clicked.bind(s.song_number))
|
||||
music_list.add_child(new_song_list_object)
|
||||
|
||||
func song_object_clicked(event: InputEvent, song_no: int) -> void:
|
||||
if (event is InputEventMouseButton && event.pressed && event.button_index == MOUSE_BUTTON_LEFT):
|
||||
print("Clicked Song Object: ", Playlist.song_object_array[song_no].to_string())
|
||||
if Settings.character_select_open == false:
|
||||
if Playlist.get_song(song_no) == null:
|
||||
print("has no song, need to download")
|
||||
var song_downloaded: Callable = func song_downloaded(body: PackedByteArray) -> void:
|
||||
var sound: AudioStream = AudioStreamMP3.new()
|
||||
sound.data = body
|
||||
Playlist.add_song_to_song_object(song_no, sound)
|
||||
music_player_container.play_song_object(song_no)
|
||||
MusicServerClient.fetch_song(song_no, song_downloaded)
|
||||
else:
|
||||
print("song already downloaded")
|
||||
music_player_container.play_song_object(song_no)
|
||||
|
||||
func reset_playlist() -> void:
|
||||
print("reset_playlist")
|
||||
var playlist_reset_done: Callable = func playlist_reset_done() -> void:
|
||||
Playlist.reset_playlist()
|
||||
fetch_song_list_at_start()
|
||||
|
||||
MusicServerClient.reset_playlist(playlist_reset_done)
|
||||
|
||||
func reset_points() -> void:
|
||||
print("reset_points")
|
||||
for player_objects: PlayerObject in Settings.player_array:
|
||||
player_objects.reset_points()
|
||||
load_players()
|
||||
# A new game starts, so turn statistics back on if winning turned them off
|
||||
if stats_paused_by_win:
|
||||
Settings.add_to_stats = true
|
||||
stats_paused_by_win = false
|
||||
|
||||
func show_about() -> void:
|
||||
print("show_about")
|
||||
about_window.open()
|
||||
|
||||
func show_shortcuts() -> void:
|
||||
print("show_shortcuts")
|
||||
shortcuts_window.open()
|
||||
|
||||
func show_settings() -> void:
|
||||
print("show_settings")
|
||||
settings_window.open()
|
||||
|
||||
func show_qr() -> void:
|
||||
print("show_qr")
|
||||
qr_window.open()
|
||||
|
||||
func get_statistics() -> void:
|
||||
print("get_statistics")
|
||||
statistic_popup.visible = true
|
||||
statistic_label.text = "Total amount of soundtracks in the playlist: " + str(soundtracks.size())
|
||||
|
||||
func sync_soundtracks() -> void:
|
||||
print("sync_soundtracks")
|
||||
sync_window.start_sync()
|
||||
|
||||
func _on_sync_started() -> void:
|
||||
print("_on_sync_started")
|
||||
sync_button.disabled = false
|
||||
sound_test_button.disabled = true
|
||||
reset_playlist_button.disabled = true
|
||||
show_answer_button.disabled = true
|
||||
next_button.disabled = true
|
||||
|
||||
func _on_sync_finished() -> void:
|
||||
print("_on_sync_finished")
|
||||
update_from_server()
|
||||
sound_test_button.disabled = false
|
||||
reset_playlist_button.disabled = false
|
||||
show_answer_button.disabled = false
|
||||
reset_points()
|
||||
|
||||
func get_suggestion_list() -> void:
|
||||
print("get_suggestion_list")
|
||||
var populate_list: Callable = func populate_list(array) -> void:
|
||||
if typeof(array) == TYPE_ARRAY:
|
||||
soundtracks = []
|
||||
Settings.delete_children(inspiration_list)
|
||||
soundtracks.append_array(array)
|
||||
for i: int in soundtracks.size():
|
||||
inspiration_list.add_child(ListRow.create(soundtracks[i], i, 20))
|
||||
else:
|
||||
print("Unexpected data")
|
||||
MusicServerClient.fetch_all_soundtracks(populate_list)
|
||||
|
||||
func add_players() -> void:
|
||||
print("add_players")
|
||||
Settings.edit_players = !Settings.edit_players
|
||||
add_player_container.visible = !add_player_container.visible
|
||||
new_player_name_field.grab_focus()
|
||||
|
||||
func add_player() -> void:
|
||||
print("add_player")
|
||||
if new_player_name_field.text != "" && Settings.player_array.size() < Settings.MAX_NUMBER_OF_PLAYERS:
|
||||
var new_player_object: PlayerObject = PlayerObject.new(new_player_name_field.text)
|
||||
new_player_name_field.text = ""
|
||||
Settings.player_array.append(new_player_object)
|
||||
new_player_object.connect("first_point_triggerd", music_player_container._on_point_triggered.bind("first"))
|
||||
new_player_object.connect("match_point_triggerd", music_player_container._on_point_triggered.bind("match"))
|
||||
new_player_object.connect("winner_triggerd", _on_player_won.bind(new_player_object))
|
||||
new_player_object.connect("point_given_sound", _on_make_point_given_sound)
|
||||
new_player_object.connect("player_point_given", _on_point_given.bind(new_player_object))
|
||||
new_player_object.connect("player_point_taken", _on_point_taken.bind(new_player_object))
|
||||
load_players()
|
||||
|
||||
func load_players() -> void:
|
||||
print("load_players")
|
||||
Settings.delete_children(players_list_container)
|
||||
var counter: int = 0
|
||||
for player_object: PlayerObject in Settings.player_array:
|
||||
print(player_object.player_name)
|
||||
|
||||
var new_player: Player = player.instantiate()
|
||||
new_player.player_object = player_object
|
||||
new_player.list_index = counter
|
||||
|
||||
new_player.connect("change_character_clicked", _on_player_change_character_clicked.bind(new_player, player_object.player_name))
|
||||
new_player.connect("player_removed", _on_player_removed.bind(player_object))
|
||||
|
||||
players_list_container.add_child(new_player)
|
||||
counter += 1
|
||||
|
||||
func _on_point_given(player_object: PlayerObject) -> void:
|
||||
print("_on_point_given")
|
||||
log_window.add_log_row(player_object.player_name + " got a point")
|
||||
if Playlist.currently_playing_song >= 0:
|
||||
Playlist.add_point(player_object.id)
|
||||
update_song_list()
|
||||
|
||||
func _on_make_point_given_sound() -> void:
|
||||
music_player_container.play_sound_effect(preload("res://sounds/itemopen.wav"))
|
||||
|
||||
func _on_point_taken(player_object: PlayerObject) -> void:
|
||||
print("_on_point_taken")
|
||||
log_window.add_log_row(player_object.player_name + " lost a point")
|
||||
music_player_container.play_sound_effect(preload("res://sounds/itemequip.wav"))
|
||||
if Playlist.currently_playing_song >= 0:
|
||||
Playlist.remove_point(player_object.id)
|
||||
update_song_list()
|
||||
|
||||
func _on_player_won(player_object: PlayerObject) -> void:
|
||||
print("_on_player_won")
|
||||
winner_window.show_winner(player_object)
|
||||
music_player_container.play_song(preload("res://sounds/winning.mp3"))
|
||||
# Songs played after the game is won don't count towards the statistics
|
||||
if Settings.add_to_stats:
|
||||
Settings.add_to_stats = false
|
||||
stats_paused_by_win = true
|
||||
|
||||
func _on_player_removed(player_object: PlayerObject) -> void:
|
||||
print("_on_player_removed ", player_object.player_name)
|
||||
Settings.player_array.erase(player_object)
|
||||
load_players()
|
||||
Playlist.remove_player_from_list(player_object.id)
|
||||
update_song_list()
|
||||
|
||||
func _on_player_change_character_clicked(new_player: Player, player_name: String) -> void:
|
||||
print("_on_player_change_character_clicked")
|
||||
current_player = new_player
|
||||
print("Change character for: " + player_name)
|
||||
new_character_select.show_grid(player_name)
|
||||
|
||||
func _on_new_character_selected(character_texture: ImageTexture) -> void:
|
||||
print("_on_new_character_selected")
|
||||
current_player.new_character_selected(character_texture)
|
||||
|
||||
func show_search() -> void:
|
||||
print("show_search")
|
||||
search_view.toggle()
|
||||
|
||||
|
||||
###Local
|
||||
func show_about() -> void:
|
||||
print("show_about")
|
||||
about_window.open()
|
||||
|
||||
var local_path: String = '/Users/sebastian/ResilioSync/Sorterat_test/Metal Gear Solid 4 - Guns of the Patriots/2-16 Metal Gear Saga.mp3'
|
||||
|
||||
func open() -> void:
|
||||
fileDialog.popup()
|
||||
func show_shortcuts() -> void:
|
||||
print("show_shortcuts")
|
||||
shortcuts_window.open()
|
||||
|
||||
func _on_file_dialog_dir_selected(path: String) -> void:
|
||||
print(path)
|
||||
dir_contents(path)
|
||||
|
||||
func dir_contents(path: String) -> void:
|
||||
var dir: DirAccess = DirAccess.open(path)
|
||||
if dir:
|
||||
dir.list_dir_begin()
|
||||
var file_name: String = dir.get_next()
|
||||
songs.clear()
|
||||
soundtracks = []
|
||||
while file_name != "":
|
||||
if dir.current_is_dir():
|
||||
#print("Found directory: " + file_name)
|
||||
soundtracks.append(file_name)
|
||||
else:
|
||||
#print("Found file: " + file_name)
|
||||
if file_name.ends_with(".mp3"):
|
||||
songs.append(path + "/" + file_name)
|
||||
file_name = dir.get_next()
|
||||
else:
|
||||
print("An error occurred when trying to access the path.")
|
||||
func show_qr() -> void:
|
||||
print("show_qr")
|
||||
qr_window.open()
|
||||
|
||||
for i: int in soundtracks.size():
|
||||
inspiration_list.add_child(ListRow.create(soundtracks[i], i, 20))
|
||||
|
||||
func get_statistics() -> void:
|
||||
print("get_statistics")
|
||||
statistic_popup.visible = true
|
||||
statistic_label.text = "Total amount of soundtracks in the playlist: " \
|
||||
+ str(search_view.soundtracks.size())
|
||||
|
||||
|
||||
func sync_soundtracks() -> void:
|
||||
print("sync_soundtracks")
|
||||
sync_window.start_sync()
|
||||
|
||||
|
||||
func _on_sync_started() -> void:
|
||||
print("_on_sync_started")
|
||||
top_bar.sync_button.disabled = false
|
||||
quiz.lock_for_sync()
|
||||
|
||||
|
||||
func _on_sync_finished() -> void:
|
||||
print("_on_sync_finished")
|
||||
search_view.get_list_of_soundtracks()
|
||||
quiz.on_sync_finished()
|
||||
+4
-414
@@ -1,73 +1,15 @@
|
||||
[gd_scene format=3 uid="uid://xwq863o6uvsu"]
|
||||
|
||||
[ext_resource type="PackedScene" path="res://TopBar.tscn" id="top_bar_scene"]
|
||||
[ext_resource type="PackedScene" path="res://Quiz.tscn" id="quiz_scene"]
|
||||
[ext_resource type="Script" uid="uid://b8f4g15cas2j2" path="res://MainWindow.gd" id="1_eu0t5"]
|
||||
[ext_resource type="Script" uid="uid://elgn7gkxgagq" path="res://InsperationScrollContainer.gd" id="2_gxtxm"]
|
||||
[ext_resource type="Texture2D" uid="uid://bcfmpd7h512ef" path="res://icons/person_add_light.svg" id="5_31tjv"]
|
||||
[ext_resource type="PackedScene" uid="uid://ds15cgsf8vpvc" path="res://MusicPlayer.tscn" id="5_emn36"]
|
||||
[ext_resource type="PackedScene" uid="uid://cdy4kvemwaiom" path="res://new_character_select.tscn" id="6_dhrvg"]
|
||||
[ext_resource type="Script" uid="uid://gf1bk6xbe3j" path="res://MusicListScrollContainer.gd" id="7_dj026"]
|
||||
[ext_resource type="Script" uid="uid://cgakje8gb37tf" path="res://PlayerNameField.gd" id="7_qsdfy"]
|
||||
[ext_resource type="Theme" uid="uid://rxexo3ur85as" path="res://LightGrayTheme.tres" id="7_wxbv6"]
|
||||
[ext_resource type="PackedScene" uid="uid://bxydgil1yifps" path="res://SearchWindow.tscn" id="9_5ijvr"]
|
||||
[ext_resource type="PackedScene" uid="uid://dpdem7pdxweb5" path="res://SyncWindow.tscn" id="10_yxw0b"]
|
||||
[ext_resource type="PackedScene" uid="uid://dldpeo5y3l5hq" path="res://SettingsWindow.tscn" id="11_k62u5"]
|
||||
[ext_resource type="PackedScene" uid="uid://bd1by80q1v27e" path="res://shortcutsWindow.tscn" id="13_8eebo"]
|
||||
[ext_resource type="PackedScene" uid="uid://bijh5h5yrivm3" path="res://Log.tscn" id="14_26rwn"]
|
||||
[ext_resource type="PackedScene" uid="uid://btupbowehiyyu" path="res://QRWindow.tscn" id="15_qr_window"]
|
||||
[ext_resource type="PackedScene" uid="uid://cp54idaudb8dp" path="res://AboutWindow.tscn" id="16_about_window"]
|
||||
[ext_resource type="PackedScene" uid="uid://cxlsmxnrp0agh" path="res://WinnerWindow.tscn" id="17_winner_window"]
|
||||
[ext_resource type="Theme" path="res://popup_theme.tres" id="popup_theme"]
|
||||
|
||||
[sub_resource type="LabelSettings" id="LabelSettings_ychxr"]
|
||||
font_size = 25
|
||||
|
||||
[sub_resource type="LabelSettings" id="LabelSettings_qspbu"]
|
||||
font_size = 50
|
||||
|
||||
[sub_resource type="LabelSettings" id="LabelSettings_3m52w"]
|
||||
font_size = 35
|
||||
|
||||
[sub_resource type="InputEventKey" id="InputEventKey_03bm3"]
|
||||
device = -1
|
||||
alt_pressed = true
|
||||
keycode = 65
|
||||
unicode = 63743
|
||||
|
||||
[sub_resource type="Shortcut" id="Shortcut_jafqj"]
|
||||
events = [SubResource("InputEventKey_03bm3")]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qk7sj"]
|
||||
bg_color = Color(0.180392, 0.180392, 0.180392, 1)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_k1ygi"]
|
||||
bg_color = Color(0.180392, 0.180392, 0.180392, 1)
|
||||
|
||||
[sub_resource type="InputEventKey" id="InputEventKey_06rl4"]
|
||||
device = -1
|
||||
alt_pressed = true
|
||||
keycode = 86
|
||||
unicode = 8249
|
||||
|
||||
[sub_resource type="Shortcut" id="Shortcut_a7fvb"]
|
||||
events = [SubResource("InputEventKey_06rl4")]
|
||||
|
||||
[sub_resource type="InputEventKey" id="InputEventKey_7dnqw"]
|
||||
device = -1
|
||||
alt_pressed = true
|
||||
keycode = 67
|
||||
unicode = 231
|
||||
|
||||
[sub_resource type="Shortcut" id="Shortcut_d6fml"]
|
||||
events = [SubResource("InputEventKey_7dnqw")]
|
||||
|
||||
[sub_resource type="InputEventKey" id="InputEventKey_ujjlu"]
|
||||
device = -1
|
||||
alt_pressed = true
|
||||
keycode = 83
|
||||
|
||||
[sub_resource type="Shortcut" id="Shortcut_fbju4"]
|
||||
events = [SubResource("InputEventKey_ujjlu")]
|
||||
|
||||
[node name="Control" type="Control" unique_id=407149696]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
@@ -77,301 +19,9 @@ grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_eu0t5")
|
||||
|
||||
[node name="TopPanelContainer" type="PanelContainer" parent="." unique_id=663404711]
|
||||
layout_mode = 0
|
||||
offset_right = 1920.0
|
||||
offset_bottom = 56.0
|
||||
[node name="TopBar" parent="." unique_id=1734261695 instance=ExtResource("top_bar_scene")]
|
||||
|
||||
[node name="Title" type="Label" parent="." unique_id=1187907861]
|
||||
layout_mode = 0
|
||||
offset_left = 64.0
|
||||
offset_top = 12.0
|
||||
offset_right = 369.0
|
||||
offset_bottom = 50.0
|
||||
size_flags_horizontal = 0
|
||||
text = "Music Player Randomizer"
|
||||
label_settings = SubResource("LabelSettings_ychxr")
|
||||
|
||||
[node name="Open" type="Button" parent="." unique_id=165052329]
|
||||
visible = false
|
||||
layout_mode = 0
|
||||
offset_left = 1296.0
|
||||
offset_top = 66.0
|
||||
offset_right = 1346.0
|
||||
offset_bottom = 97.0
|
||||
text = "Open"
|
||||
|
||||
[node name="FileDialog" type="FileDialog" parent="." unique_id=485771559]
|
||||
title = "Open a Directory"
|
||||
initial_position = 2
|
||||
size = Vector2i(616, 400)
|
||||
ok_button_text = "Select Current Folder"
|
||||
file_mode = 2
|
||||
access = 2
|
||||
show_hidden_files = true
|
||||
|
||||
[node name="MusicListPanel" type="PanelContainer" parent="." unique_id=2011292375]
|
||||
layout_mode = 0
|
||||
offset_left = 64.0
|
||||
offset_top = 384.0
|
||||
offset_right = 1152.0
|
||||
offset_bottom = 896.0
|
||||
theme = ExtResource("7_wxbv6")
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="MusicListPanel" unique_id=707750459]
|
||||
layout_mode = 2
|
||||
follow_focus = true
|
||||
script = ExtResource("7_dj026")
|
||||
|
||||
[node name="MusicList" type="VBoxContainer" parent="MusicListPanel/ScrollContainer" unique_id=373398568]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme_override_constants/separation = 10
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="." unique_id=498283761]
|
||||
layout_mode = 0
|
||||
offset_left = 1216.0
|
||||
offset_top = 128.0
|
||||
offset_right = 1857.0
|
||||
offset_bottom = 512.0
|
||||
horizontal_scroll_mode = 0
|
||||
vertical_scroll_mode = 3
|
||||
script = ExtResource("2_gxtxm")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="ScrollContainer" unique_id=1215162949]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=42468498]
|
||||
layout_mode = 0
|
||||
offset_left = 64.0
|
||||
offset_top = 64.0
|
||||
offset_right = 1152.0
|
||||
offset_bottom = 384.0
|
||||
|
||||
[node name="SoundtrackLabel" type="Label" parent="VBoxContainer" unique_id=173147793]
|
||||
custom_minimum_size = Vector2(1, 1)
|
||||
layout_mode = 2
|
||||
label_settings = SubResource("LabelSettings_qspbu")
|
||||
horizontal_alignment = 1
|
||||
autowrap_mode = 2
|
||||
|
||||
[node name="SongLabel" type="Label" parent="VBoxContainer" unique_id=1687490027]
|
||||
custom_minimum_size = Vector2(1, 1)
|
||||
layout_mode = 2
|
||||
label_settings = SubResource("LabelSettings_3m52w")
|
||||
horizontal_alignment = 1
|
||||
autowrap_mode = 2
|
||||
|
||||
[node name="MusicPlayer" parent="." unique_id=1990459832 instance=ExtResource("5_emn36")]
|
||||
layout_mode = 0
|
||||
anchors_preset = 0
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
offset_left = 65.0
|
||||
offset_top = 992.0
|
||||
offset_right = 1856.0
|
||||
offset_bottom = 1024.0
|
||||
grow_horizontal = 1
|
||||
grow_vertical = 1
|
||||
|
||||
[node name="Players" type="PanelContainer" parent="." unique_id=1708375945]
|
||||
layout_mode = 0
|
||||
offset_left = 1216.0
|
||||
offset_top = 576.0
|
||||
offset_right = 1857.0
|
||||
offset_bottom = 977.0
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="Players" unique_id=270084211]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="Players/VBoxContainer" unique_id=513047023]
|
||||
custom_minimum_size = Vector2(2.08165e-12, 40)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="Players/VBoxContainer/HBoxContainer" unique_id=1331357386]
|
||||
custom_minimum_size = Vector2(2.08165e-12, 40)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 1
|
||||
text = "Players"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="AddPlayersButton" type="TextureButton" parent="Players/VBoxContainer/HBoxContainer" unique_id=634410148]
|
||||
custom_minimum_size = Vector2(2.08165e-12, 40)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 8
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
shortcut = SubResource("Shortcut_jafqj")
|
||||
texture_normal = ExtResource("5_31tjv")
|
||||
|
||||
[node name="AddPlayerContainer" type="HBoxContainer" parent="Players/VBoxContainer" unique_id=1371948739]
|
||||
visible = false
|
||||
custom_minimum_size = Vector2(2.08165e-12, 40)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Panel" type="Panel" parent="Players/VBoxContainer/AddPlayerContainer" unique_id=1569854056]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_qk7sj")
|
||||
|
||||
[node name="PlayerNameField" type="TextEdit" parent="Players/VBoxContainer/AddPlayerContainer" unique_id=332535327]
|
||||
custom_minimum_size = Vector2(2.08165e-12, 40)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
placeholder_text = "Player name"
|
||||
script = ExtResource("7_qsdfy")
|
||||
|
||||
[node name="AddPlayerButton" type="Button" parent="Players/VBoxContainer/AddPlayerContainer" unique_id=245001313]
|
||||
custom_minimum_size = Vector2(2.08165e-12, 40)
|
||||
layout_mode = 2
|
||||
text = "Add"
|
||||
|
||||
[node name="Panel2" type="Panel" parent="Players/VBoxContainer/AddPlayerContainer" unique_id=60215448]
|
||||
custom_minimum_size = Vector2(2.08165e-12, 40)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_k1ygi")
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="Players/VBoxContainer" unique_id=1000000001]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 15
|
||||
horizontal_scroll_mode = 0
|
||||
|
||||
[node name="PlayersListVBoxContainer" type="VBoxContainer" parent="Players/VBoxContainer/ScrollContainer" unique_id=1000000002]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 15
|
||||
|
||||
[node name="ResetPlaylistButton" type="Button" parent="." unique_id=1754155620]
|
||||
layout_mode = 0
|
||||
offset_left = 1360.0
|
||||
offset_top = 64.0
|
||||
offset_right = 1471.0
|
||||
offset_bottom = 95.0
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
text = "Reset playlist"
|
||||
|
||||
[node name="ResetPointsButton" type="Button" parent="." unique_id=942880776]
|
||||
layout_mode = 0
|
||||
offset_left = 1488.0
|
||||
offset_top = 64.0
|
||||
offset_right = 1599.0
|
||||
offset_bottom = 95.0
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
text = "Reset points"
|
||||
|
||||
[node name="SyncButton" type="Button" parent="." unique_id=1228313556]
|
||||
layout_mode = 0
|
||||
offset_left = 1442.0
|
||||
offset_top = 8.0
|
||||
offset_right = 1512.0
|
||||
offset_bottom = 48.0
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
text = "Sync"
|
||||
|
||||
[node name="SoundTestButton" type="Button" parent="." unique_id=1332971879]
|
||||
layout_mode = 0
|
||||
offset_left = 1744.0
|
||||
offset_top = 64.0
|
||||
offset_right = 1855.0
|
||||
offset_bottom = 95.0
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
text = "Sound test"
|
||||
|
||||
[node name="ShowAnswerButton" type="Button" parent="." unique_id=415388039]
|
||||
layout_mode = 0
|
||||
offset_left = 65.0
|
||||
offset_top = 944.0
|
||||
offset_right = 176.0
|
||||
offset_bottom = 975.0
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
shortcut = SubResource("Shortcut_a7fvb")
|
||||
text = "Show answer"
|
||||
|
||||
[node name="NextButton" type="Button" parent="." unique_id=1744703488]
|
||||
layout_mode = 0
|
||||
offset_left = 193.0
|
||||
offset_top = 944.0
|
||||
offset_right = 368.0
|
||||
offset_bottom = 975.0
|
||||
focus_mode = 0
|
||||
disabled = true
|
||||
action_mode = 0
|
||||
shortcut = SubResource("Shortcut_d6fml")
|
||||
text = "Randomize new track"
|
||||
|
||||
[node name="SearchButton" type="Button" parent="." unique_id=1782453581]
|
||||
layout_mode = 0
|
||||
offset_left = 1354.0
|
||||
offset_top = 8.0
|
||||
offset_right = 1424.0
|
||||
offset_bottom = 48.0
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
shortcut = SubResource("Shortcut_fbju4")
|
||||
text = "Search"
|
||||
|
||||
[node name="SettingsButton" type="Button" parent="." unique_id=1193400323]
|
||||
layout_mode = 0
|
||||
offset_left = 1616.0
|
||||
offset_top = 64.0
|
||||
offset_right = 1727.0
|
||||
offset_bottom = 95.0
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
text = "Settings"
|
||||
|
||||
[node name="StatisticsButton" type="Button" parent="." unique_id=2045021269]
|
||||
layout_mode = 0
|
||||
offset_left = 1530.0
|
||||
offset_top = 8.0
|
||||
offset_right = 1600.0
|
||||
offset_bottom = 48.0
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
text = "Statistics"
|
||||
|
||||
[node name="ShortcutsButton" type="Button" parent="." unique_id=2066498226]
|
||||
layout_mode = 0
|
||||
offset_left = 1626.0
|
||||
offset_top = 8.0
|
||||
offset_right = 1696.0
|
||||
offset_bottom = 48.0
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
text = "Shortcuts"
|
||||
|
||||
[node name="AboutButton" type="Button" parent="." unique_id=1057035253]
|
||||
layout_mode = 0
|
||||
offset_left = 1722.0
|
||||
offset_top = 8.0
|
||||
offset_right = 1792.0
|
||||
offset_bottom = 48.0
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
text = "About"
|
||||
|
||||
[node name="QRButton" type="Button" parent="." unique_id=2021908559]
|
||||
layout_mode = 0
|
||||
offset_left = 1802.0
|
||||
offset_top = 8.0
|
||||
offset_right = 1872.0
|
||||
offset_bottom = 48.0
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
text = "QR"
|
||||
[node name="Quiz" parent="." unique_id=1357908642 instance=ExtResource("quiz_scene")]
|
||||
|
||||
[node name="Search" parent="." unique_id=1661250375 instance=ExtResource("9_5ijvr")]
|
||||
visible = false
|
||||
@@ -390,14 +40,6 @@ offset_top = 153.0
|
||||
offset_right = 210.0
|
||||
offset_bottom = 153.0
|
||||
|
||||
[node name="SettingsWindow" parent="." unique_id=1323246978 instance=ExtResource("11_k62u5")]
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
offset_left = 720.0
|
||||
offset_top = 153.0
|
||||
offset_right = 720.0
|
||||
offset_bottom = 153.0
|
||||
|
||||
[node name="QRWindow" parent="." unique_id=589733821 instance=ExtResource("15_qr_window")]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
@@ -407,15 +49,6 @@ offset_top = 153.0
|
||||
offset_right = 720.0
|
||||
offset_bottom = 153.0
|
||||
|
||||
[node name="NewCharacterSelect" parent="." unique_id=1679856322 instance=ExtResource("6_dhrvg")]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
offset_left = 405.0
|
||||
offset_top = 165.0
|
||||
offset_right = 1557.0
|
||||
offset_bottom = 805.0
|
||||
|
||||
[node name="ShortcutsWindow" parent="." unique_id=1513416242 instance=ExtResource("13_8eebo")]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
@@ -457,46 +90,3 @@ offset_top = 153.0
|
||||
offset_right = 720.0
|
||||
offset_bottom = 153.0
|
||||
|
||||
[node name="WinnerWindow" parent="." unique_id=999999999 instance=ExtResource("17_winner_window")]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
offset_left = 720.0
|
||||
offset_top = 153.0
|
||||
offset_right = 720.0
|
||||
offset_bottom = 153.0
|
||||
|
||||
[node name="RepeatSongCheckButton" type="CheckButton" parent="." unique_id=776835053]
|
||||
layout_mode = 0
|
||||
offset_left = 64.0
|
||||
offset_top = 1032.0
|
||||
offset_right = 208.0
|
||||
offset_bottom = 1063.0
|
||||
focus_mode = 0
|
||||
button_pressed = true
|
||||
action_mode = 0
|
||||
text = "Auto repeat"
|
||||
|
||||
[node name="DebugLabel" type="Label" parent="." unique_id=1959221257]
|
||||
visible = false
|
||||
layout_mode = 0
|
||||
offset_left = 974.0
|
||||
offset_top = 904.0
|
||||
offset_right = 1014.0
|
||||
offset_bottom = 927.0
|
||||
scale = Vector2(3, 3)
|
||||
text = "DEBUG"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
autowrap_mode = 2
|
||||
|
||||
[node name="Log" parent="." unique_id=1289881388 instance=ExtResource("14_26rwn")]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
offset_left = 562.0
|
||||
offset_top = 132.0
|
||||
offset_right = 1262.0
|
||||
offset_bottom = 832.0
|
||||
|
||||
[connection signal="dir_selected" from="FileDialog" to="." method="_on_file_dialog_dir_selected"]
|
||||
@@ -0,0 +1,343 @@
|
||||
[gd_scene format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://quiz.gd" id="quiz_script"]
|
||||
[ext_resource type="Script" uid="uid://elgn7gkxgagq" path="res://InsperationScrollContainer.gd" id="2_gxtxm"]
|
||||
[ext_resource type="Texture2D" uid="uid://bcfmpd7h512ef" path="res://icons/person_add_light.svg" id="5_31tjv"]
|
||||
[ext_resource type="PackedScene" uid="uid://ds15cgsf8vpvc" path="res://MusicPlayer.tscn" id="5_emn36"]
|
||||
[ext_resource type="PackedScene" uid="uid://cdy4kvemwaiom" path="res://new_character_select.tscn" id="6_dhrvg"]
|
||||
[ext_resource type="Script" uid="uid://gf1bk6xbe3j" path="res://MusicListScrollContainer.gd" id="7_dj026"]
|
||||
[ext_resource type="Script" uid="uid://cgakje8gb37tf" path="res://PlayerNameField.gd" id="7_qsdfy"]
|
||||
[ext_resource type="Theme" uid="uid://rxexo3ur85as" path="res://LightGrayTheme.tres" id="7_wxbv6"]
|
||||
[ext_resource type="PackedScene" uid="uid://dldpeo5y3l5hq" path="res://SettingsWindow.tscn" id="11_k62u5"]
|
||||
[ext_resource type="PackedScene" uid="uid://bijh5h5yrivm3" path="res://Log.tscn" id="14_26rwn"]
|
||||
[ext_resource type="PackedScene" uid="uid://cxlsmxnrp0agh" path="res://WinnerWindow.tscn" id="17_winner_window"]
|
||||
|
||||
[sub_resource type="LabelSettings" id="LabelSettings_qspbu"]
|
||||
font_size = 50
|
||||
|
||||
[sub_resource type="LabelSettings" id="LabelSettings_3m52w"]
|
||||
font_size = 35
|
||||
|
||||
[sub_resource type="InputEventKey" id="InputEventKey_03bm3"]
|
||||
device = -1
|
||||
alt_pressed = true
|
||||
keycode = 65
|
||||
unicode = 63743
|
||||
|
||||
[sub_resource type="Shortcut" id="Shortcut_jafqj"]
|
||||
events = [SubResource("InputEventKey_03bm3")]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qk7sj"]
|
||||
bg_color = Color(0.180392, 0.180392, 0.180392, 1)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_k1ygi"]
|
||||
bg_color = Color(0.180392, 0.180392, 0.180392, 1)
|
||||
|
||||
[sub_resource type="InputEventKey" id="InputEventKey_06rl4"]
|
||||
device = -1
|
||||
alt_pressed = true
|
||||
keycode = 86
|
||||
unicode = 8249
|
||||
|
||||
[sub_resource type="Shortcut" id="Shortcut_a7fvb"]
|
||||
events = [SubResource("InputEventKey_06rl4")]
|
||||
|
||||
[sub_resource type="InputEventKey" id="InputEventKey_7dnqw"]
|
||||
device = -1
|
||||
alt_pressed = true
|
||||
keycode = 67
|
||||
unicode = 231
|
||||
|
||||
[sub_resource type="Shortcut" id="Shortcut_d6fml"]
|
||||
events = [SubResource("InputEventKey_7dnqw")]
|
||||
|
||||
[node name="Quiz" type="Control" unique_id=1146705218]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
script = ExtResource("quiz_script")
|
||||
|
||||
[node name="Open" type="Button" parent="." unique_id=165052329]
|
||||
visible = false
|
||||
layout_mode = 0
|
||||
offset_left = 1296.0
|
||||
offset_top = 66.0
|
||||
offset_right = 1346.0
|
||||
offset_bottom = 97.0
|
||||
text = "Open"
|
||||
|
||||
[node name="FileDialog" type="FileDialog" parent="." unique_id=485771559]
|
||||
title = "Open a Directory"
|
||||
initial_position = 2
|
||||
size = Vector2i(616, 400)
|
||||
ok_button_text = "Select Current Folder"
|
||||
file_mode = 2
|
||||
access = 2
|
||||
show_hidden_files = true
|
||||
|
||||
[node name="MusicListPanel" type="PanelContainer" parent="." unique_id=2011292375]
|
||||
layout_mode = 0
|
||||
offset_left = 64.0
|
||||
offset_top = 384.0
|
||||
offset_right = 1152.0
|
||||
offset_bottom = 896.0
|
||||
theme = ExtResource("7_wxbv6")
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="MusicListPanel" unique_id=707750459]
|
||||
layout_mode = 2
|
||||
follow_focus = true
|
||||
script = ExtResource("7_dj026")
|
||||
|
||||
[node name="MusicList" type="VBoxContainer" parent="MusicListPanel/ScrollContainer" unique_id=373398568]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme_override_constants/separation = 10
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="." unique_id=498283761]
|
||||
layout_mode = 0
|
||||
offset_left = 1216.0
|
||||
offset_top = 128.0
|
||||
offset_right = 1857.0
|
||||
offset_bottom = 512.0
|
||||
horizontal_scroll_mode = 0
|
||||
vertical_scroll_mode = 3
|
||||
script = ExtResource("2_gxtxm")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="ScrollContainer" unique_id=1215162949]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=42468498]
|
||||
layout_mode = 0
|
||||
offset_left = 64.0
|
||||
offset_top = 64.0
|
||||
offset_right = 1152.0
|
||||
offset_bottom = 384.0
|
||||
|
||||
[node name="SoundtrackLabel" type="Label" parent="VBoxContainer" unique_id=173147793]
|
||||
custom_minimum_size = Vector2(1, 1)
|
||||
layout_mode = 2
|
||||
label_settings = SubResource("LabelSettings_qspbu")
|
||||
horizontal_alignment = 1
|
||||
autowrap_mode = 2
|
||||
|
||||
[node name="SongLabel" type="Label" parent="VBoxContainer" unique_id=1687490027]
|
||||
custom_minimum_size = Vector2(1, 1)
|
||||
layout_mode = 2
|
||||
label_settings = SubResource("LabelSettings_3m52w")
|
||||
horizontal_alignment = 1
|
||||
autowrap_mode = 2
|
||||
|
||||
[node name="MusicPlayer" parent="." unique_id=1990459832 instance=ExtResource("5_emn36")]
|
||||
layout_mode = 0
|
||||
anchors_preset = 0
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
offset_left = 65.0
|
||||
offset_top = 992.0
|
||||
offset_right = 1856.0
|
||||
offset_bottom = 1024.0
|
||||
grow_horizontal = 1
|
||||
grow_vertical = 1
|
||||
|
||||
[node name="Players" type="PanelContainer" parent="." unique_id=1708375945]
|
||||
layout_mode = 0
|
||||
offset_left = 1216.0
|
||||
offset_top = 576.0
|
||||
offset_right = 1857.0
|
||||
offset_bottom = 977.0
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="Players" unique_id=270084211]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="Players/VBoxContainer" unique_id=513047023]
|
||||
custom_minimum_size = Vector2(2.08165e-12, 40)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="Players/VBoxContainer/HBoxContainer" unique_id=1331357386]
|
||||
custom_minimum_size = Vector2(2.08165e-12, 40)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 1
|
||||
text = "Players"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="AddPlayersButton" type="TextureButton" parent="Players/VBoxContainer/HBoxContainer" unique_id=634410148]
|
||||
custom_minimum_size = Vector2(2.08165e-12, 40)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 8
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
shortcut = SubResource("Shortcut_jafqj")
|
||||
texture_normal = ExtResource("5_31tjv")
|
||||
|
||||
[node name="AddPlayerContainer" type="HBoxContainer" parent="Players/VBoxContainer" unique_id=1371948739]
|
||||
visible = false
|
||||
custom_minimum_size = Vector2(2.08165e-12, 40)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Panel" type="Panel" parent="Players/VBoxContainer/AddPlayerContainer" unique_id=1569854056]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_qk7sj")
|
||||
|
||||
[node name="PlayerNameField" type="TextEdit" parent="Players/VBoxContainer/AddPlayerContainer" unique_id=332535327]
|
||||
custom_minimum_size = Vector2(2.08165e-12, 40)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
placeholder_text = "Player name"
|
||||
script = ExtResource("7_qsdfy")
|
||||
|
||||
[node name="AddPlayerButton" type="Button" parent="Players/VBoxContainer/AddPlayerContainer" unique_id=245001313]
|
||||
custom_minimum_size = Vector2(2.08165e-12, 40)
|
||||
layout_mode = 2
|
||||
text = "Add"
|
||||
|
||||
[node name="Panel2" type="Panel" parent="Players/VBoxContainer/AddPlayerContainer" unique_id=60215448]
|
||||
custom_minimum_size = Vector2(2.08165e-12, 40)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_k1ygi")
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="Players/VBoxContainer" unique_id=1000000001]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 15
|
||||
horizontal_scroll_mode = 0
|
||||
|
||||
[node name="PlayersListVBoxContainer" type="VBoxContainer" parent="Players/VBoxContainer/ScrollContainer" unique_id=1000000002]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 15
|
||||
|
||||
[node name="ResetPlaylistButton" type="Button" parent="." unique_id=1754155620]
|
||||
layout_mode = 0
|
||||
offset_left = 1360.0
|
||||
offset_top = 64.0
|
||||
offset_right = 1471.0
|
||||
offset_bottom = 95.0
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
text = "Reset playlist"
|
||||
|
||||
[node name="ResetPointsButton" type="Button" parent="." unique_id=942880776]
|
||||
layout_mode = 0
|
||||
offset_left = 1488.0
|
||||
offset_top = 64.0
|
||||
offset_right = 1599.0
|
||||
offset_bottom = 95.0
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
text = "Reset points"
|
||||
|
||||
[node name="SoundTestButton" type="Button" parent="." unique_id=1332971879]
|
||||
layout_mode = 0
|
||||
offset_left = 1744.0
|
||||
offset_top = 64.0
|
||||
offset_right = 1855.0
|
||||
offset_bottom = 95.0
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
text = "Sound test"
|
||||
|
||||
[node name="ShowAnswerButton" type="Button" parent="." unique_id=415388039]
|
||||
layout_mode = 0
|
||||
offset_left = 65.0
|
||||
offset_top = 944.0
|
||||
offset_right = 176.0
|
||||
offset_bottom = 975.0
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
shortcut = SubResource("Shortcut_a7fvb")
|
||||
text = "Show answer"
|
||||
|
||||
[node name="NextButton" type="Button" parent="." unique_id=1744703488]
|
||||
layout_mode = 0
|
||||
offset_left = 193.0
|
||||
offset_top = 944.0
|
||||
offset_right = 368.0
|
||||
offset_bottom = 975.0
|
||||
focus_mode = 0
|
||||
disabled = true
|
||||
action_mode = 0
|
||||
shortcut = SubResource("Shortcut_d6fml")
|
||||
text = "Randomize new track"
|
||||
|
||||
[node name="SettingsButton" type="Button" parent="." unique_id=1193400323]
|
||||
layout_mode = 0
|
||||
offset_left = 1616.0
|
||||
offset_top = 64.0
|
||||
offset_right = 1727.0
|
||||
offset_bottom = 95.0
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
text = "Settings"
|
||||
|
||||
[node name="SettingsWindow" parent="." unique_id=1323246978 instance=ExtResource("11_k62u5")]
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
offset_left = 720.0
|
||||
offset_top = 153.0
|
||||
offset_right = 720.0
|
||||
offset_bottom = 153.0
|
||||
|
||||
[node name="NewCharacterSelect" parent="." unique_id=1679856322 instance=ExtResource("6_dhrvg")]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
offset_left = 405.0
|
||||
offset_top = 165.0
|
||||
offset_right = 1557.0
|
||||
offset_bottom = 805.0
|
||||
|
||||
[node name="WinnerWindow" parent="." unique_id=999999999 instance=ExtResource("17_winner_window")]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
offset_left = 720.0
|
||||
offset_top = 153.0
|
||||
offset_right = 720.0
|
||||
offset_bottom = 153.0
|
||||
|
||||
[node name="RepeatSongCheckButton" type="CheckButton" parent="." unique_id=776835053]
|
||||
layout_mode = 0
|
||||
offset_left = 64.0
|
||||
offset_top = 1032.0
|
||||
offset_right = 208.0
|
||||
offset_bottom = 1063.0
|
||||
focus_mode = 0
|
||||
button_pressed = true
|
||||
action_mode = 0
|
||||
text = "Auto repeat"
|
||||
|
||||
[node name="DebugLabel" type="Label" parent="." unique_id=1959221257]
|
||||
visible = false
|
||||
layout_mode = 0
|
||||
offset_left = 974.0
|
||||
offset_top = 904.0
|
||||
offset_right = 1014.0
|
||||
offset_bottom = 927.0
|
||||
scale = Vector2(3, 3)
|
||||
text = "DEBUG"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
autowrap_mode = 2
|
||||
|
||||
[node name="Log" parent="." unique_id=1289881388 instance=ExtResource("14_26rwn")]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
offset_left = 562.0
|
||||
offset_top = 132.0
|
||||
offset_right = 1262.0
|
||||
offset_bottom = 832.0
|
||||
|
||||
[connection signal="dir_selected" from="FileDialog" to="." method="_on_file_dialog_dir_selected"]
|
||||
@@ -110,11 +110,17 @@ func _ready() -> void:
|
||||
select_server_button.select(Settings.selected_server)
|
||||
select_server_button.item_selected.connect(select_server)
|
||||
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)
|
||||
|
||||
# Settings can change outside this window, e.g. winning turns statistics off
|
||||
func _on_visibility_changed() -> void:
|
||||
if visible:
|
||||
add_to_database_button.button_pressed = Settings.add_to_stats
|
||||
_update_fullscreen_button()
|
||||
|
||||
func _update_fullscreen_button() -> void:
|
||||
fullscreen_button.set_pressed_no_signal(Settings.fullscreen)
|
||||
|
||||
func fullscreen() -> void:
|
||||
Settings.fullscreen = !Settings.fullscreen
|
||||
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
[gd_scene format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://top_bar.gd" id="top_bar_script"]
|
||||
|
||||
[sub_resource type="LabelSettings" id="LabelSettings_ychxr"]
|
||||
font_size = 25
|
||||
|
||||
[sub_resource type="InputEventKey" id="InputEventKey_ujjlu"]
|
||||
device = -1
|
||||
alt_pressed = true
|
||||
keycode = 83
|
||||
|
||||
[sub_resource type="Shortcut" id="Shortcut_fbju4"]
|
||||
events = [SubResource("InputEventKey_ujjlu")]
|
||||
|
||||
[node name="TopBar" type="Control" unique_id=1489162753]
|
||||
layout_mode = 3
|
||||
anchors_preset = 10
|
||||
anchor_right = 1.0
|
||||
offset_bottom = 56.0
|
||||
grow_horizontal = 2
|
||||
mouse_filter = 2
|
||||
script = ExtResource("top_bar_script")
|
||||
|
||||
[node name="TopPanelContainer" type="PanelContainer" parent="." unique_id=663404711]
|
||||
layout_mode = 0
|
||||
offset_right = 1920.0
|
||||
offset_bottom = 56.0
|
||||
|
||||
[node name="Title" type="Label" parent="." unique_id=1187907861]
|
||||
layout_mode = 0
|
||||
offset_left = 64.0
|
||||
offset_top = 12.0
|
||||
offset_right = 369.0
|
||||
offset_bottom = 50.0
|
||||
size_flags_horizontal = 0
|
||||
text = "Music Player Randomizer"
|
||||
label_settings = SubResource("LabelSettings_ychxr")
|
||||
|
||||
[node name="SyncButton" type="Button" parent="." unique_id=1228313556]
|
||||
layout_mode = 0
|
||||
offset_left = 1442.0
|
||||
offset_top = 8.0
|
||||
offset_right = 1512.0
|
||||
offset_bottom = 48.0
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
text = "Sync"
|
||||
|
||||
[node name="SearchButton" type="Button" parent="." unique_id=1782453581]
|
||||
layout_mode = 0
|
||||
offset_left = 1354.0
|
||||
offset_top = 8.0
|
||||
offset_right = 1424.0
|
||||
offset_bottom = 48.0
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
shortcut = SubResource("Shortcut_fbju4")
|
||||
text = "Search"
|
||||
|
||||
[node name="StatisticsButton" type="Button" parent="." unique_id=2045021269]
|
||||
layout_mode = 0
|
||||
offset_left = 1530.0
|
||||
offset_top = 8.0
|
||||
offset_right = 1600.0
|
||||
offset_bottom = 48.0
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
text = "Statistics"
|
||||
|
||||
[node name="ShortcutsButton" type="Button" parent="." unique_id=2066498226]
|
||||
layout_mode = 0
|
||||
offset_left = 1626.0
|
||||
offset_top = 8.0
|
||||
offset_right = 1696.0
|
||||
offset_bottom = 48.0
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
text = "Shortcuts"
|
||||
|
||||
[node name="AboutButton" type="Button" parent="." unique_id=1057035253]
|
||||
layout_mode = 0
|
||||
offset_left = 1722.0
|
||||
offset_top = 8.0
|
||||
offset_right = 1792.0
|
||||
offset_bottom = 48.0
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
text = "About"
|
||||
|
||||
[node name="QRButton" type="Button" parent="." unique_id=2021908559]
|
||||
layout_mode = 0
|
||||
offset_left = 1802.0
|
||||
offset_top = 8.0
|
||||
offset_right = 1872.0
|
||||
offset_bottom = 48.0
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
text = "QR"
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
class_name PopupBackdrop
|
||||
extends ColorRect
|
||||
## Dims everything behind an open popup and keeps clicks from reaching the buttons
|
||||
## under it.
|
||||
##
|
||||
## The backdrop sits above the rest of its parent and below the popups it belongs to.
|
||||
## Popups close themselves when clicked outside, and the backdrop keeps that click from
|
||||
## reaching the buttons behind them.
|
||||
|
||||
var _popups: Array[PopupWindow] = []
|
||||
|
||||
|
||||
## Adds a backdrop to [param parent] and moves [param popups] above it. The backdrop
|
||||
## shows while any of them is open.
|
||||
static func add_to(parent: Control, popups: Array[PopupWindow]) -> PopupBackdrop:
|
||||
var backdrop: PopupBackdrop = PopupBackdrop.new()
|
||||
backdrop.name = "PopupBackdrop"
|
||||
backdrop.color = Color(0, 0, 0, 0.55)
|
||||
backdrop.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
backdrop._popups = popups
|
||||
parent.add_child(backdrop)
|
||||
backdrop.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
for popup: PopupWindow in popups:
|
||||
parent.move_child(popup, -1)
|
||||
popup.visibility_changed.connect(backdrop._update)
|
||||
backdrop._update()
|
||||
return backdrop
|
||||
|
||||
|
||||
func _update() -> void:
|
||||
visible = _popups.any(func(popup: PopupWindow) -> bool: return popup.visible)
|
||||
@@ -0,0 +1 @@
|
||||
uid://swjhekc8beob
|
||||
@@ -0,0 +1,496 @@
|
||||
class_name Quiz
|
||||
extends Control
|
||||
## The quiz: the song that is playing, the playlist, the players and their points, and
|
||||
## the popups opened from here (settings, character select, winner and log).
|
||||
##
|
||||
## The main window shows the quiz below its top bar. Pictures and buttons keep the
|
||||
## positions they had in the main window, so the quiz fills the whole window and lets
|
||||
## clicks through where it has nothing.
|
||||
|
||||
## Emitted when another MusicServer is picked in the settings, after the quiz has
|
||||
## reloaded from it.
|
||||
signal server_changed
|
||||
|
||||
@onready
|
||||
var open_button: Button = $Open
|
||||
|
||||
@onready
|
||||
var fileDialog: FileDialog = $FileDialog
|
||||
|
||||
@onready
|
||||
var inspiration_scroll: ScrollContainer = $ScrollContainer
|
||||
|
||||
@onready
|
||||
var inspiration_list: VBoxContainer = $ScrollContainer/VBoxContainer
|
||||
|
||||
@onready
|
||||
var soundtrack_label: Label = $VBoxContainer/SoundtrackLabel
|
||||
|
||||
@onready
|
||||
var song_label: Label = $VBoxContainer/SongLabel
|
||||
|
||||
@onready
|
||||
var add_player_container: HBoxContainer = $Players/VBoxContainer/AddPlayerContainer
|
||||
|
||||
@onready
|
||||
var add_players_button: TextureButton = $Players/VBoxContainer/HBoxContainer/AddPlayersButton
|
||||
|
||||
@onready
|
||||
var add_player_button: Button = $Players/VBoxContainer/AddPlayerContainer/AddPlayerButton
|
||||
|
||||
@onready
|
||||
var new_player_name_field: TextEdit = $Players/VBoxContainer/AddPlayerContainer/PlayerNameField
|
||||
|
||||
@onready
|
||||
var reset_playlist_button: Button = $ResetPlaylistButton
|
||||
|
||||
@onready
|
||||
var reset_points_button: Button = $ResetPointsButton
|
||||
|
||||
@onready
|
||||
var sound_test_button: Button = $SoundTestButton
|
||||
|
||||
@onready
|
||||
var settings_button: Button = $SettingsButton
|
||||
|
||||
@onready
|
||||
var settings_window: Control = $SettingsWindow
|
||||
|
||||
@onready
|
||||
var winner_window: Control = %WinnerWindow
|
||||
|
||||
@onready
|
||||
var show_answer_button: Button = $ShowAnswerButton
|
||||
|
||||
@onready
|
||||
var next_button: Button = $NextButton
|
||||
|
||||
@onready
|
||||
var music_list_scroll: ScrollContainer = $MusicListPanel/ScrollContainer
|
||||
|
||||
@onready
|
||||
var music_list: VBoxContainer = $MusicListPanel/ScrollContainer/MusicList
|
||||
|
||||
@onready
|
||||
var player_container: VBoxContainer = $Players/VBoxContainer
|
||||
|
||||
@onready
|
||||
var players_list_container: VBoxContainer = $Players/VBoxContainer/ScrollContainer/PlayersListVBoxContainer
|
||||
|
||||
@onready
|
||||
var new_character_select: Control = %NewCharacterSelect
|
||||
|
||||
@onready
|
||||
var auto_repeat_song_button: CheckButton = $RepeatSongCheckButton
|
||||
|
||||
@onready
|
||||
var music_player_container: PanelContainer = $MusicPlayer
|
||||
|
||||
@onready
|
||||
var log_window: Control = %Log
|
||||
|
||||
@onready
|
||||
var debug_label: Label = $DebugLabel
|
||||
|
||||
var player: PackedScene = preload("res://Player.tscn")
|
||||
var song_list_object: PackedScene = preload("res://song_list_object.tscn")
|
||||
|
||||
var songs: Array= []
|
||||
var soundtracks: Array = []
|
||||
var current_player: Node
|
||||
|
||||
## True while statistics are off because a player won, until points are reset.
|
||||
var stats_paused_by_win: bool = false
|
||||
|
||||
## Dims the window behind an open popup and keeps clicks from reaching the buttons under it.
|
||||
var popup_backdrop: PopupBackdrop
|
||||
|
||||
## Seconds to wait before retrying a song download step that failed.
|
||||
var song_fetch_retry_seconds: float = 5.0
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
popup_backdrop = PopupBackdrop.add_to(self, _popups())
|
||||
if OS.has_feature("debug"):
|
||||
print("is_debug")
|
||||
debug_label.visible = true
|
||||
Settings.is_debug = true
|
||||
settings_window.set_winning_score(5)
|
||||
settings_window.set_selected_server(4)
|
||||
next_button.pressed.connect(play_next_song)
|
||||
show_answer_button.pressed.connect(show_answer_pressed)
|
||||
sound_test_button.pressed.connect(music_player_container.get_sound_test_song)
|
||||
settings_button.pressed.connect(show_settings)
|
||||
reset_playlist_button.pressed.connect(reset_playlist)
|
||||
reset_points_button.pressed.connect(reset_points)
|
||||
|
||||
new_character_select.connect("new_new_character_selected", _on_new_character_selected)
|
||||
new_player_name_field.connect("enter_key_pressed", add_player)
|
||||
music_player_container.connect("play_next_song", play_next_song)
|
||||
music_player_container.connect("update_song_list", update_song_list)
|
||||
settings_window.connect("server_changed", _on_server_changed)
|
||||
settings_window.visibility_changed.connect(_on_settings_window_visibility_changed)
|
||||
|
||||
add_players_button.pressed.connect(add_players)
|
||||
add_player_button.pressed.connect(add_player)
|
||||
open_button.pressed.connect(open)
|
||||
auto_repeat_song_button.pressed.connect(repeat_song)
|
||||
|
||||
get_suggestion_list()
|
||||
fetch_song_list_at_start()
|
||||
new_character_select.load_characters()
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if event is InputEventKey and event.pressed:
|
||||
if event.alt_pressed && event.keycode == KEY_UP:
|
||||
print("Alt + UP pressed")
|
||||
music_player_container.change_volume(0.05)
|
||||
if event.alt_pressed && event.keycode == KEY_DOWN:
|
||||
print("Alt + DOWN pressed")
|
||||
music_player_container.change_volume(-0.05)
|
||||
if event.alt_pressed && event.ctrl_pressed && event.keycode == KEY_LEFT:
|
||||
print("Ctrl + Alt + LEFT pressed")
|
||||
music_player_container.restart()
|
||||
elif event.alt_pressed && event.keycode == KEY_LEFT:
|
||||
print("Alt + LEFT pressed")
|
||||
music_player_container.seek(-5)
|
||||
if event.alt_pressed && event.keycode == KEY_RIGHT:
|
||||
print("Alt + RIGHT pressed")
|
||||
music_player_container.seek(5)
|
||||
if event.alt_pressed && event.keycode == KEY_L:
|
||||
log_window.toggle()
|
||||
|
||||
|
||||
## Disables the buttons that use the song list while the server syncs it.
|
||||
func lock_for_sync() -> void:
|
||||
sound_test_button.disabled = true
|
||||
reset_playlist_button.disabled = true
|
||||
show_answer_button.disabled = true
|
||||
next_button.disabled = true
|
||||
|
||||
|
||||
## Reloads from the server after a sync and starts a new game.
|
||||
func on_sync_finished() -> void:
|
||||
refresh_from_server()
|
||||
sound_test_button.disabled = false
|
||||
reset_playlist_button.disabled = false
|
||||
show_answer_button.disabled = false
|
||||
reset_points()
|
||||
|
||||
|
||||
## Reloads the soundtracks, playlist and characters from the server.
|
||||
func refresh_from_server() -> void:
|
||||
print("refresh_from_server")
|
||||
get_suggestion_list()
|
||||
reset_playlist()
|
||||
new_character_select.load_characters()
|
||||
|
||||
|
||||
func _on_server_changed() -> void:
|
||||
print("_on_server_changed")
|
||||
refresh_from_server()
|
||||
reset_points()
|
||||
server_changed.emit()
|
||||
|
||||
|
||||
func repeat_song() -> void:
|
||||
Settings.auto_repeat_song = !Settings.auto_repeat_song
|
||||
|
||||
|
||||
func show_answer_pressed() -> void:
|
||||
print("show_answer_pressed")
|
||||
Playlist.song_is_answered()
|
||||
update_song_list()
|
||||
|
||||
|
||||
# Loads the songs already in the server playlist, then starts preloading new ones.
|
||||
# Preloading waits for the list, because loading the list clears the playlist.
|
||||
func fetch_song_list_at_start() -> void:
|
||||
print("fetch_song_list_at_start")
|
||||
var fetch_song_list_at_start_done: Callable = func fetch_songs_at_start_done(data) -> void:
|
||||
Playlist.clear_song_list()
|
||||
if typeof(data) == TYPE_ARRAY:
|
||||
for d: Dictionary in data:
|
||||
var new_song_object: SongObject = SongObject.new()
|
||||
new_song_object.song_number = d.SongNo
|
||||
new_song_object.soundtrack_title = d.Soundtrack
|
||||
new_song_object.song_title = d.Song
|
||||
new_song_object.is_answered = true
|
||||
new_song_object.has_played = true
|
||||
Playlist.add_to_playlist(new_song_object)
|
||||
elif data != null:
|
||||
print("Unexpected data")
|
||||
Playlist.update_last_played_song()
|
||||
update_song_list()
|
||||
if !Playlist.is_song_downloading:
|
||||
fetch_song()
|
||||
MusicServerClient.fetch_song_list(fetch_song_list_at_start_done,
|
||||
_retry_later.bind(fetch_song_list_at_start))
|
||||
|
||||
|
||||
func play_next_song() -> void:
|
||||
print("play_next_song ", Playlist.latest_played_song)
|
||||
if Playlist.first_song_played:
|
||||
Playlist.set_latest_played_song()
|
||||
Playlist.first_song_played = true
|
||||
music_player_container.play_song_object(Playlist.latest_played_song)
|
||||
if !(Playlist.get_preloaded_songs() > 0):
|
||||
next_button.disabled = true
|
||||
if !Playlist.is_song_downloading:
|
||||
fetch_song()
|
||||
|
||||
|
||||
func _popups() -> Array[PopupWindow]:
|
||||
return [settings_window, new_character_select, winner_window, log_window]
|
||||
|
||||
|
||||
# The cache size can be changed in the settings. Waiting until the settings are closed
|
||||
# avoids starting downloads while the user is still clicking the number up and down.
|
||||
func _on_settings_window_visibility_changed() -> void:
|
||||
if !settings_window.visible && !Playlist.is_song_downloading && Playlist.should_preload_more_songs():
|
||||
fetch_song()
|
||||
|
||||
|
||||
func fetch_song() -> void:
|
||||
print("fetch_song")
|
||||
Playlist.is_song_downloading = true
|
||||
_download_random_song(SongObject.new())
|
||||
|
||||
|
||||
# Fetching a song takes three requests. If one fails, only that step is retried, so
|
||||
# the server and the game keep the same songs in their playlists.
|
||||
func _download_random_song(new_song_object: SongObject) -> void:
|
||||
var song_downloaded: Callable = func song_downloaded(body: PackedByteArray) -> void:
|
||||
print("song_downloaded")
|
||||
var sound: AudioStream = AudioStreamMP3.new()
|
||||
sound.data = body
|
||||
new_song_object.song = sound
|
||||
_add_song_to_queue(new_song_object)
|
||||
MusicServerClient.fetch_random_song(Settings.use_low_played_mode, song_downloaded,
|
||||
_retry_later.bind(_download_random_song.bind(new_song_object)))
|
||||
|
||||
|
||||
func _add_song_to_queue(new_song_object: SongObject) -> void:
|
||||
MusicServerClient.add_to_queue(_fetch_song_info.bind(new_song_object),
|
||||
_retry_later.bind(_add_song_to_queue.bind(new_song_object)))
|
||||
|
||||
|
||||
func _fetch_song_info(new_song_object: SongObject) -> void:
|
||||
var song_info_fetched: Callable = func song_info_fetched(data_received: Dictionary) -> void:
|
||||
print("song_info_fetched")
|
||||
new_song_object.song_number = data_received.SongNo
|
||||
new_song_object.soundtrack_title = data_received.Soundtrack
|
||||
new_song_object.song_title = data_received.Song
|
||||
print("new_song_object: ", new_song_object.to_string())
|
||||
Playlist.add_to_playlist(new_song_object)
|
||||
next_button.disabled = false
|
||||
if Playlist.should_preload_more_songs():
|
||||
fetch_song()
|
||||
else:
|
||||
Playlist.is_song_downloading = false
|
||||
if Settings.is_debug:
|
||||
update_song_list()
|
||||
MusicServerClient.fetch_song_info(song_info_fetched,
|
||||
_retry_later.bind(_fetch_song_info.bind(new_song_object)))
|
||||
|
||||
|
||||
func _retry_later(step: Callable) -> void:
|
||||
print("Song download failed, retrying in %s seconds" % song_fetch_retry_seconds)
|
||||
get_tree().create_timer(song_fetch_retry_seconds).timeout.connect(step)
|
||||
|
||||
|
||||
func update_song_list() -> void:
|
||||
print("update_song_list")
|
||||
if Playlist.currently_playing_song >= 0:
|
||||
soundtrack_label.text = Playlist.get_current_soundtrack_title()
|
||||
song_label.text = Playlist.get_current_song_title()
|
||||
Settings.delete_children(music_list)
|
||||
for s: SongObject in Playlist.get_playlist():
|
||||
var new_song_list_object: SongListObject = song_list_object.instantiate()
|
||||
new_song_list_object.song_object = s
|
||||
new_song_list_object.gui_input.connect(song_object_clicked.bind(s.song_number))
|
||||
music_list.add_child(new_song_list_object)
|
||||
|
||||
|
||||
func song_object_clicked(event: InputEvent, song_no: int) -> void:
|
||||
if (event is InputEventMouseButton && event.pressed && event.button_index == MOUSE_BUTTON_LEFT):
|
||||
print("Clicked Song Object: ", Playlist.song_object_array[song_no].to_string())
|
||||
if Settings.character_select_open == false:
|
||||
if Playlist.get_song(song_no) == null:
|
||||
print("has no song, need to download")
|
||||
var song_downloaded: Callable = func song_downloaded(body: PackedByteArray) -> void:
|
||||
var sound: AudioStream = AudioStreamMP3.new()
|
||||
sound.data = body
|
||||
Playlist.add_song_to_song_object(song_no, sound)
|
||||
music_player_container.play_song_object(song_no)
|
||||
MusicServerClient.fetch_song(song_no, song_downloaded)
|
||||
else:
|
||||
print("song already downloaded")
|
||||
music_player_container.play_song_object(song_no)
|
||||
|
||||
|
||||
func reset_playlist() -> void:
|
||||
print("reset_playlist")
|
||||
var playlist_reset_done: Callable = func playlist_reset_done() -> void:
|
||||
Playlist.reset_playlist()
|
||||
fetch_song_list_at_start()
|
||||
|
||||
MusicServerClient.reset_playlist(playlist_reset_done)
|
||||
|
||||
|
||||
func reset_points() -> void:
|
||||
print("reset_points")
|
||||
for player_objects: PlayerObject in Settings.player_array:
|
||||
player_objects.reset_points()
|
||||
load_players()
|
||||
# A new game starts, so turn statistics back on if winning turned them off
|
||||
if stats_paused_by_win:
|
||||
Settings.add_to_stats = true
|
||||
stats_paused_by_win = false
|
||||
|
||||
|
||||
func show_settings() -> void:
|
||||
print("show_settings")
|
||||
settings_window.open()
|
||||
|
||||
|
||||
func get_suggestion_list() -> void:
|
||||
print("get_suggestion_list")
|
||||
var populate_list: Callable = func populate_list(array) -> void:
|
||||
if typeof(array) == TYPE_ARRAY:
|
||||
soundtracks = []
|
||||
Settings.delete_children(inspiration_list)
|
||||
soundtracks.append_array(array)
|
||||
for i: int in soundtracks.size():
|
||||
inspiration_list.add_child(ListRow.create(soundtracks[i], i, 20))
|
||||
else:
|
||||
print("Unexpected data")
|
||||
MusicServerClient.fetch_all_soundtracks(populate_list)
|
||||
|
||||
|
||||
func add_players() -> void:
|
||||
print("add_players")
|
||||
Settings.edit_players = !Settings.edit_players
|
||||
add_player_container.visible = !add_player_container.visible
|
||||
new_player_name_field.grab_focus()
|
||||
|
||||
|
||||
func add_player() -> void:
|
||||
print("add_player")
|
||||
if new_player_name_field.text != "" && Settings.player_array.size() < Settings.MAX_NUMBER_OF_PLAYERS:
|
||||
var new_player_object: PlayerObject = PlayerObject.new(new_player_name_field.text)
|
||||
new_player_name_field.text = ""
|
||||
Settings.player_array.append(new_player_object)
|
||||
new_player_object.connect("first_point_triggerd", music_player_container._on_point_triggered.bind("first"))
|
||||
new_player_object.connect("match_point_triggerd", music_player_container._on_point_triggered.bind("match"))
|
||||
new_player_object.connect("winner_triggerd", _on_player_won.bind(new_player_object))
|
||||
new_player_object.connect("point_given_sound", _on_make_point_given_sound)
|
||||
new_player_object.connect("player_point_given", _on_point_given.bind(new_player_object))
|
||||
new_player_object.connect("player_point_taken", _on_point_taken.bind(new_player_object))
|
||||
load_players()
|
||||
|
||||
|
||||
func load_players() -> void:
|
||||
print("load_players")
|
||||
Settings.delete_children(players_list_container)
|
||||
var counter: int = 0
|
||||
for player_object: PlayerObject in Settings.player_array:
|
||||
print(player_object.player_name)
|
||||
|
||||
var new_player: Player = player.instantiate()
|
||||
new_player.player_object = player_object
|
||||
new_player.list_index = counter
|
||||
|
||||
new_player.connect("change_character_clicked", _on_player_change_character_clicked.bind(new_player, player_object.player_name))
|
||||
new_player.connect("player_removed", _on_player_removed.bind(player_object))
|
||||
|
||||
players_list_container.add_child(new_player)
|
||||
counter += 1
|
||||
|
||||
|
||||
func _on_point_given(player_object: PlayerObject) -> void:
|
||||
print("_on_point_given")
|
||||
log_window.add_log_row(player_object.player_name + " got a point")
|
||||
if Playlist.currently_playing_song >= 0:
|
||||
Playlist.add_point(player_object.id)
|
||||
update_song_list()
|
||||
|
||||
|
||||
func _on_make_point_given_sound() -> void:
|
||||
music_player_container.play_sound_effect(preload("res://sounds/itemopen.wav"))
|
||||
|
||||
|
||||
func _on_point_taken(player_object: PlayerObject) -> void:
|
||||
print("_on_point_taken")
|
||||
log_window.add_log_row(player_object.player_name + " lost a point")
|
||||
music_player_container.play_sound_effect(preload("res://sounds/itemequip.wav"))
|
||||
if Playlist.currently_playing_song >= 0:
|
||||
Playlist.remove_point(player_object.id)
|
||||
update_song_list()
|
||||
|
||||
|
||||
func _on_player_won(player_object: PlayerObject) -> void:
|
||||
print("_on_player_won")
|
||||
winner_window.show_winner(player_object)
|
||||
music_player_container.play_song(preload("res://sounds/winning.mp3"))
|
||||
# Songs played after the game is won don't count towards the statistics
|
||||
if Settings.add_to_stats:
|
||||
Settings.add_to_stats = false
|
||||
stats_paused_by_win = true
|
||||
|
||||
|
||||
func _on_player_removed(player_object: PlayerObject) -> void:
|
||||
print("_on_player_removed ", player_object.player_name)
|
||||
Settings.player_array.erase(player_object)
|
||||
load_players()
|
||||
Playlist.remove_player_from_list(player_object.id)
|
||||
update_song_list()
|
||||
|
||||
|
||||
func _on_player_change_character_clicked(new_player: Player, player_name: String) -> void:
|
||||
print("_on_player_change_character_clicked")
|
||||
current_player = new_player
|
||||
print("Change character for: " + player_name)
|
||||
new_character_select.show_grid(player_name)
|
||||
|
||||
|
||||
func _on_new_character_selected(character_texture: ImageTexture) -> void:
|
||||
print("_on_new_character_selected")
|
||||
current_player.new_character_selected(character_texture)
|
||||
|
||||
|
||||
###Local
|
||||
|
||||
var local_path: String = '/Users/sebastian/ResilioSync/Sorterat_test/Metal Gear Solid 4 - Guns of the Patriots/2-16 Metal Gear Saga.mp3'
|
||||
|
||||
func open() -> void:
|
||||
fileDialog.popup()
|
||||
|
||||
|
||||
func _on_file_dialog_dir_selected(path: String) -> void:
|
||||
print(path)
|
||||
dir_contents(path)
|
||||
|
||||
|
||||
func dir_contents(path: String) -> void:
|
||||
var dir: DirAccess = DirAccess.open(path)
|
||||
if dir:
|
||||
dir.list_dir_begin()
|
||||
var file_name: String = dir.get_next()
|
||||
songs.clear()
|
||||
soundtracks = []
|
||||
while file_name != "":
|
||||
if dir.current_is_dir():
|
||||
#print("Found directory: " + file_name)
|
||||
soundtracks.append(file_name)
|
||||
else:
|
||||
#print("Found file: " + file_name)
|
||||
if file_name.ends_with(".mp3"):
|
||||
songs.append(path + "/" + file_name)
|
||||
file_name = dir.get_next()
|
||||
else:
|
||||
print("An error occurred when trying to access the path.")
|
||||
|
||||
for i: int in soundtracks.size():
|
||||
inspiration_list.add_child(ListRow.create(soundtracks[i], i, 20))
|
||||
@@ -0,0 +1 @@
|
||||
uid://b0od47vseenx4
|
||||
File diff suppressed because it is too large.
Load diff
@@ -11,6 +11,7 @@ var _server: FakeHttpTransport
|
||||
var _snapshot: AutoloadSnapshot
|
||||
var _runner: GdUnitSceneRunner
|
||||
var _window: Control
|
||||
var _quiz: Quiz
|
||||
|
||||
|
||||
func before_test() -> void:
|
||||
@@ -39,6 +40,7 @@ func before_test() -> void:
|
||||
_before_window_starts()
|
||||
_runner = scene_runner("res://MainWindow.tscn")
|
||||
_window = _runner.scene()
|
||||
_quiz = _window.get_node("Quiz")
|
||||
# The debug build lowers the winning score on startup; keep it out of reach
|
||||
Settings.winning_score = 20
|
||||
|
||||
@@ -61,8 +63,9 @@ func _next_song_is(info: Dictionary) -> void:
|
||||
_server.respond_json("/music/info", info)
|
||||
|
||||
|
||||
## Presses a button in the quiz.
|
||||
func _press(button_path: String) -> void:
|
||||
(_window.get_node(button_path) as Button).pressed.emit()
|
||||
(_quiz.get_node(button_path) as Button).pressed.emit()
|
||||
|
||||
|
||||
## Presses Next and waits until the first song is playing.
|
||||
@@ -73,16 +76,16 @@ func _play_first_song() -> void:
|
||||
|
||||
## Adds a player through the name field, the same way the user does.
|
||||
func _add_player(player_name: String) -> PlayerObject:
|
||||
var name_field: TextEdit = _window.get_node(
|
||||
var name_field: TextEdit = _quiz.get_node(
|
||||
"Players/VBoxContainer/AddPlayerContainer/PlayerNameField")
|
||||
name_field.text = player_name
|
||||
_runner.invoke("add_player")
|
||||
_quiz.add_player()
|
||||
return Settings.player_array.back() if !Settings.player_array.is_empty() else null
|
||||
|
||||
|
||||
## Returns the row in the player list that shows [param player_object].
|
||||
func _player_row(player_object: PlayerObject) -> Player:
|
||||
var rows: Node = _window.get_node(
|
||||
var rows: Node = _quiz.get_node(
|
||||
"Players/VBoxContainer/ScrollContainer/PlayersListVBoxContainer")
|
||||
for row: Player in rows.get_children():
|
||||
if row.player_object == player_object:
|
||||
@@ -97,6 +100,15 @@ func _remove_player(player_object: PlayerObject) -> void:
|
||||
await _runner.simulate_frames(1)
|
||||
|
||||
|
||||
## Clicks at [param position], given in the window's 1920x1080 layout coordinates.
|
||||
func _click_at(position: Vector2) -> void:
|
||||
# The runner takes window pixels; the headless test window is much smaller than the
|
||||
# layout, so convert like a real mouse position would be
|
||||
_runner.set_mouse_position(_window.get_viewport().get_final_transform() * position)
|
||||
await _runner.simulate_mouse_button_pressed(MOUSE_BUTTON_LEFT)
|
||||
await _runner.await_input_processed()
|
||||
|
||||
|
||||
## Presses [param keys] together, e.g. [code][KEY_ALT, KEY_1][/code], then releases them.
|
||||
func _press_keys(keys: Array[Key]) -> void:
|
||||
for key: Key in keys:
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
class_name UiTour
|
||||
extends RefCounted
|
||||
## Walks the main window through a fixed set of states against a fake MusicServer:
|
||||
## startup, players added, a song playing, every popup open and a sync.
|
||||
##
|
||||
## Used for screenshots ([code]tools/take_screenshots.gd[/code]) and for the layout
|
||||
## test, so both look at exactly the same states. Nodes are looked up by name in the
|
||||
## main window and in its Quiz and TopBar scenes, so the tour works no matter which of
|
||||
## them a node lives in.
|
||||
|
||||
const SOUNDTRACKS: Array[String] = [
|
||||
"Chrono Trigger", "Final Fantasy VI", "Secret of Mana", "Terranigma",
|
||||
"Earthbound", "Super Metroid", "The Legend of Zelda: A Link to the Past",
|
||||
"Donkey Kong Country 2", "Lufia II", "Illusion of Gaia",
|
||||
]
|
||||
const SONG_INFO: Dictionary = {
|
||||
"SongNo": 0, "Soundtrack": "Chrono Trigger", "Song": "Corridors of Time",
|
||||
}
|
||||
const SYNC_PROGRESS: Dictionary = {"progress": 40, "time_spent": "00:00:02"}
|
||||
const SYNC_RESULT: Dictionary = {
|
||||
"soundtracks_added": ["Terranigma", "Lufia II"],
|
||||
"soundtracks_re_added": [],
|
||||
"soundtracks_changed_title": {"FF6": "Final Fantasy VI"},
|
||||
"soundtracks_changed_content": ["Chrono Trigger"],
|
||||
"soundtracks_removed": ["Old Soundtrack"],
|
||||
"catched_errors": [],
|
||||
"total_time": "00:00:03",
|
||||
}
|
||||
|
||||
## Popups in the order the tour opens them.
|
||||
const POPUPS: Array[String] = [
|
||||
"Search", "SyncWindow", "SettingsWindow", "QRWindow", "NewCharacterSelect",
|
||||
"ShortcutsWindow", "AboutWindow", "WinnerWindow", "Log", "StatisticsPopupPanel",
|
||||
]
|
||||
|
||||
var _main: Control
|
||||
|
||||
|
||||
func _init(main: Control) -> void:
|
||||
_main = main
|
||||
|
||||
|
||||
## Returns a fake server that answers every request the main window makes on the tour.
|
||||
## Call [method FakeHttpTransport.install] before the main window is created.
|
||||
static func make_server() -> FakeHttpTransport:
|
||||
var server: FakeHttpTransport = FakeHttpTransport.new()
|
||||
var mp3: PackedByteArray = FileAccess.get_file_as_bytes("res://tests/data/short.mp3")
|
||||
server.respond_json("/music/all", SOUNDTRACKS)
|
||||
server.respond_json("/music/all/order", SOUNDTRACKS)
|
||||
server.respond_json("/music/list", [])
|
||||
server.respond_json("/characters", [])
|
||||
server.respond_bytes("/music/rand", mp3)
|
||||
server.respond_bytes("/music/addQue", PackedByteArray())
|
||||
server.respond_json("/music/info", SONG_INFO)
|
||||
server.respond_bytes("/music/reset", PackedByteArray())
|
||||
server.respond_bytes("/music/played?song=0", PackedByteArray())
|
||||
server.respond_bytes("/sync", PackedByteArray())
|
||||
server.respond_json("/sync/progress", SYNC_PROGRESS)
|
||||
return server
|
||||
|
||||
|
||||
## Puts Settings and Playlist in the state the tour expects. Call it before the main
|
||||
## window is created.
|
||||
static func prepare_autoloads() -> void:
|
||||
Playlist.reset_playlist()
|
||||
Playlist.first_song_played = false
|
||||
Playlist.is_song_downloading = false
|
||||
Settings.player_array.clear()
|
||||
Settings.edit_players = false
|
||||
Settings.currently_syncing = false
|
||||
Settings.character_select_open = false
|
||||
Settings.add_to_stats = true
|
||||
Settings.hide_next_track = true
|
||||
Settings.use_low_played_mode = false
|
||||
Settings.winning_score = 20
|
||||
# The inspiration list scrolls by itself, which would make every picture different
|
||||
Settings.inspiration_list_speed = 0
|
||||
|
||||
|
||||
## Runs the tour. [param on_state] is called (and awaited) with the name of each state
|
||||
## and the popup that is open in it, or [code]null[/code] when no popup is open.
|
||||
func run(on_state: Callable) -> void:
|
||||
# The debug build lowers the winning score on startup; keep it out of reach
|
||||
Settings.winning_score = 20
|
||||
await _settle()
|
||||
await on_state.call("startup", null)
|
||||
|
||||
_call("add_players")
|
||||
await _settle()
|
||||
await on_state.call("add_player_row", null)
|
||||
|
||||
_add_player("Crono")
|
||||
_add_player("Marle")
|
||||
_call("add_players")
|
||||
await _settle()
|
||||
await on_state.call("players_added", null)
|
||||
|
||||
_call("play_next_song")
|
||||
await _settle()
|
||||
_freeze_music_player()
|
||||
_player_row(0).add_point()
|
||||
_player_row(0).add_point()
|
||||
_player_row(1).add_point()
|
||||
_call("show_answer_pressed")
|
||||
await _settle()
|
||||
_freeze_music_player()
|
||||
_fix_log_times()
|
||||
await _settle()
|
||||
await on_state.call("playing", null)
|
||||
|
||||
for popup_name: String in POPUPS:
|
||||
var popup: Node = await _open_popup(popup_name)
|
||||
await _settle()
|
||||
await on_state.call("popup_" + popup_name, popup)
|
||||
_close_popup(popup)
|
||||
await _settle()
|
||||
|
||||
var sync_window: PopupWindow = _node("SyncWindow")
|
||||
Settings.currently_syncing = false
|
||||
sync_window.has_synced = false
|
||||
sync_window.start_sync()
|
||||
sync_window.sync_timer.stop()
|
||||
sync_window._on_sync_timeout()
|
||||
await _settle()
|
||||
await on_state.call("sync_in_progress", sync_window)
|
||||
sync_window.close()
|
||||
await _settle()
|
||||
await on_state.call("sync_buttons_locked", null)
|
||||
|
||||
_server().respond_json("/sync/progress", SYNC_RESULT)
|
||||
sync_window._on_sync_timeout()
|
||||
sync_window.open()
|
||||
await _settle()
|
||||
await on_state.call("sync_result", sync_window)
|
||||
sync_window.close()
|
||||
await _settle()
|
||||
|
||||
|
||||
## Returns where every Control and Window in [param subtree] is on screen, keyed by its
|
||||
## path from [param main]: [code]{"rect": [x, y, width, height], "visible": bool}[/code].
|
||||
## Nodes added at runtime without a name are keyed by their index, e.g. [code]#2[/code],
|
||||
## so the keys are the same on every run.
|
||||
static func dump_layout(main: Node, subtree: Node) -> Dictionary:
|
||||
var layout: Dictionary = {}
|
||||
var path: String = String(main.get_path_to(subtree))
|
||||
_dump_node(subtree, path, layout)
|
||||
return layout
|
||||
|
||||
|
||||
static func _dump_node(node: Node, path: String, layout: Dictionary) -> void:
|
||||
if node is Control:
|
||||
var rect: Rect2 = (node as Control).get_global_rect()
|
||||
layout[path] = {"rect": _round_rect(rect), "visible": node.is_visible_in_tree()}
|
||||
elif node is Window:
|
||||
var window: Window = node
|
||||
layout[path] = {
|
||||
"rect": _round_rect(Rect2(window.position, window.size)),
|
||||
"visible": window.visible,
|
||||
}
|
||||
for child: Node in node.get_children():
|
||||
var key: String = "#%d" % child.get_index() if "@" in child.name \
|
||||
else String(child.name)
|
||||
_dump_node(child, key if path == "." else path + "/" + key, layout)
|
||||
|
||||
|
||||
static func _round_rect(rect: Rect2) -> Array[float]:
|
||||
return [snappedf(rect.position.x, 0.01), snappedf(rect.position.y, 0.01),
|
||||
snappedf(rect.size.x, 0.01), snappedf(rect.size.y, 0.01)]
|
||||
|
||||
|
||||
## Finds a node of the main window by [param node_name], whether it is a direct child
|
||||
## of the main window or of its Quiz or TopBar scene.
|
||||
func _node(node_name: String) -> Node:
|
||||
for parent: Node in [_main, _main.get_node_or_null("Quiz"),
|
||||
_main.get_node_or_null("TopBar")]:
|
||||
if parent != null && parent.has_node(node_name):
|
||||
return parent.get_node(node_name)
|
||||
push_error("UiTour: no node named " + node_name)
|
||||
return null
|
||||
|
||||
|
||||
## Calls [param method] on the main window, or on its Quiz scene if that has it.
|
||||
func _call(method: String, args: Array = []) -> Variant:
|
||||
var quiz: Node = _main.get_node_or_null("Quiz")
|
||||
var target: Node = quiz if quiz != null && quiz.has_method(method) else _main
|
||||
return target.callv(method, args)
|
||||
|
||||
|
||||
func _server() -> FakeHttpTransport:
|
||||
return MusicServerClient.transport
|
||||
|
||||
|
||||
func _settle() -> void:
|
||||
for i: int in 4:
|
||||
await _main.get_tree().process_frame
|
||||
|
||||
|
||||
func _add_player(player_name: String) -> void:
|
||||
(_node("Players").get_node("VBoxContainer/AddPlayerContainer/PlayerNameField") \
|
||||
as TextEdit).text = player_name
|
||||
_call("add_player")
|
||||
|
||||
|
||||
func _player_row(index: int) -> Player:
|
||||
return _node("Players").get_node(
|
||||
"VBoxContainer/ScrollContainer/PlayersListVBoxContainer").get_child(index)
|
||||
|
||||
|
||||
## Stops the song so the time and progress bar look the same on every run.
|
||||
func _freeze_music_player() -> void:
|
||||
var music_player: MusicPlayer = _node("MusicPlayer")
|
||||
music_player.audio_player.stop()
|
||||
music_player.sound_player.stop()
|
||||
music_player.progress_slider.value = 0
|
||||
music_player.music_time_label.text = "00:00 / 00:01"
|
||||
|
||||
|
||||
## The log starts each row with the time of day; use a fixed time instead.
|
||||
func _fix_log_times() -> void:
|
||||
var log_window: PopupWindow = _node("Log")
|
||||
for row: Label in log_window.log_box.get_children():
|
||||
row.text = "12:00:00 " + row.text.substr(9)
|
||||
|
||||
|
||||
func _open_popup(popup_name: String) -> Node:
|
||||
var popup: Node = _node(popup_name)
|
||||
match popup_name:
|
||||
"SyncWindow":
|
||||
# Opening the window starts a sync; show the empty window instead
|
||||
popup.open()
|
||||
"NewCharacterSelect":
|
||||
popup.show_grid("Crono")
|
||||
"WinnerWindow":
|
||||
popup.show_winner(Settings.player_array[0])
|
||||
"StatisticsPopupPanel":
|
||||
_call("get_statistics")
|
||||
_:
|
||||
popup.open()
|
||||
return popup
|
||||
|
||||
|
||||
func _close_popup(popup: Node) -> void:
|
||||
if popup is PopupWindow:
|
||||
popup.close()
|
||||
else:
|
||||
popup.visible = false
|
||||
@@ -0,0 +1 @@
|
||||
uid://v42qdc68bn17
|
||||
@@ -0,0 +1,98 @@
|
||||
extends GdUnitTestSuite
|
||||
## Checks that every node of the main window is where it was before the window was
|
||||
## split into scenes.
|
||||
##
|
||||
## [code]tests/data/layout_baseline.json[/code] was written by
|
||||
## [code]tools/take_screenshots.gd[/code] before the split. It holds the position, size
|
||||
## and visibility of every node in each UiTour state, keyed by the node's old path. This
|
||||
## test runs the same tour and compares, after moving each old path to where the node
|
||||
## lives now.
|
||||
|
||||
const BASELINE_PATH: String = "res://tests/data/layout_baseline.json"
|
||||
|
||||
## Nodes that moved from the main window into the Quiz scene, by their old name.
|
||||
const QUIZ_NODES: Array[String] = [
|
||||
"Open", "FileDialog", "MusicListPanel", "ScrollContainer", "VBoxContainer",
|
||||
"MusicPlayer", "Players", "ResetPlaylistButton", "ResetPointsButton", "SettingsButton",
|
||||
"SoundTestButton", "ShowAnswerButton", "NextButton", "RepeatSongCheckButton",
|
||||
"DebugLabel", "SettingsWindow", "NewCharacterSelect", "WinnerWindow", "Log",
|
||||
]
|
||||
|
||||
## Nodes that moved from the main window into the TopBar scene, by their old name.
|
||||
const TOP_BAR_NODES: Array[String] = [
|
||||
"TopPanelContainer", "Title", "SearchButton", "SyncButton", "StatisticsButton",
|
||||
"ShortcutsButton", "AboutButton", "QRButton",
|
||||
]
|
||||
|
||||
## How far a node may be from its old place, in pixels.
|
||||
const TOLERANCE: float = 0.5
|
||||
|
||||
var _snapshot: AutoloadSnapshot
|
||||
var _server: FakeHttpTransport
|
||||
var _main: Control
|
||||
var _layout: Dictionary = {}
|
||||
|
||||
|
||||
func before() -> void:
|
||||
_snapshot = AutoloadSnapshot.take([Settings, Playlist])
|
||||
UiTour.prepare_autoloads()
|
||||
_server = UiTour.make_server().install()
|
||||
# Laid out at the size the baseline was taken at, like the screenshots
|
||||
var viewport: SubViewport = auto_free(SubViewport.new())
|
||||
viewport.size = Vector2i(1920, 1080)
|
||||
viewport.gui_embed_subwindows = true
|
||||
Engine.get_main_loop().root.add_child(viewport)
|
||||
_main = load("res://MainWindow.tscn").instantiate()
|
||||
viewport.add_child(_main)
|
||||
await UiTour.new(_main).run(func(state: String, popup: Node) -> void:
|
||||
_layout[state] = UiTour.dump_layout(_main, _main if popup == null else popup))
|
||||
|
||||
|
||||
func after() -> void:
|
||||
_main.free()
|
||||
await Engine.get_main_loop().process_frame
|
||||
_server.uninstall()
|
||||
_snapshot.restore()
|
||||
|
||||
|
||||
## Where the node that used to be at [param old_path] in the main window is now.
|
||||
static func new_path(old_path: String) -> String:
|
||||
var first: String = old_path.get_slice("/", 0)
|
||||
if first in QUIZ_NODES:
|
||||
return "Quiz/" + old_path
|
||||
if first in TOP_BAR_NODES:
|
||||
return "TopBar/" + old_path
|
||||
return old_path
|
||||
|
||||
|
||||
func _baseline() -> Dictionary:
|
||||
return JSON.parse_string(FileAccess.get_file_as_string(BASELINE_PATH))
|
||||
|
||||
|
||||
func test_tour_has_the_same_states_as_the_baseline() -> void:
|
||||
assert_array(_layout.keys()).contains_exactly_in_any_order(_baseline().keys())
|
||||
|
||||
|
||||
func test_every_node_is_where_it_was() -> void:
|
||||
var problems: Array[String] = []
|
||||
var baseline: Dictionary = _baseline()
|
||||
for state: String in baseline:
|
||||
var actual: Dictionary = _layout.get(state, {})
|
||||
for old_path: String in baseline[state]:
|
||||
var path: String = new_path(old_path)
|
||||
var expected: Dictionary = baseline[state][old_path]
|
||||
if !actual.has(path):
|
||||
problems.append("%s: %s is missing (was %s)" % [state, path, old_path])
|
||||
continue
|
||||
var rect: Array = actual[path].rect
|
||||
for i: int in 4:
|
||||
if absf(rect[i] - expected.rect[i]) > TOLERANCE:
|
||||
problems.append("%s: %s is at %s, was %s"
|
||||
% [state, path, rect, expected.rect])
|
||||
break
|
||||
if actual[path].visible != expected.visible:
|
||||
problems.append("%s: %s visible is %s, was %s"
|
||||
% [state, path, actual[path].visible, expected.visible])
|
||||
assert_array(problems).override_failure_message(
|
||||
"%d nodes moved:\n%s" % [problems.size(), "\n".join(problems.slice(0, 30))]) \
|
||||
.is_empty()
|
||||
@@ -0,0 +1 @@
|
||||
uid://b2077235y1g0f
|
||||
@@ -7,7 +7,7 @@ func test_startup_loads_soundtracks_and_preloads_a_song() -> void:
|
||||
assert_int(_server.count("/music/all")).is_equal(1)
|
||||
assert_array(_server.requested_paths).contains(["/music/rand", "/music/addQue", "/music/info"])
|
||||
assert_int(Playlist.song_object_array.size()).is_equal(1)
|
||||
assert_bool((_window.get_node("NextButton") as Button).disabled).is_false()
|
||||
assert_bool((_quiz.get_node("NextButton") as Button).disabled).is_false()
|
||||
|
||||
|
||||
func test_low_played_mode_fetches_from_low_played_endpoint() -> void:
|
||||
@@ -15,7 +15,7 @@ func test_low_played_mode_fetches_from_low_played_endpoint() -> void:
|
||||
_server.respond_bytes(
|
||||
"/music/rand/low", FileAccess.get_file_as_bytes("res://tests/data/short.mp3"))
|
||||
|
||||
_runner.invoke("fetch_song")
|
||||
_quiz.fetch_song()
|
||||
|
||||
assert_int(_server.count("/music/rand/low")).is_equal(1)
|
||||
|
||||
@@ -33,7 +33,7 @@ func test_next_plays_preloaded_song_and_fetches_another() -> void:
|
||||
|
||||
|
||||
func test_title_is_hidden_until_answer_is_shown() -> void:
|
||||
var soundtrack_label: Label = _window.get_node("VBoxContainer/SoundtrackLabel")
|
||||
var soundtrack_label: Label = _quiz.get_node("VBoxContainer/SoundtrackLabel")
|
||||
_press("NextButton")
|
||||
await _runner.simulate_frames(2)
|
||||
|
||||
@@ -81,13 +81,13 @@ func test_cannot_add_player_without_name() -> void:
|
||||
|
||||
|
||||
func test_failed_song_download_is_retried_until_the_server_answers() -> void:
|
||||
_window.song_fetch_retry_seconds = 0.05
|
||||
_quiz.song_fetch_retry_seconds = 0.05
|
||||
var songs_before: int = Playlist.song_object_array.size()
|
||||
var mp3: PackedByteArray = FileAccess.get_file_as_bytes("res://tests/data/short.mp3")
|
||||
_server.respond_bytes("/music/rand", PackedByteArray(), 503)
|
||||
var rand_requests_before: int = _server.count("/music/rand")
|
||||
|
||||
_runner.invoke("fetch_song")
|
||||
_quiz.fetch_song()
|
||||
await await_millis(200)
|
||||
|
||||
assert_int(_server.count("/music/rand") - rand_requests_before).is_greater(1)
|
||||
@@ -102,13 +102,13 @@ func test_failed_song_download_is_retried_until_the_server_answers() -> void:
|
||||
|
||||
|
||||
func test_failed_song_info_retries_only_that_step() -> void:
|
||||
_window.song_fetch_retry_seconds = 0.05
|
||||
_quiz.song_fetch_retry_seconds = 0.05
|
||||
var songs_before: int = Playlist.song_object_array.size()
|
||||
_server.respond_bytes("/music/info", PackedByteArray(), 503)
|
||||
var rand_before: int = _server.count("/music/rand")
|
||||
var queue_before: int = _server.count("/music/addQue")
|
||||
|
||||
_runner.invoke("fetch_song")
|
||||
_quiz.fetch_song()
|
||||
await await_millis(200)
|
||||
_next_song_is(SONG_INFO[1])
|
||||
await await_millis(200)
|
||||
@@ -124,7 +124,7 @@ func test_failed_song_info_retries_only_that_step() -> void:
|
||||
func test_preloads_as_many_songs_as_the_cache_setting() -> void:
|
||||
Playlist.number_of_tracks_to_preload = 3
|
||||
|
||||
_runner.invoke("fetch_song")
|
||||
_quiz.fetch_song()
|
||||
|
||||
assert_int(Playlist.get_preloaded_songs()).is_equal(3)
|
||||
assert_bool(Playlist.is_song_downloading).is_false()
|
||||
@@ -132,7 +132,7 @@ func test_preloads_as_many_songs_as_the_cache_setting() -> void:
|
||||
|
||||
func test_playing_a_song_tops_the_cache_up_again() -> void:
|
||||
Playlist.number_of_tracks_to_preload = 3
|
||||
_runner.invoke("fetch_song")
|
||||
_quiz.fetch_song()
|
||||
|
||||
await _play_first_song()
|
||||
|
||||
@@ -141,13 +141,13 @@ func test_playing_a_song_tops_the_cache_up_again() -> void:
|
||||
|
||||
|
||||
func test_cache_keeps_filling_after_a_failed_download() -> void:
|
||||
_window.song_fetch_retry_seconds = 0.05
|
||||
_quiz.song_fetch_retry_seconds = 0.05
|
||||
Playlist.number_of_tracks_to_preload = 3
|
||||
var mp3: PackedByteArray = FileAccess.get_file_as_bytes("res://tests/data/short.mp3")
|
||||
# One more download works, then the server stops answering
|
||||
_server.fail_after("/music/rand", 1)
|
||||
|
||||
_runner.invoke("fetch_song")
|
||||
_quiz.fetch_song()
|
||||
await await_millis(150)
|
||||
|
||||
assert_int(Playlist.get_preloaded_songs()).is_equal(2)
|
||||
@@ -165,11 +165,11 @@ func _open_settings() -> void:
|
||||
|
||||
|
||||
func _close_settings() -> void:
|
||||
(_window.get_node("SettingsWindow") as Control).visible = false
|
||||
(_quiz.get_node("SettingsWindow") as Control).visible = false
|
||||
|
||||
|
||||
func _press_settings_button(unique_name: String) -> void:
|
||||
(_window.get_node("SettingsWindow").get_node("%" + unique_name) as Button).pressed.emit()
|
||||
(_quiz.get_node("SettingsWindow").get_node("%" + unique_name) as Button).pressed.emit()
|
||||
|
||||
|
||||
func test_raising_cache_size_does_not_download_while_settings_are_open() -> void:
|
||||
@@ -224,12 +224,12 @@ func test_show_answer_before_first_song_does_not_reveal_next_song() -> void:
|
||||
|
||||
await _play_first_song()
|
||||
|
||||
assert_str((_window.get_node("VBoxContainer/SoundtrackLabel") as Label).text) \
|
||||
assert_str((_quiz.get_node("VBoxContainer/SoundtrackLabel") as Label).text) \
|
||||
.is_equal("????????")
|
||||
|
||||
|
||||
func test_log_adds_one_row_per_point_with_padded_time() -> void:
|
||||
var log_box: VBoxContainer = _window.get_node("%Log").get_node("%LogVBoxContainer")
|
||||
var log_box: VBoxContainer = _quiz.get_node("%Log").get_node("%LogVBoxContainer")
|
||||
var crono: PlayerObject = _add_player("Crono")
|
||||
await _play_first_song()
|
||||
|
||||
@@ -243,7 +243,7 @@ func test_log_adds_one_row_per_point_with_padded_time() -> void:
|
||||
|
||||
|
||||
func test_inspiration_list_alternates_row_backgrounds() -> void:
|
||||
var rows: Array[Node] = _window.get_node("ScrollContainer/VBoxContainer").get_children()
|
||||
var rows: Array[Node] = _quiz.get_node("ScrollContainer/VBoxContainer").get_children()
|
||||
|
||||
assert_int(rows.size()).is_equal(2)
|
||||
assert_str((rows[0] as ListRow).text).is_equal("Chrono Trigger")
|
||||
|
||||
@@ -28,7 +28,7 @@ func test_next_plays_the_preloaded_song_after_a_slow_song_list() -> void:
|
||||
|
||||
|
||||
func test_failed_song_list_is_retried_before_preloading() -> void:
|
||||
_window.song_fetch_retry_seconds = 0.05
|
||||
_quiz.song_fetch_retry_seconds = 0.05
|
||||
_server.respond_json("/music/list", [], 503)
|
||||
_server.release("/music/list")
|
||||
|
||||
|
||||
@@ -143,7 +143,7 @@ func test_player_list_shows_rows_in_player_order() -> void:
|
||||
await _remove_player(_crono)
|
||||
var robo: PlayerObject = _add_player("Robo")
|
||||
|
||||
var rows: Array[Node] = _window.get_node(
|
||||
var rows: Array[Node] = _quiz.get_node(
|
||||
"Players/VBoxContainer/ScrollContainer/PlayersListVBoxContainer").get_children()
|
||||
|
||||
assert_array(rows.map(func(row: Player) -> PlayerObject: return row.player_object)) \
|
||||
|
||||
@@ -1,28 +1,27 @@
|
||||
extends MainWindowTestSuite
|
||||
## All popups share PopupWindow: clicking outside the panel closes them, the backdrop
|
||||
## dims the window behind them, and the closing click does not reach the main window.
|
||||
## The main window and the quiz each have their own popups and their own backdrop.
|
||||
|
||||
const POPUP_NODES: Array[String] = [
|
||||
"Search", "SyncWindow", "SettingsWindow", "QRWindow", "NewCharacterSelect",
|
||||
"ShortcutsWindow", "AboutWindow", "WinnerWindow", "Log",
|
||||
## Popups opened from the top bar, in the main window.
|
||||
const MAIN_POPUPS: Array[String] = [
|
||||
"Search", "SyncWindow", "QRWindow", "ShortcutsWindow", "AboutWindow",
|
||||
]
|
||||
|
||||
## Popups opened from the quiz, in the Quiz scene.
|
||||
const QUIZ_POPUPS: Array[String] = [
|
||||
"SettingsWindow", "NewCharacterSelect", "WinnerWindow", "Log",
|
||||
]
|
||||
|
||||
|
||||
func _popup(node_name: String) -> PopupWindow:
|
||||
return _window.get_node(node_name)
|
||||
return _quiz.get_node(node_name) if node_name in QUIZ_POPUPS \
|
||||
else _window.get_node(node_name)
|
||||
|
||||
|
||||
func _backdrop() -> ColorRect:
|
||||
return _window.get_node("PopupBackdrop")
|
||||
|
||||
|
||||
## Clicks at [param position], given in the window's 1920x1080 layout coordinates.
|
||||
func _click_at(position: Vector2) -> void:
|
||||
# The runner takes window pixels; the headless test window is much smaller than the
|
||||
# layout, so convert like a real mouse position would be
|
||||
_runner.set_mouse_position(_window.get_viewport().get_final_transform() * position)
|
||||
await _runner.simulate_mouse_button_pressed(MOUSE_BUTTON_LEFT)
|
||||
await _runner.await_input_processed()
|
||||
## The backdrop that shows behind [param popup].
|
||||
func _backdrop(popup: PopupWindow = _popup("SettingsWindow")) -> ColorRect:
|
||||
return popup.get_parent().get_node("PopupBackdrop")
|
||||
|
||||
|
||||
## A point on screen outside the popup's panel.
|
||||
@@ -32,34 +31,40 @@ func _outside(popup: PopupWindow) -> Vector2:
|
||||
else Vector2(rect.position.x - 5, rect.position.y - 5)
|
||||
|
||||
|
||||
func test_backdrop_is_hidden_without_popups() -> void:
|
||||
assert_bool(_backdrop().visible).is_false()
|
||||
func test_backdrops_are_hidden_without_popups() -> void:
|
||||
assert_bool(_backdrop(_popup("SettingsWindow")).visible).is_false()
|
||||
assert_bool(_backdrop(_popup("AboutWindow")).visible).is_false()
|
||||
|
||||
|
||||
func test_backdrop_covers_the_whole_window() -> void:
|
||||
_popup("SettingsWindow").open()
|
||||
func test_backdrops_cover_the_whole_window() -> void:
|
||||
for node_name: String in ["SettingsWindow", "AboutWindow"]:
|
||||
var popup: PopupWindow = _popup(node_name)
|
||||
popup.open()
|
||||
|
||||
assert_vector(_backdrop().get_global_rect().size).is_equal(_window.get_viewport_rect().size)
|
||||
assert_object(_backdrop(popup).get_global_rect()).override_failure_message(
|
||||
"%s: backdrop does not cover the window" % node_name) \
|
||||
.is_equal(_window.get_viewport_rect())
|
||||
popup.close()
|
||||
|
||||
|
||||
func test_every_popup_is_a_popup_window() -> void:
|
||||
for node_name: String in POPUP_NODES:
|
||||
assert_object(_window.get_node(node_name)).is_instanceof(PopupWindow)
|
||||
for node_name: String in MAIN_POPUPS + QUIZ_POPUPS:
|
||||
assert_object(_popup(node_name)).is_instanceof(PopupWindow)
|
||||
|
||||
|
||||
func test_every_popup_shows_backdrop_and_closes_on_outside_click() -> void:
|
||||
for node_name: String in POPUP_NODES:
|
||||
for node_name: String in MAIN_POPUPS + QUIZ_POPUPS:
|
||||
var popup: PopupWindow = _popup(node_name)
|
||||
popup.open()
|
||||
await _runner.simulate_frames(1)
|
||||
assert_bool(_backdrop().visible).override_failure_message(
|
||||
assert_bool(_backdrop(popup).visible).override_failure_message(
|
||||
"%s: backdrop not shown" % node_name).is_true()
|
||||
|
||||
await _click_at(_outside(popup))
|
||||
|
||||
assert_bool(popup.visible).override_failure_message(
|
||||
"%s: not closed by outside click" % node_name).is_false()
|
||||
assert_bool(_backdrop().visible).override_failure_message(
|
||||
assert_bool(_backdrop(popup).visible).override_failure_message(
|
||||
"%s: backdrop still shown" % node_name).is_false()
|
||||
|
||||
|
||||
@@ -75,7 +80,7 @@ func test_click_inside_popup_keeps_it_open() -> void:
|
||||
|
||||
func test_closing_click_does_not_press_button_behind_popup() -> void:
|
||||
var settings: PopupWindow = _popup("SettingsWindow")
|
||||
var reset_button: Button = _window.get_node("ResetPlaylistButton")
|
||||
var reset_button: Button = _quiz.get_node("ResetPlaylistButton")
|
||||
settings.open()
|
||||
await _runner.simulate_frames(1)
|
||||
assert_bool(settings.panel.get_global_rect().intersects(reset_button.get_global_rect())) \
|
||||
@@ -88,7 +93,7 @@ func test_closing_click_does_not_press_button_behind_popup() -> void:
|
||||
|
||||
|
||||
func test_button_works_again_after_popup_closed() -> void:
|
||||
var reset_button: Button = _window.get_node("ResetPlaylistButton")
|
||||
var reset_button: Button = _quiz.get_node("ResetPlaylistButton")
|
||||
|
||||
await _click_at(reset_button.get_global_rect().get_center())
|
||||
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
extends MainWindowTestSuite
|
||||
## The main window holds the top bar, the quiz and the popups opened from the top bar.
|
||||
## These tests check that the parts are where they belong and still work together:
|
||||
## clicks reach the right scene, popups dim the whole window, and syncing, changing
|
||||
## server and the keyboard shortcuts reach both the main window and the quiz.
|
||||
|
||||
const MAIN_WINDOW_CHILDREN: Array[String] = [
|
||||
"TopBar", "Quiz", "PopupBackdrop", "Search", "SyncWindow", "QRWindow",
|
||||
"ShortcutsWindow", "StatisticsPopupPanel", "AboutWindow",
|
||||
]
|
||||
|
||||
const TOP_BAR_CHILDREN: Array[String] = [
|
||||
"TopPanelContainer", "Title", "SyncButton", "SearchButton", "StatisticsButton",
|
||||
"ShortcutsButton", "AboutButton", "QRButton",
|
||||
]
|
||||
|
||||
## Each top bar button and the popup it opens in the main window.
|
||||
const TOP_BAR_POPUPS: Dictionary = {
|
||||
"SearchButton": "Search",
|
||||
"SyncButton": "SyncWindow",
|
||||
"StatisticsButton": "StatisticsPopupPanel",
|
||||
"ShortcutsButton": "ShortcutsWindow",
|
||||
"AboutButton": "AboutWindow",
|
||||
"QRButton": "QRWindow",
|
||||
}
|
||||
|
||||
|
||||
func _top_bar_button(button_name: String) -> Button:
|
||||
return _window.get_node("TopBar/" + button_name)
|
||||
|
||||
|
||||
func _children(node: Node) -> Array[String]:
|
||||
var names: Array[String] = []
|
||||
for child: Node in node.get_children():
|
||||
names.append(String(child.name))
|
||||
return names
|
||||
|
||||
|
||||
func _center(control: Control) -> Vector2:
|
||||
return control.get_global_rect().get_center()
|
||||
|
||||
|
||||
func test_main_window_holds_only_top_bar_quiz_and_its_popups() -> void:
|
||||
assert_array(_children(_window)).contains_exactly_in_any_order(MAIN_WINDOW_CHILDREN)
|
||||
|
||||
|
||||
func test_top_bar_is_drawn_below_the_quiz() -> void:
|
||||
# The quiz popups' backdrop has to cover the top bar too
|
||||
assert_int(_window.get_node("TopBar").get_index()) \
|
||||
.is_less(_window.get_node("Quiz").get_index())
|
||||
|
||||
|
||||
func test_top_bar_holds_only_title_and_buttons() -> void:
|
||||
assert_array(_children(_window.get_node("TopBar"))) \
|
||||
.contains_exactly_in_any_order(TOP_BAR_CHILDREN)
|
||||
|
||||
|
||||
func test_quiz_and_top_bar_let_clicks_through_where_they_are_empty() -> void:
|
||||
assert_int(_quiz.mouse_filter).is_equal(Control.MOUSE_FILTER_IGNORE)
|
||||
assert_int((_window.get_node("TopBar") as Control).mouse_filter) \
|
||||
.is_equal(Control.MOUSE_FILTER_IGNORE)
|
||||
|
||||
|
||||
func test_clicking_each_top_bar_button_opens_its_popup() -> void:
|
||||
for button_name: String in TOP_BAR_POPUPS:
|
||||
var popup: Node = _window.get_node(TOP_BAR_POPUPS[button_name])
|
||||
|
||||
await _click_at(_center(_top_bar_button(button_name)))
|
||||
|
||||
assert_bool(popup.visible).override_failure_message(
|
||||
"%s did not open %s" % [button_name, popup.name]).is_true()
|
||||
if popup is PopupWindow:
|
||||
popup.close()
|
||||
else:
|
||||
popup.visible = false
|
||||
await _runner.simulate_frames(1)
|
||||
|
||||
|
||||
func test_clicking_quiz_buttons_plays_and_shows_answer() -> void:
|
||||
await _click_at(_center(_quiz.get_node("NextButton")))
|
||||
await _runner.simulate_frames(2)
|
||||
assert_int(Playlist.currently_playing_song).is_equal(0)
|
||||
assert_str((_quiz.get_node("VBoxContainer/SoundtrackLabel") as Label).text) \
|
||||
.is_not_equal("Chrono Trigger")
|
||||
|
||||
await _click_at(_center(_quiz.get_node("ShowAnswerButton")))
|
||||
|
||||
assert_str((_quiz.get_node("VBoxContainer/SoundtrackLabel") as Label).text) \
|
||||
.is_equal("Chrono Trigger")
|
||||
|
||||
|
||||
func test_quiz_popup_backdrop_covers_the_top_bar() -> void:
|
||||
var settings: PopupWindow = _quiz.get_node("SettingsWindow")
|
||||
settings.open()
|
||||
await _runner.simulate_frames(1)
|
||||
|
||||
var backdrop: Control = _quiz.get_node("PopupBackdrop")
|
||||
assert_bool(backdrop.visible).is_true()
|
||||
assert_bool(backdrop.get_global_rect().encloses(
|
||||
(_window.get_node("TopBar/TopPanelContainer") as Control).get_global_rect())) \
|
||||
.is_true()
|
||||
|
||||
|
||||
func test_click_on_top_bar_while_quiz_popup_is_open_only_closes_it() -> void:
|
||||
var settings: PopupWindow = _quiz.get_node("SettingsWindow")
|
||||
var search: PopupWindow = _window.get_node("Search")
|
||||
settings.open()
|
||||
await _runner.simulate_frames(1)
|
||||
|
||||
await _click_at(_center(_top_bar_button("SearchButton")))
|
||||
|
||||
assert_bool(settings.visible).is_false()
|
||||
assert_bool(search.visible).is_false()
|
||||
|
||||
|
||||
func test_sync_disables_quiz_buttons_until_finished() -> void:
|
||||
_server.respond_bytes("/sync", PackedByteArray())
|
||||
_server.respond_json("/sync/progress", {"progress": 10, "time_spent": "00:00:01"})
|
||||
var sync_window: PopupWindow = _window.get_node("SyncWindow")
|
||||
|
||||
_top_bar_button("SyncButton").pressed.emit()
|
||||
sync_window.sync_timer.stop()
|
||||
|
||||
for button_name: String in ["SoundTestButton", "ResetPlaylistButton", "ShowAnswerButton",
|
||||
"NextButton"]:
|
||||
assert_bool((_quiz.get_node(button_name) as Button).disabled) \
|
||||
.override_failure_message(button_name + " not disabled").is_true()
|
||||
assert_bool(_top_bar_button("SyncButton").disabled).is_false()
|
||||
|
||||
_server.respond_json("/sync/progress", UiTour.SYNC_RESULT)
|
||||
var reloads_before: int = _server.count("/music/all/order")
|
||||
sync_window._on_sync_timeout()
|
||||
await _runner.simulate_frames(2)
|
||||
|
||||
for button_name: String in ["SoundTestButton", "ResetPlaylistButton", "ShowAnswerButton",
|
||||
"NextButton"]:
|
||||
assert_bool((_quiz.get_node(button_name) as Button).disabled) \
|
||||
.override_failure_message(button_name + " still disabled").is_false()
|
||||
# The search list in the main window and the quiz playlist are both reloaded
|
||||
assert_int(_server.count("/music/all/order")).is_equal(reloads_before + 1)
|
||||
assert_int(_server.count("/music/reset")).is_equal(1)
|
||||
|
||||
|
||||
func test_changing_server_reloads_search_and_quiz() -> void:
|
||||
var search_loads: int = _server.count("/music/all/order")
|
||||
var inspiration_loads: int = _server.count("/music/all")
|
||||
var player: PlayerObject = _add_player("Crono")
|
||||
player.add_point()
|
||||
|
||||
_quiz.get_node("SettingsWindow").server_changed.emit()
|
||||
|
||||
assert_int(_server.count("/music/all/order")).is_equal(search_loads + 1)
|
||||
assert_int(_server.count("/music/all")).is_equal(inspiration_loads + 1)
|
||||
assert_int(_server.count("/music/reset")).is_equal(1)
|
||||
assert_int(player.player_score).is_equal(0)
|
||||
|
||||
|
||||
func test_alt_k_opens_shortcuts_and_alt_l_opens_log() -> void:
|
||||
await _press_keys([KEY_ALT, KEY_K])
|
||||
assert_bool((_window.get_node("ShortcutsWindow") as Control).visible).is_true()
|
||||
|
||||
await _press_keys([KEY_ALT, KEY_L])
|
||||
assert_bool((_quiz.get_node("Log") as Control).visible).is_true()
|
||||
|
||||
|
||||
func test_alt_down_lowers_the_volume() -> void:
|
||||
var bus: int = AudioServer.get_bus_index("Master")
|
||||
var volume_db: float = AudioServer.get_bus_volume_db(bus)
|
||||
var slider: HSlider = _quiz.get_node("MusicPlayer").volume_slider
|
||||
var volume: float = slider.value
|
||||
|
||||
await _press_keys([KEY_ALT, KEY_DOWN])
|
||||
|
||||
assert_float(slider.value).is_less(volume)
|
||||
AudioServer.set_bus_volume_db(bus, volume_db)
|
||||
|
||||
|
||||
func test_alt_enter_switches_fullscreen_and_settings_shows_it() -> void:
|
||||
var settings: PopupWindow = _quiz.get_node("SettingsWindow")
|
||||
var was_fullscreen: bool = Settings.fullscreen
|
||||
|
||||
await _press_keys([KEY_ALT, KEY_ENTER])
|
||||
settings.open()
|
||||
|
||||
assert_bool(Settings.fullscreen).is_equal(!was_fullscreen)
|
||||
assert_bool(settings.fullscreen_button.button_pressed).is_equal(!was_fullscreen)
|
||||
await _press_keys([KEY_ALT, KEY_ENTER])
|
||||
assert_bool(Settings.fullscreen).is_equal(was_fullscreen)
|
||||
|
||||
|
||||
func test_statistics_counts_the_soundtracks() -> void:
|
||||
_top_bar_button("StatisticsButton").pressed.emit()
|
||||
|
||||
assert_str((_window.get_node("StatisticsPopupPanel/StatisticsLabel") as Label).text) \
|
||||
.is_equal("Total amount of soundtracks in the playlist: 2")
|
||||
@@ -0,0 +1 @@
|
||||
uid://c6lqhrfu02227
|
||||
@@ -12,7 +12,7 @@ func before_test() -> void:
|
||||
|
||||
|
||||
func _stats_checkbox() -> CheckButton:
|
||||
return _window.get_node("SettingsWindow").get_node("%AddToDatabaseCheckButton")
|
||||
return _quiz.get_node("SettingsWindow").get_node("%AddToDatabaseCheckButton")
|
||||
|
||||
|
||||
func test_winning_turns_statistics_off() -> void:
|
||||
@@ -57,7 +57,7 @@ func test_songs_after_a_win_are_not_counted() -> void:
|
||||
|
||||
func test_winning_score_cannot_go_below_one() -> void:
|
||||
_press("SettingsButton")
|
||||
var lower_button: Button = _window.get_node("SettingsWindow").get_node("%LowerButton")
|
||||
var lower_button: Button = _quiz.get_node("SettingsWindow").get_node("%LowerButton")
|
||||
|
||||
for i: int in 3:
|
||||
lower_button.pressed.emit()
|
||||
|
||||
Executable
+55
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compares two folders of screenshots from tools/take_screenshots.gd pixel by pixel.
|
||||
|
||||
Usage: tools/compare_screenshots.py <before dir> <after dir> [<diff dir>]
|
||||
|
||||
Prints how many pixels differ in each screenshot. With a diff dir, writes an image
|
||||
for each screenshot that differs, with the changed pixels in red over a faded copy of
|
||||
the after picture. Exits with 1 if any screenshot differs or is missing.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image, ImageChops
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if len(sys.argv) < 3:
|
||||
print(__doc__)
|
||||
return 2
|
||||
before_dir, after_dir = Path(sys.argv[1]), Path(sys.argv[2])
|
||||
diff_dir = Path(sys.argv[3]) if len(sys.argv) > 3 else None
|
||||
if diff_dir:
|
||||
diff_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
names = sorted({p.name for p in before_dir.glob("*.png")}
|
||||
| {p.name for p in after_dir.glob("*.png")})
|
||||
failed = False
|
||||
for name in names:
|
||||
before_path, after_path = before_dir / name, after_dir / name
|
||||
if not before_path.exists() or not after_path.exists():
|
||||
print(f"{name}: missing in {'before' if not before_path.exists() else 'after'}")
|
||||
failed = True
|
||||
continue
|
||||
before = Image.open(before_path).convert("RGB")
|
||||
after = Image.open(after_path).convert("RGB")
|
||||
if before.size != after.size:
|
||||
print(f"{name}: size {before.size} -> {after.size}")
|
||||
failed = True
|
||||
continue
|
||||
mask = ImageChops.difference(before, after).convert("L").point(
|
||||
lambda value: 255 if value else 0)
|
||||
changed = mask.histogram()[255]
|
||||
print(f"{name}: {changed} pixels differ")
|
||||
if changed:
|
||||
failed = True
|
||||
if diff_dir:
|
||||
faded = Image.blend(after, Image.new("RGB", after.size, "white"), 0.7)
|
||||
faded.paste(Image.new("RGB", after.size, "red"), mask=mask)
|
||||
faded.save(diff_dir / name)
|
||||
return 1 if failed else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,53 @@
|
||||
extends SceneTree
|
||||
## Takes a screenshot of the main window in every UiTour state, against a fake server.
|
||||
##
|
||||
## Usage:
|
||||
## godot --path . -s tools/take_screenshots.gd -- <output dir>
|
||||
##
|
||||
## Writes [code]<state>.png[/code] for each state and [code]layout.json[/code] with where
|
||||
## every node is in each state. With [code]--headless[/code] only the layout is written.
|
||||
## Compare two runs with [code]tools/compare_screenshots.py[/code].
|
||||
|
||||
const SIZE: Vector2i = Vector2i(1920, 1080)
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
_take.call_deferred()
|
||||
|
||||
|
||||
func _take() -> void:
|
||||
var args: PackedStringArray = OS.get_cmdline_user_args()
|
||||
var out_dir: String = args[0] if !args.is_empty() else "user://screenshots"
|
||||
DirAccess.make_dir_recursive_absolute(out_dir)
|
||||
var draws: bool = DisplayServer.get_name() != "headless"
|
||||
|
||||
# Loaded here, not named as a class: autoloads such as Playlist do not exist yet
|
||||
# when this script is compiled
|
||||
var ui_tour: GDScript = load("res://tests/fakes/ui_tour.gd")
|
||||
ui_tour.prepare_autoloads()
|
||||
var server: RefCounted = ui_tour.make_server().install()
|
||||
|
||||
# Drawn at the layout size, so the pictures do not depend on the screen size
|
||||
var viewport: SubViewport = SubViewport.new()
|
||||
viewport.size = SIZE
|
||||
viewport.gui_embed_subwindows = true
|
||||
viewport.render_target_update_mode = SubViewport.UPDATE_ALWAYS
|
||||
root.add_child(viewport)
|
||||
var main: Control = load("res://MainWindow.tscn").instantiate()
|
||||
viewport.add_child(main)
|
||||
|
||||
var layout: Dictionary = {}
|
||||
var save_state: Callable = func(state: String, popup: Node) -> void:
|
||||
layout[state] = ui_tour.dump_layout(main, main if popup == null else popup)
|
||||
if draws:
|
||||
await RenderingServer.frame_post_draw
|
||||
viewport.get_texture().get_image().save_png(out_dir.path_join(state + ".png"))
|
||||
print("Saved state ", state)
|
||||
await ui_tour.new(main).run(save_state)
|
||||
|
||||
var file: FileAccess = FileAccess.open(out_dir.path_join("layout.json"), FileAccess.WRITE)
|
||||
file.store_string(JSON.stringify(layout, " ", true))
|
||||
file.close()
|
||||
server.uninstall()
|
||||
main.queue_free()
|
||||
quit()
|
||||
@@ -0,0 +1 @@
|
||||
uid://0837em1gsrrx
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
class_name TopBar
|
||||
extends Control
|
||||
## The bar along the top of the main window, with the title and the buttons for things
|
||||
## that are not part of a quiz.
|
||||
|
||||
signal search_pressed
|
||||
signal sync_pressed
|
||||
signal statistics_pressed
|
||||
signal shortcuts_pressed
|
||||
signal about_pressed
|
||||
signal qr_pressed
|
||||
|
||||
@onready var sync_button: Button = $SyncButton
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
$SearchButton.pressed.connect(search_pressed.emit)
|
||||
sync_button.pressed.connect(sync_pressed.emit)
|
||||
$StatisticsButton.pressed.connect(statistics_pressed.emit)
|
||||
$ShortcutsButton.pressed.connect(shortcuts_pressed.emit)
|
||||
$AboutButton.pressed.connect(about_pressed.emit)
|
||||
$QRButton.pressed.connect(qr_pressed.emit)
|
||||
@@ -0,0 +1 @@
|
||||
uid://bb2ukabiec1vv
|
||||
Reference in new issue
Block a user