diff --git a/InsperationScrollContainer.gd b/InsperationScrollContainer.gd deleted file mode 100644 index aa83b8b..0000000 --- a/InsperationScrollContainer.gd +++ /dev/null @@ -1,38 +0,0 @@ -extends ScrollContainer - -# Scroll direction: 1 = down, -1 = up -var scroll_direction: int = 1 - -# Called when the node enters the scene tree for the first time. -func _ready() -> void: - scroll_direction = 1 - -# Called every frame. 'delta' is the elapsed time since the previous frame. -func _process(delta: float) -> void: - var speed_pixels_per_second: float = float(Settings.inspiration_list_speed) * 60.0 - var scroll_amount: float = speed_pixels_per_second * delta * float(scroll_direction) - - var scroll_bar = get_v_scroll_bar() - - # Don't scroll if list is empty or too short - if scroll_bar.max_value <= 0: - return - - var current_pos = scroll_bar.value - - if scroll_direction == 1: - # Scrolling down - if current_pos + scroll_bar.page < scroll_bar.max_value: - scroll_bar.value = min(current_pos + scroll_amount, scroll_bar.max_value - scroll_bar.page) - else: - # Reached bottom, reverse direction - scroll_direction = -1 - scroll_bar.value = scroll_bar.max_value - scroll_bar.page - else: - # Scrolling up - if current_pos > 0: - scroll_bar.value = max(current_pos + scroll_amount, 0) - else: - # Reached top, reverse direction - scroll_direction = 1 - scroll_bar.value = 0 diff --git a/InspirationList.tscn b/InspirationList.tscn new file mode 100644 index 0000000..4ec42ea --- /dev/null +++ b/InspirationList.tscn @@ -0,0 +1,16 @@ +[gd_scene format=3] + +[ext_resource type="Script" uid="uid://elgn7gkxgagq" path="res://inspiration_list.gd" id="2_gxtxm"] + +[node name="InspirationList" type="ScrollContainer" unique_id=498283761] +layout_mode = 3 +horizontal_scroll_mode = 0 +vertical_scroll_mode = 3 +offset_right = 641.0 +offset_bottom = 384.0 +script = ExtResource("2_gxtxm") + +[node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=1215162949] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 diff --git a/MusicListPanel.tscn b/MusicListPanel.tscn new file mode 100644 index 0000000..e5c89e6 --- /dev/null +++ b/MusicListPanel.tscn @@ -0,0 +1,23 @@ +[gd_scene format=3] + +[ext_resource type="Script" path="res://music_list_panel.gd" id="part_script"] +[ext_resource type="Script" uid="uid://gf1bk6xbe3j" path="res://MusicListScrollContainer.gd" id="7_dj026"] +[ext_resource type="Theme" uid="uid://rxexo3ur85as" path="res://LightGrayTheme.tres" id="7_wxbv6"] + +[node name="MusicListPanel" type="PanelContainer" unique_id=2011292375] +layout_mode = 3 +theme = ExtResource("7_wxbv6") +offset_right = 1088.0 +offset_bottom = 512.0 +script = ExtResource("part_script") + +[node name="ScrollContainer" type="ScrollContainer" parent="." unique_id=707750459] +layout_mode = 2 +follow_focus = true +script = ExtResource("7_dj026") + +[node name="MusicList" type="VBoxContainer" parent="ScrollContainer" unique_id=373398568] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_constants/separation = 10 diff --git a/PlayersPanel.tscn b/PlayersPanel.tscn new file mode 100644 index 0000000..db43c0b --- /dev/null +++ b/PlayersPanel.tscn @@ -0,0 +1,89 @@ +[gd_scene format=3] + +[ext_resource type="Script" path="res://players_panel.gd" id="part_script"] +[ext_resource type="Texture2D" uid="uid://bcfmpd7h512ef" path="res://icons/person_add_light.svg" id="5_31tjv"] +[ext_resource type="Script" uid="uid://cgakje8gb37tf" path="res://PlayerNameField.gd" id="7_qsdfy"] + +[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) + +[node name="PlayersPanel" type="PanelContainer" unique_id=1708375945] +layout_mode = 3 +offset_right = 641.0 +offset_bottom = 401.0 +script = ExtResource("part_script") + +[node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=270084211] +layout_mode = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer" unique_id=513047023] +custom_minimum_size = Vector2(2.08165e-12, 40) +layout_mode = 2 + +[node name="Label" type="Label" parent="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="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="VBoxContainer" unique_id=1371948739] +visible = false +custom_minimum_size = Vector2(2.08165e-12, 40) +layout_mode = 2 + +[node name="Panel" type="Panel" parent="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="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="VBoxContainer/AddPlayerContainer" unique_id=245001313] +custom_minimum_size = Vector2(2.08165e-12, 40) +layout_mode = 2 +text = "Add" + +[node name="Panel2" type="Panel" parent="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="VBoxContainer" unique_id=1000000001] +layout_mode = 2 +size_flags_vertical = 15 +horizontal_scroll_mode = 0 + +[node name="PlayersListVBoxContainer" type="VBoxContainer" parent="VBoxContainer/ScrollContainer" unique_id=1000000002] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 15 diff --git a/Quiz.tscn b/Quiz.tscn index 88bd1d8..4df21c6 100644 --- a/Quiz.tscn +++ b/Quiz.tscn @@ -1,16 +1,14 @@ [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"] +[ext_resource type="PackedScene" path="res://MusicListPanel.tscn" id="scene_MusicListPanel"] +[ext_resource type="PackedScene" path="res://PlayersPanel.tscn" id="scene_PlayersPanel"] +[ext_resource type="PackedScene" path="res://InspirationList.tscn" id="scene_InspirationList"] [sub_resource type="LabelSettings" id="LabelSettings_qspbu"] font_size = 50 @@ -18,21 +16,6 @@ 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 @@ -79,39 +62,19 @@ file_mode = 2 access = 2 show_hidden_files = true -[node name="MusicListPanel" type="PanelContainer" parent="." unique_id=2011292375] +[node name="MusicListPanel" parent="." unique_id=2011292375 instance=ExtResource("scene_MusicListPanel")] 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] +[node name="InspirationList" parent="." unique_id=498283761 instance=ExtResource("scene_InspirationList")] 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 @@ -148,76 +111,13 @@ offset_bottom = 1024.0 grow_horizontal = 1 grow_vertical = 1 -[node name="Players" type="PanelContainer" parent="." unique_id=1708375945] +[node name="PlayersPanel" parent="." unique_id=1708375945 instance=ExtResource("scene_PlayersPanel")] 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 diff --git a/inspiration_list.gd b/inspiration_list.gd new file mode 100644 index 0000000..3ad9a4a --- /dev/null +++ b/inspiration_list.gd @@ -0,0 +1,67 @@ +class_name InspirationList +extends ScrollContainer +## The list of every soundtrack, shown for inspiration. It scrolls slowly up and down +## by itself, at the speed set in the settings. + +## The soundtracks shown, in order. +var soundtracks: Array = [] + +# Scroll direction: 1 = down, -1 = up +var scroll_direction: int = 1 + +@onready +var _rows: VBoxContainer = $VBoxContainer + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + scroll_direction = 1 + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta: float) -> void: + var speed_pixels_per_second: float = float(Settings.inspiration_list_speed) * 60.0 + var scroll_amount: float = speed_pixels_per_second * delta * float(scroll_direction) + + var scroll_bar = get_v_scroll_bar() + + # Don't scroll if list is empty or too short + if scroll_bar.max_value <= 0: + return + + var current_pos = scroll_bar.value + + if scroll_direction == 1: + # Scrolling down + if current_pos + scroll_bar.page < scroll_bar.max_value: + scroll_bar.value = min(current_pos + scroll_amount, scroll_bar.max_value - scroll_bar.page) + else: + # Reached bottom, reverse direction + scroll_direction = -1 + scroll_bar.value = scroll_bar.max_value - scroll_bar.page + else: + # Scrolling up + if current_pos > 0: + scroll_bar.value = max(current_pos + scroll_amount, 0) + else: + # Reached top, reverse direction + scroll_direction = 1 + scroll_bar.value = 0 + + +## Fetches every soundtrack from the server and shows them. +func load_from_server() -> void: + print("load_from_server") + var populate_list: Callable = func populate_list(array) -> void: + if typeof(array) == TYPE_ARRAY: + show_soundtracks(array) + else: + print("Unexpected data") + MusicServerClient.fetch_all_soundtracks(populate_list) + + +## Shows [param list] instead of the soundtracks shown now. +func show_soundtracks(list: Array) -> void: + soundtracks = [] + soundtracks.append_array(list) + Settings.delete_children(_rows) + for i: int in soundtracks.size(): + _rows.add_child(ListRow.create(soundtracks[i], i, 20)) diff --git a/InsperationScrollContainer.gd.uid b/inspiration_list.gd.uid similarity index 100% rename from InsperationScrollContainer.gd.uid rename to inspiration_list.gd.uid diff --git a/music_list_panel.gd b/music_list_panel.gd new file mode 100644 index 0000000..fdb0687 --- /dev/null +++ b/music_list_panel.gd @@ -0,0 +1,26 @@ +class_name MusicListPanel +extends PanelContainer +## The list of songs in the playlist, one row per song. + +## Emitted when the row of song number [param song_no] is clicked. +signal song_clicked(song_no: int) + +var _song_list_object: PackedScene = preload("res://song_list_object.tscn") + +@onready +var _rows: VBoxContainer = $ScrollContainer/MusicList + + +## Shows the songs in the playlist again, so it shows their latest state. +func refresh() -> void: + Settings.delete_children(_rows) + 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(_on_row_input.bind(s.song_number)) + _rows.add_child(new_song_list_object) + + +func _on_row_input(event: InputEvent, song_no: int) -> void: + if event is InputEventMouseButton && event.pressed && event.button_index == MOUSE_BUTTON_LEFT: + song_clicked.emit(song_no) diff --git a/music_list_panel.gd.uid b/music_list_panel.gd.uid new file mode 100644 index 0000000..2b22754 --- /dev/null +++ b/music_list_panel.gd.uid @@ -0,0 +1 @@ +uid://b8e27uui01tt diff --git a/players_panel.gd b/players_panel.gd new file mode 100644 index 0000000..96a51e1 --- /dev/null +++ b/players_panel.gd @@ -0,0 +1,90 @@ +class_name PlayersPanel +extends PanelContainer +## The players and their points, with a row for adding new players. +## +## The players themselves live in [code]Settings.player_array[/code]; this panel shows +## them and adds and removes them there. + +## Emitted when a player has been added to [code]Settings.player_array[/code], before +## the list shows it. +signal player_added(player_object: PlayerObject) + +## Emitted when a player has been removed from [code]Settings.player_array[/code]. +signal player_removed(player_object: PlayerObject) + +## Emitted when the character button in [param row] is pressed. +signal change_character_requested(row: Player, player_name: String) + +var _player_row: PackedScene = preload("res://Player.tscn") + +@onready +var add_players_button: TextureButton = $VBoxContainer/HBoxContainer/AddPlayersButton + +@onready +var add_player_container: HBoxContainer = $VBoxContainer/AddPlayerContainer + +@onready +var add_player_button: Button = $VBoxContainer/AddPlayerContainer/AddPlayerButton + +@onready +var new_player_name_field: TextEdit = $VBoxContainer/AddPlayerContainer/PlayerNameField + +@onready +var players_list_container: VBoxContainer = $VBoxContainer/ScrollContainer/PlayersListVBoxContainer + + +func _ready() -> void: + add_players_button.pressed.connect(add_players) + add_player_button.pressed.connect(add_player) + new_player_name_field.connect("enter_key_pressed", add_player) + + +## Shows or hides the row for adding players, and the buttons for removing them. +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() + + +## Adds a player with the name in the name field, unless it is empty or there are +## already as many players as there can be. +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) + player_added.emit(new_player_object) + reload() + + +## Shows the players in [code]Settings.player_array[/code] again, e.g. after their +## points were reset. +func reload() -> 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_row.instantiate() + new_player.player_object = player_object + new_player.list_index = counter + + new_player.connect("change_character_clicked", _on_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_change_character_clicked(row: Player, player_name: String) -> void: + change_character_requested.emit(row, player_name) + + +func _on_player_removed(player_object: PlayerObject) -> void: + print("_on_player_removed ", player_object.player_name) + Settings.player_array.erase(player_object) + reload() + player_removed.emit(player_object) diff --git a/players_panel.gd.uid b/players_panel.gd.uid new file mode 100644 index 0000000..509afde --- /dev/null +++ b/players_panel.gd.uid @@ -0,0 +1 @@ +uid://ctayn0soyatfk diff --git a/quiz.gd b/quiz.gd index 071f4ce..f2c5b9d 100644 --- a/quiz.gd +++ b/quiz.gd @@ -18,10 +18,7 @@ var open_button: Button = $Open var fileDialog: FileDialog = $FileDialog @onready -var inspiration_scroll: ScrollContainer = $ScrollContainer - -@onready -var inspiration_list: VBoxContainer = $ScrollContainer/VBoxContainer +var inspiration_list: InspirationList = $InspirationList @onready var soundtrack_label: Label = $VBoxContainer/SoundtrackLabel @@ -30,16 +27,7 @@ var soundtrack_label: Label = $VBoxContainer/SoundtrackLabel 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 +var players_panel: PlayersPanel = $PlayersPanel @onready var reset_playlist_button: Button = $ResetPlaylistButton @@ -66,16 +54,7 @@ var show_answer_button: Button = $ShowAnswerButton 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 +var music_list_panel: MusicListPanel = $MusicListPanel @onready var new_character_select: Control = %NewCharacterSelect @@ -92,11 +71,7 @@ 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. @@ -110,354 +85,302 @@ 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) + 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) + new_character_select.connect("new_new_character_selected", _on_new_character_selected) + music_list_panel.song_clicked.connect(song_object_clicked) + players_panel.player_added.connect(_on_player_added) + players_panel.player_removed.connect(_on_player_removed) + players_panel.change_character_requested.connect(_on_player_change_character_clicked) + 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) + 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() + inspiration_list.load_from_server() + 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() + 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 + 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() + 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() + print("refresh_from_server") + inspiration_list.load_from_server() + reset_playlist() + new_character_select.load_characters() func _on_server_changed() -> void: - print("_on_server_changed") - refresh_from_server() - reset_points() - server_changed.emit() + print("_on_server_changed") + refresh_from_server() + reset_points() + server_changed.emit() func repeat_song() -> void: - Settings.auto_repeat_song = !Settings.auto_repeat_song + Settings.auto_repeat_song = !Settings.auto_repeat_song func show_answer_pressed() -> void: - print("show_answer_pressed") - Playlist.song_is_answered() - update_song_list() + 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)) + 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() + 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] + 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() + 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()) + 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))) + 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))) + 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))) + 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) + 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) + 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() + music_list_panel.refresh() -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 song_object_clicked(song_no: int) -> void: + 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() + 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) + 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 + print("reset_points") + for player_objects: PlayerObject in Settings.player_array: + player_objects.reset_points() + players_panel.reload() + # 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() + 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 +# Each new player's points reach the music player, the log and the winner window. +func _on_player_added(new_player_object: PlayerObject) -> void: + 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)) 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() + 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")) + 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() + 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 + 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() + 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) + 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) + print("_on_new_character_selected") + current_player.new_character_selected(character_texture) ###Local @@ -465,32 +388,30 @@ func _on_new_character_selected(character_texture: ImageTexture) -> void: 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() + fileDialog.popup() func _on_file_dialog_dir_selected(path: String) -> void: - print(path) - dir_contents(path) + 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)) + var dir: DirAccess = DirAccess.open(path) + if dir: + dir.list_dir_begin() + var file_name: String = dir.get_next() + songs.clear() + var soundtracks: Array = [] + 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() + inspiration_list.show_soundtracks(soundtracks) + else: + print("An error occurred when trying to access the path.") diff --git a/tests/fakes/main_window_test_suite.gd b/tests/fakes/main_window_test_suite.gd index 5a90a84..2c4b792 100644 --- a/tests/fakes/main_window_test_suite.gd +++ b/tests/fakes/main_window_test_suite.gd @@ -77,16 +77,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 = _quiz.get_node( - "Players/VBoxContainer/AddPlayerContainer/PlayerNameField") + "PlayersPanel/VBoxContainer/AddPlayerContainer/PlayerNameField") name_field.text = player_name - _quiz.add_player() + _quiz.players_panel.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 = _quiz.get_node( - "Players/VBoxContainer/ScrollContainer/PlayersListVBoxContainer") + "PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer") for row: Player in rows.get_children(): if row.player_object == player_object: return row diff --git a/tests/fakes/ui_tour.gd b/tests/fakes/ui_tour.gd index 03e1fb2..71e4b39 100644 --- a/tests/fakes/ui_tour.gd +++ b/tests/fakes/ui_tour.gd @@ -179,11 +179,14 @@ func _node(node_name: String) -> Node: return null -## Calls [param method] on the main window, or on its Quiz scene if that has it. +## Calls [param method] on the players panel, the quiz or the main window, whichever +## has it first. 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) + for path: String in ["Quiz/PlayersPanel", "Quiz"]: + var target: Node = _main.get_node_or_null(path) + if target != null && target.has_method(method): + return target.callv(method, args) + return _main.callv(method, args) func _server() -> FakeHttpTransport: @@ -196,13 +199,13 @@ func _settle() -> void: func _add_player(player_name: String) -> void: - (_node("Players").get_node("VBoxContainer/AddPlayerContainer/PlayerNameField") \ + (_node("PlayersPanel").get_node("VBoxContainer/AddPlayerContainer/PlayerNameField") \ as TextEdit).text = player_name _call("add_player") func _player_row(index: int) -> Player: - return _node("Players").get_node( + return _node("PlayersPanel").get_node( "VBoxContainer/ScrollContainer/PlayersListVBoxContainer").get_child(index) diff --git a/tests/integration/test_inspiration_list.gd b/tests/integration/test_inspiration_list.gd new file mode 100644 index 0000000..89f085d --- /dev/null +++ b/tests/integration/test_inspiration_list.gd @@ -0,0 +1,92 @@ +extends GdUnitTestSuite +## The inspiration list on its own: it shows every soundtrack from the server and +## scrolls by itself. + +const SOUNDTRACKS: Array[String] = ["Chrono Trigger", "Final Fantasy VI", "Secret of Mana"] + +var _snapshot: AutoloadSnapshot +var _server: FakeHttpTransport +var _runner: GdUnitSceneRunner +var _list: InspirationList + + +func before_test() -> void: + _snapshot = AutoloadSnapshot.take([Settings]) + Settings.inspiration_list_speed = 0 + _server = FakeHttpTransport.new().install() + _server.respond_json("/music/all", SOUNDTRACKS) + _runner = scene_runner("res://InspirationList.tscn") + _list = _runner.scene() + + +func after_test() -> void: + await await_idle_frame() + _server.uninstall() + _snapshot.restore() + + +func _rows() -> Array[Node]: + return _list.get_node("VBoxContainer").get_children() + + +func _texts() -> Array: + return _rows().map(func(row: ListRow) -> String: return row.text) + + +func test_load_from_server_shows_every_soundtrack_in_order() -> void: + _list.load_from_server() + + assert_array(_texts()).contains_exactly(SOUNDTRACKS) + assert_array(_list.soundtracks).contains_exactly(SOUNDTRACKS) + + +func test_rows_alternate_backgrounds() -> void: + _list.load_from_server() + + var rows: Array[Node] = _rows() + assert_that((rows[0] as ListRow).get_theme_stylebox("panel")) \ + .is_not_equal((rows[1] as ListRow).get_theme_stylebox("panel")) + assert_that((rows[0] as ListRow).get_theme_stylebox("panel")) \ + .is_equal((rows[2] as ListRow).get_theme_stylebox("panel")) + + +func test_loading_again_replaces_the_rows() -> void: + _list.load_from_server() + _server.respond_json("/music/all", ["Terranigma"]) + + _list.load_from_server() + + assert_array(_texts()).contains_exactly(["Terranigma"]) + + +func test_failed_load_keeps_the_rows() -> void: + _list.load_from_server() + _server.respond_bytes("/music/all", PackedByteArray(), 500) + + _list.load_from_server() + + assert_array(_texts()).contains_exactly(SOUNDTRACKS) + + +func _long_list() -> Array[String]: + var names: Array[String] = [] + for i: int in 40: + names.append("Soundtrack %d" % i) + return names + + +func test_scrolls_down_by_itself() -> void: + _list.show_soundtracks(_long_list()) + Settings.inspiration_list_speed = 5 + + await _runner.simulate_frames(30, 16) + + assert_int(_list.scroll_vertical).is_greater(0) + + +func test_does_not_scroll_at_speed_zero() -> void: + _list.show_soundtracks(_long_list()) + + await _runner.simulate_frames(30, 16) + + assert_int(_list.scroll_vertical).is_equal(0) diff --git a/tests/integration/test_inspiration_list.gd.uid b/tests/integration/test_inspiration_list.gd.uid new file mode 100644 index 0000000..cb05a55 --- /dev/null +++ b/tests/integration/test_inspiration_list.gd.uid @@ -0,0 +1 @@ +uid://2hfs3x1k46wu diff --git a/tests/integration/test_layout_unchanged.gd b/tests/integration/test_layout_unchanged.gd index d5df4ed..10aede3 100644 --- a/tests/integration/test_layout_unchanged.gd +++ b/tests/integration/test_layout_unchanged.gd @@ -12,12 +12,18 @@ 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", + "Open", "FileDialog", "MusicListPanel", "VBoxContainer", + "MusicPlayer", "ResetPlaylistButton", "ResetPointsButton", "SettingsButton", "SoundTestButton", "ShowAnswerButton", "NextButton", "RepeatSongCheckButton", "DebugLabel", "SettingsWindow", "NewCharacterSelect", "WinnerWindow", "Log", ] +## Nodes that moved into the Quiz scene and were renamed there: old name to new name. +const RENAMED_QUIZ_NODES: Dictionary = { + "Players": "PlayersPanel", + "ScrollContainer": "InspirationList", +} + ## 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", @@ -60,6 +66,8 @@ 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 RENAMED_QUIZ_NODES: + return "Quiz/" + RENAMED_QUIZ_NODES[first] + old_path.trim_prefix(first) if first in TOP_BAR_NODES: return "TopBar/" + old_path return old_path diff --git a/tests/integration/test_main_window.gd b/tests/integration/test_main_window.gd index 91322e2..5f9dc11 100644 --- a/tests/integration/test_main_window.gd +++ b/tests/integration/test_main_window.gd @@ -240,12 +240,3 @@ func test_log_adds_one_row_per_point_with_padded_time() -> void: var time_pattern: RegEx = RegEx.create_from_string("^\\d\\d:\\d\\d:\\d\\d Crono got a point$") assert_object(time_pattern.search((log_box.get_child(0) as Label).text)).is_not_null() assert_str((log_box.get_child(1) as Label).text).ends_with("Crono lost a point") - - -func test_inspiration_list_alternates_row_backgrounds() -> void: - 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") - assert_that((rows[0] as ListRow).get_theme_stylebox("panel")) \ - .is_not_equal((rows[1] as ListRow).get_theme_stylebox("panel")) diff --git a/tests/integration/test_music_list_panel.gd b/tests/integration/test_music_list_panel.gd new file mode 100644 index 0000000..f6fa444 --- /dev/null +++ b/tests/integration/test_music_list_panel.gd @@ -0,0 +1,79 @@ +extends GdUnitTestSuite +## The music list on its own: one row per song in the playlist, and clicking a row +## tells which song was clicked. + +var _snapshot: AutoloadSnapshot +var _runner: GdUnitSceneRunner +var _panel: MusicListPanel + + +func before_test() -> void: + _snapshot = AutoloadSnapshot.take([Settings, Playlist]) + Playlist.reset_playlist() + for i: int in 3: + var song: SongObject = SongObject.new() + song.song_number = i + song.soundtrack_title = "Soundtrack %d" % i + song.song_title = "Song %d" % i + song.has_played = true + Playlist.add_to_playlist(song) + _runner = scene_runner("res://MusicListPanel.tscn") + _panel = _runner.scene() + + +func after_test() -> void: + await await_idle_frame() + _snapshot.restore() + + +func _rows() -> Array[Node]: + return _panel.get_node("ScrollContainer/MusicList").get_children() + + +func test_starts_empty() -> void: + assert_array(_rows()).is_empty() + + +func test_refresh_shows_one_row_per_song_in_order() -> void: + _panel.refresh() + + assert_array(_rows().map(func(row: SongListObject) -> int: return row.song_object.song_number)) \ + .contains_exactly([0, 1, 2]) + + +func test_refresh_again_replaces_the_rows() -> void: + _panel.refresh() + Playlist.song_object_array.pop_back() + + _panel.refresh() + + assert_int(_rows().size()).is_equal(2) + + +func test_clicking_a_row_tells_which_song() -> void: + _panel.refresh() + await _runner.simulate_frames(2) + var signals: GdUnitSignalAssert = assert_signal(monitor_signals(_panel)) + var row: Control = _rows()[1] + # The left end of the row is the song text, not one of the player icons + var target: Vector2 = row.get_global_rect().position + Vector2(20, row.size.y / 2) + + _runner.set_mouse_position(_panel.get_viewport().get_final_transform() * target) + await _runner.simulate_mouse_button_pressed(MOUSE_BUTTON_LEFT) + await _runner.await_input_processed() + + await signals.is_emitted("song_clicked", [1]) + + +func test_right_click_does_nothing() -> void: + _panel.refresh() + await _runner.simulate_frames(2) + var signals: GdUnitSignalAssert = assert_signal(monitor_signals(_panel)) + var row: Control = _rows()[0] + + _runner.set_mouse_position(_panel.get_viewport().get_final_transform() + * (row.get_global_rect().position + Vector2(20, row.size.y / 2))) + await _runner.simulate_mouse_button_pressed(MOUSE_BUTTON_RIGHT) + await _runner.await_input_processed() + + await signals.wait_until(200).is_not_emitted("song_clicked") diff --git a/tests/integration/test_music_list_panel.gd.uid b/tests/integration/test_music_list_panel.gd.uid new file mode 100644 index 0000000..962ce7a --- /dev/null +++ b/tests/integration/test_music_list_panel.gd.uid @@ -0,0 +1 @@ +uid://c1dj75bnbdjle diff --git a/tests/integration/test_player_keybinds.gd b/tests/integration/test_player_keybinds.gd index 3c175f2..8dd3778 100644 --- a/tests/integration/test_player_keybinds.gd +++ b/tests/integration/test_player_keybinds.gd @@ -144,7 +144,7 @@ func test_player_list_shows_rows_in_player_order() -> void: var robo: PlayerObject = _add_player("Robo") var rows: Array[Node] = _quiz.get_node( - "Players/VBoxContainer/ScrollContainer/PlayersListVBoxContainer").get_children() + "PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer").get_children() assert_array(rows.map(func(row: Player) -> PlayerObject: return row.player_object)) \ .contains_exactly([_marle, _lucca, robo]) diff --git a/tests/integration/test_players_panel.gd b/tests/integration/test_players_panel.gd new file mode 100644 index 0000000..07642a8 --- /dev/null +++ b/tests/integration/test_players_panel.gd @@ -0,0 +1,146 @@ +extends GdUnitTestSuite +## The player list on its own: adding players through the name field, removing them +## and asking for a new character. + +var _snapshot: AutoloadSnapshot +var _runner: GdUnitSceneRunner +var _panel: PlayersPanel + + +func before_test() -> void: + _snapshot = AutoloadSnapshot.take([Settings]) + Settings.player_array.clear() + Settings.edit_players = false + _runner = scene_runner("res://PlayersPanel.tscn") + _panel = _runner.scene() + + +func after_test() -> void: + await await_idle_frame() + _snapshot.restore() + + +func _rows() -> Array[Node]: + return _panel.players_list_container.get_children() + + +func _add(player_name: String) -> void: + _panel.new_player_name_field.text = player_name + _panel.add_player() + + +func _click(control: Control) -> void: + _runner.set_mouse_position(_panel.get_viewport().get_final_transform() + * control.get_global_rect().get_center()) + await _runner.simulate_mouse_button_pressed(MOUSE_BUTTON_LEFT) + await _runner.await_input_processed() + + +func test_add_button_shows_and_hides_the_add_row() -> void: + assert_bool(_panel.add_player_container.visible).is_false() + + await _click(_panel.add_players_button) + assert_bool(_panel.add_player_container.visible).is_true() + assert_bool(Settings.edit_players).is_true() + + await _click(_panel.add_players_button) + assert_bool(_panel.add_player_container.visible).is_false() + assert_bool(Settings.edit_players).is_false() + + +func test_adding_a_name_adds_a_player_and_a_row() -> void: + var signals: GdUnitSignalAssert = assert_signal(monitor_signals(_panel)) + + _add("Crono") + + assert_int(Settings.player_array.size()).is_equal(1) + var crono: PlayerObject = Settings.player_array[0] + assert_str(crono.player_name).is_equal("Crono") + await signals.is_emitted("player_added", [crono]) + assert_str(_panel.new_player_name_field.text).is_empty() + assert_object((_rows()[0] as Player).player_object).is_same(crono) + + +func test_add_button_in_the_row_adds_the_player() -> void: + await _click(_panel.add_players_button) + _panel.new_player_name_field.text = "Marle" + + await _click(_panel.add_player_button) + + assert_int(Settings.player_array.size()).is_equal(1) + + +func test_enter_in_the_name_field_adds_the_player() -> void: + await _click(_panel.add_players_button) + _panel.new_player_name_field.text = "Lucca" + _panel.new_player_name_field.grab_focus() + + _runner.simulate_key_pressed(KEY_ENTER) + await _runner.await_input_processed() + + assert_array(Settings.player_array.map( + func(p: PlayerObject) -> String: return p.player_name)).contains_exactly(["Lucca"]) + + +func test_empty_name_adds_nobody() -> void: + var signals: GdUnitSignalAssert = assert_signal(monitor_signals(_panel)) + + _add("") + + assert_array(Settings.player_array).is_empty() + assert_array(_rows()).is_empty() + await signals.wait_until(200).is_not_emitted("player_added") + + +func test_cannot_add_more_than_max_players() -> void: + for i: int in Settings.MAX_NUMBER_OF_PLAYERS + 1: + _add("Player %d" % i) + + assert_int(Settings.player_array.size()).is_equal(Settings.MAX_NUMBER_OF_PLAYERS) + assert_int(_rows().size()).is_equal(Settings.MAX_NUMBER_OF_PLAYERS) + + +func test_rows_follow_player_order_and_numbering() -> void: + _add("Crono") + _add("Marle") + _add("Lucca") + + assert_array(_rows().map(func(row: Player) -> String: return row.player_object.player_name)) \ + .contains_exactly(["Crono", "Marle", "Lucca"]) + assert_array(_rows().map(func(row: Player) -> int: return row.list_index)) \ + .contains_exactly([0, 1, 2]) + + +func test_remove_button_removes_the_player_and_row() -> void: + _add("Crono") + _add("Marle") + var crono: PlayerObject = Settings.player_array[0] + var signals: GdUnitSignalAssert = assert_signal(monitor_signals(_panel)) + + (_rows()[0].get_node("HBoxContainer/RemovePlayer") as BaseButton).pressed.emit() + + await signals.is_emitted("player_removed", [crono]) + assert_array(Settings.player_array.map( + func(p: PlayerObject) -> String: return p.player_name)).contains_exactly(["Marle"]) + assert_int(_rows().size()).is_equal(1) + assert_int((_rows()[0] as Player).list_index).is_equal(0) + + +func test_character_button_asks_for_a_new_character() -> void: + _add("Crono") + var row: Player = _rows()[0] + var signals: GdUnitSignalAssert = assert_signal(monitor_signals(_panel)) + + (row.get_node("HBoxContainer/Character") as BaseButton).pressed.emit() + + await signals.is_emitted("change_character_requested", [row, "Crono"]) + + +func test_reload_shows_new_scores() -> void: + _add("Crono") + Settings.player_array[0].player_score = 3 + + _panel.reload() + + assert_str(((_rows()[0] as Player).get_node("HBoxContainer/Name") as Label).text) \ + .ends_with("Crono: 3") diff --git a/tests/integration/test_players_panel.gd.uid b/tests/integration/test_players_panel.gd.uid new file mode 100644 index 0000000..0e32cbb --- /dev/null +++ b/tests/integration/test_players_panel.gd.uid @@ -0,0 +1 @@ +uid://chtn6hbf4vqb6 diff --git a/tests/integration/test_scene_split.gd b/tests/integration/test_scene_split.gd index bc5fbdf..4291818 100644 --- a/tests/integration/test_scene_split.gd +++ b/tests/integration/test_scene_split.gd @@ -50,6 +50,12 @@ func test_top_bar_is_drawn_below_the_quiz() -> void: .is_less(_window.get_node("Quiz").get_index()) +func test_quiz_parts_are_their_own_scenes() -> void: + for node_name: String in ["MusicListPanel", "PlayersPanel", "InspirationList"]: + assert_str(_quiz.get_node(node_name).scene_file_path) \ + .is_equal("res://%s.tscn" % node_name) + + 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) @@ -89,6 +95,48 @@ func test_clicking_quiz_buttons_plays_and_shows_answer() -> void: .is_equal("Chrono Trigger") +func test_clicking_a_song_in_the_music_list_plays_it() -> void: + _next_song_is(SONG_INFO[1]) + await _play_first_song() + _press("NextButton") + # Changing song waits a moment for the old one to stop + await get_tree().create_timer(0.7).timeout + assert_int(Playlist.currently_playing_song).is_equal(1) + var rows: Array[Node] = _quiz.get_node("MusicListPanel/ScrollContainer/MusicList") \ + .get_children() + var first_row: Control = rows.filter( + func(row: SongListObject) -> bool: return row.song_object.song_number == 0)[0] + + await _click_at(first_row.get_global_rect().position + Vector2(20, first_row.size.y / 2)) + await get_tree().create_timer(0.7).timeout + + assert_int(Playlist.currently_playing_song).is_equal(0) + + +func test_points_from_the_player_list_reach_the_song_and_log() -> void: + await _play_first_song() + var crono: PlayerObject = _add_player("Crono") + + (_player_row(crono).get_node("HBoxContainer/AddPoint") as BaseButton).pressed.emit() + + assert_array(Playlist.song_object_array[0].players_given_point).contains_exactly([crono.id]) + assert_int((_quiz.get_node("%Log").get_node("%LogVBoxContainer") as Node).get_child_count()) \ + .is_equal(1) + + +func test_character_button_in_the_player_list_picks_a_new_character() -> void: + var crono: PlayerObject = _add_player("Crono") + var character_select: PopupWindow = _quiz.get_node("NewCharacterSelect") + + (_player_row(crono).get_node("HBoxContainer/Character") as BaseButton).pressed.emit() + + assert_bool(character_select.visible).is_true() + var texture: ImageTexture = ImageTexture.create_from_image( + Image.create(4, 4, false, Image.FORMAT_RGBA8)) + character_select.new_new_character_selected.emit(texture) + assert_object(crono.character).is_same(texture) + + func test_quiz_popup_backdrop_covers_the_top_bar() -> void: var settings: PopupWindow = _quiz.get_node("SettingsWindow") settings.open()