10 Commits
Author SHA1 Message Date
Sansan 74fc28614f New app icon, packaged with a menu entry for Linux
Build / test (pull_request) Successful in 23s
Build / build (pull_request) Skipped
The icon is a plate wearing headphones with a red Nordic house cookie
on it. It replaces the default Godot icon, so the game window, Windows
and macOS builds use it.

Linux programs can't carry their own icon, so the build now also makes
MusicPlayer_<version>_linux.tar.gz with the game, the icon as a PNG, a
.desktop menu entry and install.sh, which installs them for the current
user. The bare Linux binary is still published.
2026-10-05 22:48:25 +02:00
Sansan 9a5806c56f Show where each item starts in the inspiration and sync lists
Items in the inspiration list and the sync result columns now each have
their own background box, alternating between two shades, with a small
gap between them. When a long name wraps over several lines it is
still clear which lines belong together. The sync columns keep their
alternating white and orange text.
2026-10-05 22:33:58 +02:00
Sansan 196a7abc35 Make the sync result look right with any number of columns
- Visible columns share the width equally, whether one or five are shown
- Long soundtrack names and errors wrap inside their column instead of
  widening the window, which pushed the title and Restart button out of
  view and cut off text
- Separators only go between columns; the extra one before the first
  column is gone
- Wider, centred panel with inner margins, and spacing above the
  Restart button
- The Re-added header no longer reads ReAdded
2026-10-05 22:26:58 +02:00
Sansan 212eda96e2 Tidy up popup layouts
- Settings: labels in one column and switches and number buttons lined
  up in another, with margins inside the panel
- Log: title and margins; text no longer touches the panel edge
- Sync result: error list centred under its header, which now reads
  Errors instead of CatchedErrors
- About: text wraps inside the panel instead of running under the
  scrollbar
- Character select: wider panel, so the right column of characters is
  no longer right next to the scrollbar
2026-10-05 21:42:53 +02:00
Sansan 65b73e3570 Rework popups and fix blurry text and player list layout
Popups:
- All nine popups now extend a shared PopupWindow class with open(),
  close() and toggle(), instead of each copying its own close-on-click
  code. Popups that need cleanup override close().
- A click outside a popup closed it and also pressed whatever was
  underneath, e.g. Reset playlist. The closing click is now consumed.
- Popups used Godot's see-through default panel, so the song list and
  titles showed through. They now use an opaque popup theme, and a
  backdrop dims the window behind them.
- The main window root now fills the screen, so the backdrop covers it.

Text and images:
- Many labels and buttons forced nearest filtering, which made text and
  icons jagged when the window is not 1920x1080. They now use the
  default smooth filtering. Only the pixel-art "no character" picture
  keeps nearest filtering when the winner window enlarges it.

Player list:
- A long player name pushed that row's character and buttons to the
  right. Names now take the free space and shorten with an ellipsis, so
  all rows line up and use the full width of the panel.
2026-10-05 21:40:40 +02:00
Sansan 7beaa7ee5c Use the Compatibility renderer
The game is a 2D interface without 3D, shaders or lights, so it does
not need Forward+. With Compatibility it uses about 25% less CPU and
30 MB less memory while running, with no visible difference.
2026-10-05 21:40:31 +02:00
Sansan f8bc4c4b71 Remove unused code
- The old character select scene, which was never shown but loaded
  every character image at startup
- Unused SyncResponse and ProgressResponse classes
- Playlist.set_last_played_answered and select/local-loading code in
  the new character select, none of which were called
- Settings.delete_player_children, identical to delete_children
- An unused variable holding a local file path
2026-10-05 21:13:30 +02:00
Sansan 59fcb3cbe3 Add one log row at a time and pad log times
The log rebuilt every row each time a point was given or taken. It now
adds just the new row, and shows times as 09:05:03 instead of 9:5:3.
2026-10-05 21:12:38 +02:00
Sansan 056ead47be Turn statistics back on for a new game and fix low winning scores
Winning turned statistics off and nothing turned them back on, while
the Settings checkbox still showed them as on. Resetting points now
turns them back on if the win turned them off, and the checkbox shows
the current state when Settings is opened.

A winning score of 1 could never be won and with 2 the first point was
not announced as match point, because the first-point check ran first.
The winning score also could not be kept from going below 1.
2026-10-05 21:12:17 +02:00
Sansan d856e03c32 Preload songs only after the song list has loaded
At startup and after resetting the playlist, loading the song list and
preloading the first song started at the same time. When the list
answered last, it cleared the playlist and dropped the preloaded song.
Preloading now starts once the list has loaded, and loading the list is
retried if it fails.
2026-10-05 21:10:42 +02:00
62 changed files with 992 additions and 403 deletions

No files matched your search

+5
View File
@@ -83,6 +83,11 @@ jobs:
godot --headless --path . --import
godot --headless --path . --export-release "Windows Desktop" "build/${PROJECT_NAME}_${GAME_VERSION}.exe"
godot --headless --path . --export-release "Linux" "build/${PROJECT_NAME}_${GAME_VERSION}.x86_64"
# Linux binaries can't hold an icon; the package adds it with a menu entry and installer
- name: Package Linux
run: |
linux/package.sh "build/${PROJECT_NAME}_${GAME_VERSION}.x86_64" "$GAME_VERSION" build
ls -la build
- name: Upload Artifacts
+3 -15
View File
@@ -1,4 +1,4 @@
extends Control
extends PopupWindow
@onready
var about_panel: Panel = %AboutPanel
@@ -51,16 +51,8 @@ const WHATS_NEW: Dictionary = {
}
func _input(event: InputEvent) -> void:
if self.visible == true:
if event is InputEventMouseButton && event.is_pressed():
var evLocal: InputEvent = make_input_local(event)
if !Rect2(Vector2(0, 0), about_panel.size).has_point(evLocal.position):
self.visible = false
func show_window() -> void:
visible = true
func open() -> void:
super()
version_label.text = Settings.version
# Build changelog text with version headers
@@ -70,7 +62,3 @@ func show_window() -> void:
changelog_text += WHATS_NEW[version] + "\n"
whats_new_label.text = changelog_text
func hide_window() -> void:
visible = false
+7 -9
View File
@@ -1,8 +1,10 @@
[gd_scene format=3 uid="uid://cp54idaudb8dp"]
[ext_resource type="Script" uid="uid://b7bh0of7olbcb" path="res://AboutWindow.gd" id="1_about_script"]
[ext_resource type="Theme" path="res://popup_theme.tres" id="popup_theme"]
[node name="AboutControl" type="Control" unique_id=1626010355]
[node name="AboutControl" type="Control" unique_id=1626010355 node_paths=PackedStringArray("panel")]
theme = ExtResource("popup_theme")
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
@@ -10,6 +12,7 @@ anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_about_script")
panel = NodePath("AboutPanel")
[node name="AboutPanel" type="Panel" parent="." unique_id=1907299275]
unique_name_in_owner = true
@@ -18,7 +21,6 @@ offset_right = 600.0
offset_bottom = 640.0
[node name="TitleLabel" type="Label" parent="AboutPanel" unique_id=200295721]
texture_filter = 1
layout_mode = 0
offset_right = 600.0
offset_bottom = 50.0
@@ -28,10 +30,11 @@ vertical_alignment = 1
[node name="ScrollContainer" type="ScrollContainer" parent="AboutPanel" unique_id=417677913]
layout_mode = 0
offset_left = 10.0
offset_left = 20.0
offset_top = 50.0
offset_right = 590.0
offset_right = 580.0
offset_bottom = 630.0
horizontal_scroll_mode = 0
[node name="ContentVBox" type="VBoxContainer" parent="AboutPanel/ScrollContainer" unique_id=1746337912]
layout_mode = 2
@@ -40,14 +43,11 @@ size_flags_vertical = 3
[node name="VersionLabel" type="Label" parent="AboutPanel/ScrollContainer/ContentVBox" unique_id=352884126]
unique_name_in_owner = true
texture_filter = 1
layout_mode = 2
size_flags_horizontal = 3
horizontal_alignment = 1
[node name="DescriptionLabel" type="Label" parent="AboutPanel/ScrollContainer/ContentVBox" unique_id=543094263]
texture_filter = 1
custom_minimum_size = Vector2(560, 0)
layout_mode = 2
size_flags_horizontal = 3
text = "Try your video game music knowledge with this VGM randomizer, invite your friends and see who is the best."
@@ -56,8 +56,6 @@ autowrap_mode = 2
[node name="WhatsNewLabel" type="Label" parent="AboutPanel/ScrollContainer/ContentVBox" unique_id=1363040291]
unique_name_in_owner = true
texture_filter = 1
custom_minimum_size = Vector2(560, 0)
layout_mode = 2
size_flags_horizontal = 3
autowrap_mode = 2
-40
View File
@@ -1,40 +0,0 @@
extends Control
@onready
var character_grid_container := $ScrollContainer2
@onready
var character_grid := $ScrollContainer2/GridContainer
signal character_selected(file_name)
# Called when the node enters the scene tree for the first time.
func _ready():
var characters := DirAccess.open("res://characters/")
if characters:
characters.list_dir_begin()
var file_name = characters.get_next()
while file_name != "":
file_name = file_name.replace('.import', '') # <--- remove the .import
if file_name.ends_with(".png"):
var texture = load("res://characters/" + file_name)
var texture_btn := TextureButton.new()
character_grid.add_child(texture_btn)
texture_btn.custom_minimum_size = Vector2(80, 40)
texture_btn.ignore_texture_size = true
texture_btn.stretch_mode = TextureButton.STRETCH_KEEP_ASPECT
texture_btn.texture_normal = texture
texture_btn.pressed.connect(select_character.bind(file_name))
file_name = characters.get_next()
func show_grid():
character_grid_container.visible = true
func select_character(file_name: String):
print("select_character")
character_selected.emit(file_name)
-1
View File
@@ -1 +0,0 @@
uid://cmxl2w45vvmx6
-25
View File
@@ -1,25 +0,0 @@
[gd_scene load_steps=2 format=3 uid="uid://b16on0oscg1bv"]
[ext_resource type="Script" uid="uid://cmxl2w45vvmx6" path="res://CharacterSelect.gd" id="1_4bba6"]
[node name="CharacterSelect" type="Control"]
layout_mode = 3
anchors_preset = 0
offset_right = 40.0
offset_bottom = 40.0
script = ExtResource("1_4bba6")
[node name="ScrollContainer2" type="ScrollContainer" parent="."]
clip_contents = false
custom_minimum_size = Vector2(100, 100)
layout_mode = 0
offset_left = 194.0
offset_top = 152.0
offset_right = 670.0
offset_bottom = 375.0
[node name="GridContainer" type="GridContainer" parent="ScrollContainer2"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
columns = 3
+21 -7
View File
@@ -1,8 +1,10 @@
[gd_scene load_steps=2 format=3 uid="uid://bijh5h5yrivm3"]
[ext_resource type="Script" uid="uid://cw41y87l64qo7" path="res://log.gd" id="1_q72ua"]
[ext_resource type="Theme" path="res://popup_theme.tres" id="popup_theme"]
[node name="Control" type="Control"]
[node name="Control" type="Control" node_paths=PackedStringArray("panel")]
theme = ExtResource("popup_theme")
custom_minimum_size = Vector2(700, 700)
layout_mode = 3
anchors_preset = 15
@@ -11,23 +13,35 @@ anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_q72ua")
panel = NodePath("LogPanel")
[node name="LogPanel" type="Panel" parent="."]
unique_name_in_owner = true
visibility_layer = 513
custom_minimum_size = Vector2(700, 700)
layout_mode = 0
offset_right = 40.0
offset_bottom = 40.0
offset_right = 700.0
offset_bottom = 700.0
[node name="TitleLabel" type="Label" parent="LogPanel"]
layout_mode = 0
offset_top = 8.0
offset_right = 700.0
offset_bottom = 48.0
theme_override_font_sizes/font_size = 25
text = "Log"
horizontal_alignment = 1
[node name="LogScrollContainer" type="ScrollContainer" parent="LogPanel"]
custom_minimum_size = Vector2(700, 700)
layout_mode = 0
offset_right = 40.0
offset_bottom = 40.0
offset_left = 20.0
offset_top = 56.0
offset_right = 680.0
offset_bottom = 684.0
horizontal_scroll_mode = 0
[node name="LogVBoxContainer" type="VBoxContainer" parent="LogPanel/LogScrollContainer"]
unique_name_in_owner = true
custom_minimum_size = Vector2(700, 700)
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 1
+55 -39
View File
@@ -109,9 +109,6 @@ var player_container: VBoxContainer = $Players/VBoxContainer
@onready
var players_list_container: VBoxContainer = $Players/VBoxContainer/ScrollContainer/PlayersListVBoxContainer
@onready
var character_select: Control = $CharacterSelect
@onready
var new_character_select: Control = %NewCharacterSelect
@@ -140,11 +137,18 @@ 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
## 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:
@@ -168,7 +172,6 @@ func _ready() -> void:
reset_playlist_button.pressed.connect(reset_playlist)
reset_points_button.pressed.connect(reset_points)
character_select.connect("character_selected", _on_character_selected)
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)
@@ -186,7 +189,6 @@ func _ready() -> void:
get_suggestion_list()
fetch_song_list_at_start()
fetch_song()
search_view.get_list_of_soundtracks()
new_character_select.load_characters()
@@ -216,9 +218,9 @@ func _input(event: InputEvent) -> void:
if Settings.fullscreen == false:
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
if event.alt_pressed && event.keycode == KEY_K:
shortcuts_window.visible = !shortcuts_window.visible
shortcuts_window.toggle()
if event.alt_pressed && event.keycode == KEY_L:
log_window.visible = !log_window.visible
log_window.toggle()
func server_updated() -> void:
print("server_updated")
@@ -240,14 +242,12 @@ func show_answer_pressed() -> void:
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 data == null:
Playlist.update_last_played_song()
update_song_list()
return
if typeof(data) == TYPE_ARRAY:
for d: Dictionary in data:
var new_song_object: SongObject = SongObject.new()
@@ -257,11 +257,14 @@ func fetch_song_list_at_start() -> void:
new_song_object.is_answered = true
new_song_object.has_played = true
Playlist.add_to_playlist(new_song_object)
else:
elif data != null:
print("Unexpected data")
Playlist.update_last_played_song()
update_song_list()
MusicServerClient.fetch_song_list(fetch_song_list_at_start_done)
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)
@@ -278,6 +281,28 @@ func play_next_song() -> void:
#"/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]
# 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:
@@ -360,7 +385,6 @@ func reset_playlist() -> void:
var playlist_reset_done: Callable = func playlist_reset_done() -> void:
Playlist.reset_playlist()
fetch_song_list_at_start()
fetch_song()
MusicServerClient.reset_playlist(playlist_reset_done)
@@ -369,22 +393,26 @@ func reset_points() -> void:
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.show_window()
about_window.open()
func show_shortcuts() -> void:
print("show_shortcuts")
shortcuts_window.visible = true
shortcuts_window.open()
func show_settings() -> void:
print("show_settings")
settings_window.visible = true
settings_window.open()
func show_qr() -> void:
print("show_qr")
qr_window.show_window()
qr_window.open()
func get_statistics() -> void:
print("get_statistics")
@@ -418,13 +446,8 @@ func get_suggestion_list() -> void:
soundtracks = []
Settings.delete_children(inspiration_list)
soundtracks.append_array(array)
for soundtrack: String in soundtracks:
var inspiration_label: Label = Label.new()
inspiration_label.set_texture_filter(TextureFilter.TEXTURE_FILTER_NEAREST)
inspiration_label.add_theme_font_size_override("font_size", 20)
inspiration_label.text = soundtrack
inspiration_label.autowrap_mode = TextServer.AUTOWRAP_WORD
inspiration_list.add_child(inspiration_label)
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)
@@ -451,7 +474,7 @@ func add_player() -> void:
func load_players() -> void:
print("load_players")
Settings.delete_player_children(players_list_container)
Settings.delete_children(players_list_container)
var counter: int = 0
for player_object: PlayerObject in Settings.player_array:
print(player_object.player_name)
@@ -488,7 +511,10 @@ 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)
@@ -503,21 +529,13 @@ func _on_player_change_character_clicked(new_player: Player, player_name: String
print("Change character for: " + player_name)
new_character_select.show_grid(player_name)
func _on_character_selected(file_name: String) -> void:
print("_on_character_selected")
character_select.visible = false
current_player.character_selected(file_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")
if search_view.visible == false:
search_view.visible = true
else:
search_view.visible = false
search_view.toggle()
###Local
@@ -550,7 +568,5 @@ func dir_contents(path: String) -> void:
else:
print("An error occurred when trying to access the path.")
for soundtrack: String in soundtracks:
var local_inspiration_label: Label = Label.new()
local_inspiration_label.text = soundtrack
inspiration_list.add_child(local_inspiration_label)
for i: int in soundtracks.size():
inspiration_list.add_child(ListRow.create(soundtracks[i], i, 20))
+13 -38
View File
@@ -1,7 +1,6 @@
[gd_scene format=3 uid="uid://xwq863o6uvsu"]
[ext_resource type="Script" uid="uid://b8f4g15cas2j2" path="res://MainWindow.gd" id="1_eu0t5"]
[ext_resource type="PackedScene" uid="uid://b16on0oscg1bv" path="res://CharacterSelect.tscn" id="2_76kf4"]
[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"]
@@ -17,6 +16,7 @@
[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
@@ -70,7 +70,11 @@ events = [SubResource("InputEventKey_ujjlu")]
[node name="Control" type="Control" unique_id=407149696]
layout_mode = 3
anchors_preset = 0
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_eu0t5")
[node name="TopPanelContainer" type="PanelContainer" parent="." unique_id=663404711]
@@ -79,7 +83,6 @@ offset_right = 1920.0
offset_bottom = 56.0
[node name="Title" type="Label" parent="." unique_id=1187907861]
texture_filter = 1
layout_mode = 0
offset_left = 64.0
offset_top = 12.0
@@ -121,14 +124,12 @@ follow_focus = true
script = ExtResource("7_dj026")
[node name="MusicList" type="VBoxContainer" parent="MusicListPanel/ScrollContainer" unique_id=373398568]
texture_filter = 1
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]
texture_filter = 1
layout_mode = 0
offset_left = 1216.0
offset_top = 128.0
@@ -139,7 +140,6 @@ vertical_scroll_mode = 3
script = ExtResource("2_gxtxm")
[node name="VBoxContainer" type="VBoxContainer" parent="ScrollContainer" unique_id=1215162949]
texture_filter = 1
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
@@ -152,7 +152,6 @@ offset_right = 1152.0
offset_bottom = 384.0
[node name="SoundtrackLabel" type="Label" parent="VBoxContainer" unique_id=173147793]
texture_filter = 1
custom_minimum_size = Vector2(1, 1)
layout_mode = 2
label_settings = SubResource("LabelSettings_qspbu")
@@ -160,22 +159,12 @@ horizontal_alignment = 1
autowrap_mode = 2
[node name="SongLabel" type="Label" parent="VBoxContainer" unique_id=1687490027]
texture_filter = 1
custom_minimum_size = Vector2(1, 1)
layout_mode = 2
label_settings = SubResource("LabelSettings_3m52w")
horizontal_alignment = 1
autowrap_mode = 2
[node name="CharacterSelect" parent="." unique_id=2079506802 instance=ExtResource("2_76kf4")]
visible = false
top_level = true
layout_mode = 0
offset_left = 616.0
offset_top = 264.0
offset_right = 776.0
offset_bottom = 424.0
[node name="MusicPlayer" parent="." unique_id=1990459832 instance=ExtResource("5_emn36")]
layout_mode = 0
anchors_preset = 0
@@ -205,7 +194,6 @@ custom_minimum_size = Vector2(2.08165e-12, 40)
layout_mode = 2
[node name="Label" type="Label" parent="Players/VBoxContainer/HBoxContainer" unique_id=1331357386]
texture_filter = 1
custom_minimum_size = Vector2(2.08165e-12, 40)
layout_mode = 2
size_flags_horizontal = 3
@@ -215,7 +203,6 @@ horizontal_alignment = 1
vertical_alignment = 1
[node name="AddPlayersButton" type="TextureButton" parent="Players/VBoxContainer/HBoxContainer" unique_id=634410148]
texture_filter = 1
custom_minimum_size = Vector2(2.08165e-12, 40)
layout_mode = 2
size_flags_horizontal = 8
@@ -242,7 +229,6 @@ placeholder_text = "Player name"
script = ExtResource("7_qsdfy")
[node name="AddPlayerButton" type="Button" parent="Players/VBoxContainer/AddPlayerContainer" unique_id=245001313]
texture_filter = 1
custom_minimum_size = Vector2(2.08165e-12, 40)
layout_mode = 2
text = "Add"
@@ -256,13 +242,14 @@ 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]
texture_filter = 1
layout_mode = 0
offset_left = 1360.0
offset_top = 64.0
@@ -273,7 +260,6 @@ action_mode = 0
text = "Reset playlist"
[node name="ResetPointsButton" type="Button" parent="." unique_id=942880776]
texture_filter = 1
layout_mode = 0
offset_left = 1488.0
offset_top = 64.0
@@ -284,7 +270,6 @@ action_mode = 0
text = "Reset points"
[node name="SyncButton" type="Button" parent="." unique_id=1228313556]
texture_filter = 1
layout_mode = 0
offset_left = 1616.0
offset_top = 64.0
@@ -295,7 +280,6 @@ action_mode = 0
text = "Sync soundtracks"
[node name="SoundTestButton" type="Button" parent="." unique_id=1332971879]
texture_filter = 1
layout_mode = 0
offset_left = 1776.0
offset_top = 64.0
@@ -306,7 +290,6 @@ action_mode = 0
text = "Sound test"
[node name="ShowAnswerButton" type="Button" parent="." unique_id=415388039]
texture_filter = 1
layout_mode = 0
offset_left = 65.0
offset_top = 944.0
@@ -318,7 +301,6 @@ shortcut = SubResource("Shortcut_a7fvb")
text = "Show answer"
[node name="NextButton" type="Button" parent="." unique_id=1744703488]
texture_filter = 1
layout_mode = 0
offset_left = 193.0
offset_top = 944.0
@@ -331,7 +313,6 @@ shortcut = SubResource("Shortcut_d6fml")
text = "Randomize new track"
[node name="SearchButton" type="Button" parent="." unique_id=1782453581]
texture_filter = 1
layout_mode = 0
offset_left = 1354.0
offset_top = 8.0
@@ -343,7 +324,6 @@ shortcut = SubResource("Shortcut_fbju4")
text = "Search"
[node name="SettingsButton" type="Button" parent="." unique_id=1193400323]
texture_filter = 1
layout_mode = 0
offset_left = 1442.0
offset_top = 8.0
@@ -354,7 +334,6 @@ action_mode = 0
text = "Settings"
[node name="StatisticsButton" type="Button" parent="." unique_id=2045021269]
texture_filter = 1
layout_mode = 0
offset_left = 1530.0
offset_top = 8.0
@@ -365,7 +344,6 @@ action_mode = 0
text = "Statistics"
[node name="ShortcutsButton" type="Button" parent="." unique_id=2066498226]
texture_filter = 1
layout_mode = 0
offset_left = 1626.0
offset_top = 8.0
@@ -376,7 +354,6 @@ action_mode = 0
text = "Shortcuts"
[node name="AboutButton" type="Button" parent="." unique_id=1057035253]
texture_filter = 1
layout_mode = 0
offset_left = 1722.0
offset_top = 8.0
@@ -387,7 +364,6 @@ action_mode = 0
text = "About"
[node name="QRButton" type="Button" parent="." unique_id=2021908559]
texture_filter = 1
layout_mode = 0
offset_left = 1802.0
offset_top = 8.0
@@ -409,9 +385,9 @@ offset_bottom = 96.0
unique_name_in_owner = true
visible = false
layout_mode = 1
offset_left = 401.0
offset_left = 210.0
offset_top = 153.0
offset_right = 401.0
offset_right = 210.0
offset_bottom = 153.0
[node name="SettingsWindow" parent="." unique_id=1323246978 instance=ExtResource("11_k62u5")]
@@ -435,9 +411,9 @@ offset_bottom = 153.0
unique_name_in_owner = true
visible = false
layout_mode = 1
offset_left = 420.0
offset_left = 405.0
offset_top = 165.0
offset_right = 1572.0
offset_right = 1557.0
offset_bottom = 805.0
[node name="ShortcutsWindow" parent="." unique_id=1513416242 instance=ExtResource("13_8eebo")]
@@ -450,12 +426,12 @@ offset_right = 1262.0
offset_bottom = 860.0
[node name="StatisticsPopupPanel" type="PopupPanel" parent="." unique_id=298311407]
theme = ExtResource("popup_theme")
oversampling_override = 1.0
initial_position = 2
size = Vector2i(450, 100)
[node name="Label" type="Label" parent="StatisticsPopupPanel" unique_id=548316493]
texture_filter = 1
offset_left = 4.0
offset_top = 4.0
offset_right = 446.0
@@ -464,7 +440,6 @@ text = "Statistics"
horizontal_alignment = 1
[node name="StatisticsLabel" type="Label" parent="StatisticsPopupPanel" unique_id=58226477]
texture_filter = 1
offset_left = 4.0
offset_top = 4.0
offset_right = 446.0
-3
View File
@@ -28,9 +28,6 @@ var volume_slider: HSlider = $MusicPlayerContainer/VolumeSlider
@onready
var music_time_label: Label = $MusicPlayerContainer/MusicTimeLabel
@onready
var path: String = '/Users/sebastian/ResilioSync/Sorterat_test/Metal Gear Solid 4 - Guns of the Patriots/2-16 Metal Gear Saga.mp3'
var play_icon: Texture = preload("res://icons/play_icon_light.svg")
var pause_icon: Texture = preload("res://icons/pause_icon_light.svg")
-5
View File
@@ -60,11 +60,6 @@ func minus_point() -> void:
func change_character() -> void:
change_character_clicked.emit()
func character_selected(file_name: String) -> void:
print("Back in player list with: " + file_name)
player_object.character = load("res://characters/" + file_name)
set_player_character()
func new_character_selected(character_texture: ImageTexture) -> void:
print("Back in player list with new")
player_object.character = character_texture
+10 -5
View File
@@ -29,9 +29,12 @@ script = ExtResource("1_if4kc")
[node name="HBoxContainer" type="HBoxContainer" parent="."]
custom_minimum_size = Vector2(2.08165e-12, 40)
layout_mode = 0
offset_right = 40.0
offset_bottom = 40.0
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="Panel4" type="Panel" parent="HBoxContainer"]
custom_minimum_size = Vector2(5, 40)
@@ -41,12 +44,15 @@ theme_override_styles/panel = SubResource("StyleBoxFlat_821k2")
[node name="Name" type="Label" parent="HBoxContainer"]
custom_minimum_size = Vector2(130, 40)
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 1
text = ": 0"
vertical_alignment = 1
clip_text = true
text_overrun_behavior = 3
[node name="Panel3" type="Panel" parent="HBoxContainer"]
custom_minimum_size = Vector2(80, 2.08165e-12)
custom_minimum_size = Vector2(16, 2.08165e-12)
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_w10e8")
@@ -91,7 +97,6 @@ theme_override_styles/panel = SubResource("StyleBoxFlat_b2crt")
[node name="RemovePlayer" type="TextureButton" parent="HBoxContainer"]
custom_minimum_size = Vector2(40, 40)
layout_mode = 2
size_flags_horizontal = 3
focus_mode = 0
action_mode = 0
texture_normal = ExtResource("3_j3uxe")
+12 -5
View File
@@ -19,7 +19,10 @@ var player_score: int
var is_first_point: bool = true
var character: Texture = load("res://noCharacter.png")
## Picture shown until the player picks a character.
const NO_CHARACTER_PATH: String = "res://noCharacter.png"
var character: Texture = load(NO_CHARACTER_PATH)
func _init(new_name: String) -> void:
player_name = new_name
@@ -37,13 +40,17 @@ func add_point() -> void:
if player_score < Settings.winning_score:
player_score += 1
player_point_given.emit()
if player_score == 1 && is_first_point:
var first_point: bool = player_score == 1 && is_first_point
if player_score == 1:
is_first_point = false
first_point_triggerd.emit()
# With a low winning score the first point can also be match point or the win,
# so the more important event is checked first
if player_score == Settings.winning_score:
winner_triggerd.emit()
elif player_score == Settings.winning_score - 1:
match_point_triggerd.emit()
elif player_score == Settings.winning_score:
winner_triggerd.emit()
elif first_point:
first_point_triggerd.emit()
else:
point_given_sound.emit()
+3 -14
View File
@@ -1,4 +1,4 @@
extends Control
extends PopupWindow
@onready
var qr_panel: Panel = $QRPanel
@@ -21,21 +21,10 @@ func _ready() -> void:
url_label.text = qr_code_rect.data
print("QR code generated for: ", url)
func show_window() -> void:
visible = true
func open() -> void:
super()
# Regenerate QR code in case settings changed
var url: String = Settings.default_path + "/search"
qr_code_rect.data = url
url_label.text = qr_code_rect.data
func _input(event: InputEvent) -> void:
if self.visible == true:
if event is InputEventMouseButton && event.is_pressed():
var evLocal: InputEvent = make_input_local(event)
if !Rect2(Vector2(0, 0), qr_panel.size).has_point(evLocal.position):
self.visible = false
func hide_window() -> void:
visible = false
+4 -1
View File
@@ -2,8 +2,10 @@
[ext_resource type="Script" uid="uid://bsa6xg5xos6h7" path="res://QRWindow.gd" id="1_qr_script"]
[ext_resource type="Script" uid="uid://dynulhnyq5hpq" path="res://addons/kenyoni/qr_code/qr_code_rect.gd" id="2_jpsxt"]
[ext_resource type="Theme" path="res://popup_theme.tres" id="popup_theme"]
[node name="QRControl" type="Control" unique_id=1996957585]
[node name="QRControl" type="Control" unique_id=1996957585 node_paths=PackedStringArray("panel")]
theme = ExtResource("popup_theme")
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
@@ -11,6 +13,7 @@ anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_qr_script")
panel = NodePath("QRPanel")
[node name="QRPanel" type="Panel" parent="." unique_id=1021365843]
unique_name_in_owner = true
+10 -1
View File
@@ -11,10 +11,19 @@ same version in the About window.
The `Build` workflow (`.gitea/workflows/gitea-build.yaml`) then:
1. runs the tests,
2. exports the Windows and Linux builds,
2. exports the Windows and Linux builds, and packages Linux as
`MusicPlayer_<version>_linux.tar.gz` with the icon, an app menu entry and `install.sh`
(Linux programs can't carry their own icon the way a Windows .exe does),
3. if no tag exists for the version yet: uploads the builds to the package registry
under `MusicPlayer/<version>/`, and creates the `<version>` tag and Gitea release
with the builds attached.
Pushes that don't change the version, and pushes to `develop`, only test and export.
Don't create tags or `1.x.y` branches by hand; the workflow makes the tag.
## Installing on Linux
Unpack `MusicPlayer_<version>_linux.tar.gz` and run `./install.sh` inside the
`MusicPlayer` folder. It installs the game to `~/.local/bin` and adds it, with its icon,
to the app menu. The bare `MusicPlayer_<version>.x86_64` is still published for anyone
who just wants to run the file.
+2 -9
View File
@@ -1,4 +1,4 @@
extends Control
extends PopupWindow
@onready
var search_list: VBoxContainer = $ScrollContainer/VBoxContainer
@@ -36,7 +36,7 @@ func focus() -> void:
func close() -> void:
clear()
self.visible = false
super()
func search() -> void:
print(search_bar.text)
@@ -179,13 +179,6 @@ func set_soundtracks(list: Array) -> void:
"cleaned": clean_term(soundtrack)
})
func _input(event: InputEvent) -> void:
if self.visible == true:
if event is InputEventMouseButton && event.is_pressed():
var evLocal: InputEvent = make_input_local(event)
if !Rect2(Vector2(0, 0), search_panel.size).has_point(evLocal.position):
self.visible = false
func clear() -> void:
search_bar.text = ""
show_all_soundtracks()
+4 -3
View File
@@ -1,8 +1,10 @@
[gd_scene format=3 uid="uid://bxydgil1yifps"]
[ext_resource type="Script" uid="uid://bg5yo08638600" path="res://SearchWindow.gd" id="1_m7bij"]
[ext_resource type="Theme" path="res://popup_theme.tres" id="popup_theme"]
[node name="Control" type="Control" unique_id=616238617]
[node name="Control" type="Control" unique_id=616238617 node_paths=PackedStringArray("panel")]
theme = ExtResource("popup_theme")
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
@@ -10,6 +12,7 @@ anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_m7bij")
panel = NodePath("SearchPanel")
[node name="SearchPanel" type="Panel" parent="." unique_id=1262945994]
unique_name_in_owner = true
@@ -18,7 +21,6 @@ offset_right = 1152.0
offset_bottom = 640.0
[node name="ClearButton" type="Button" parent="." unique_id=1549439398]
texture_filter = 1
layout_mode = 0
offset_left = 936.0
offset_top = 16.0
@@ -30,7 +32,6 @@ action_mode = 0
text = "Clear"
[node name="Searchbar" type="TextEdit" parent="." unique_id=2112669372]
texture_filter = 1
layout_mode = 0
offset_left = 32.0
offset_top = 16.0
-5
View File
@@ -36,8 +36,3 @@ func delete_children(node: Node) -> void:
for n: Node in node.get_children():
node.remove_child(n)
n.queue_free()
func delete_player_children(node: Node) -> void:
for n: Node in node.get_children():
node.remove_child(n)
n.queue_free()
+7 -8
View File
@@ -1,4 +1,4 @@
extends Control
extends PopupWindow
@onready
var settings_panel: Panel = %SettingsPanel
@@ -109,13 +109,12 @@ func _ready() -> void:
update_hide_for_seconds_enabled()
select_server_button.select(Settings.selected_server)
select_server_button.item_selected.connect(select_server)
visibility_changed.connect(_on_visibility_changed)
func _input(event: InputEvent) -> void:
if self.visible == true:
if event is InputEventMouseButton && event.is_pressed():
var evLocal: InputEvent = make_input_local(event)
if !Rect2(Vector2(0, 0), settings_panel.size).has_point(evLocal.position):
self.visible = false
# 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
func fullscreen() -> void:
Settings.fullscreen = !Settings.fullscreen
@@ -154,7 +153,7 @@ func hide_ticks() -> void:
Settings.hide_ticks = !Settings.hide_ticks
func lower_winning_score() -> void:
Settings.winning_score -= 1
Settings.winning_score = max(Settings.winning_score - 1, 1)
score_label.text = str(Settings.winning_score)
func increase_winning_score() -> void:
+31 -13
View File
@@ -1,11 +1,13 @@
[gd_scene format=3 uid="uid://dldpeo5y3l5hq"]
[ext_resource type="Script" uid="uid://dukqyjoduf1af" path="res://SettingsWindow.gd" id="1_bt55j"]
[ext_resource type="Theme" path="res://popup_theme.tres" id="popup_theme"]
[sub_resource type="LabelSettings" id="LabelSettings_3xrlm"]
font_size = 25
[node name="SettingsControl" type="Control" unique_id=1298840328]
[node name="SettingsControl" type="Control" unique_id=1298840328 node_paths=PackedStringArray("panel")]
theme = ExtResource("popup_theme")
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
@@ -13,6 +15,7 @@ anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_bt55j")
panel = NodePath("SettingsPanel")
[node name="SettingsPanel" type="Panel" parent="." unique_id=45537159]
unique_name_in_owner = true
@@ -22,8 +25,11 @@ offset_bottom = 640.0
[node name="ScrollContainer" type="ScrollContainer" parent="SettingsPanel" unique_id=1325795358]
layout_mode = 0
offset_right = 500.0
offset_bottom = 640.0
offset_left = 20.0
offset_top = 8.0
offset_right = 480.0
offset_bottom = 632.0
horizontal_scroll_mode = 0
[node name="VBoxContainer" type="VBoxContainer" parent="SettingsPanel/ScrollContainer" unique_id=1796635163]
clip_contents = true
@@ -40,7 +46,7 @@ horizontal_alignment = 1
[node name="StopAfterCurrentCheckButton" type="CheckButton" parent="SettingsPanel/ScrollContainer/VBoxContainer" unique_id=508305575]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 0
size_flags_horizontal = 3
button_pressed = true
action_mode = 0
text = "Stop after current"
@@ -48,7 +54,7 @@ text = "Stop after current"
[node name="HideNextTrackCheckButton" type="CheckButton" parent="SettingsPanel/ScrollContainer/VBoxContainer" unique_id=1568244599]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 0
size_flags_horizontal = 3
button_pressed = true
action_mode = 0
text = "Hide next track"
@@ -56,21 +62,21 @@ text = "Hide next track"
[node name="AddToDatabaseCheckButton" type="CheckButton" parent="SettingsPanel/ScrollContainer/VBoxContainer" unique_id=1673750542]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 0
size_flags_horizontal = 3
action_mode = 0
text = "Turn on played to database"
[node name="LowPlayedCheckButton" type="CheckButton" parent="SettingsPanel/ScrollContainer/VBoxContainer" unique_id=1672657048]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 0
size_flags_horizontal = 3
action_mode = 0
text = "Use low played mode"
[node name="HideBeginningCheckButton" type="CheckButton" parent="SettingsPanel/ScrollContainer/VBoxContainer" unique_id=1370617867]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 0
size_flags_horizontal = 3
action_mode = 0
text = "Hide beginning of songs"
@@ -81,12 +87,15 @@ layout_mode = 2
[node name="HideForSecondsTitle" type="Label" parent="SettingsPanel/ScrollContainer/VBoxContainer/HideForSecondsHBoxContainer" unique_id=1822369383]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
text = "Hide Start Seconds: "
[node name="HideForSecondsLabel" type="Label" parent="SettingsPanel/ScrollContainer/VBoxContainer/HideForSecondsHBoxContainer" unique_id=1580108945]
unique_name_in_owner = true
custom_minimum_size = Vector2(48, 0)
layout_mode = 2
text = "5.0"
horizontal_alignment = 1
[node name="LowerHideForSecondsButton" type="Button" parent="SettingsPanel/ScrollContainer/VBoxContainer/HideForSecondsHBoxContainer" unique_id=180643442]
unique_name_in_owner = true
@@ -107,14 +116,14 @@ text = "+1"
[node name="HideLengthCheckButton" type="CheckButton" parent="SettingsPanel/ScrollContainer/VBoxContainer" unique_id=67806372]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 0
size_flags_horizontal = 3
action_mode = 0
text = "Hide length of songs"
[node name="HideTicksCheckButton" type="CheckButton" parent="SettingsPanel/ScrollContainer/VBoxContainer" unique_id=706322752]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 0
size_flags_horizontal = 3
action_mode = 0
text = "Hide ticks in playbar"
@@ -123,12 +132,15 @@ layout_mode = 2
[node name="Label2" type="Label" parent="SettingsPanel/ScrollContainer/VBoxContainer/HBoxContainer" unique_id=1892907487]
layout_mode = 2
size_flags_horizontal = 3
text = "Winning Score: "
[node name="ScoreLabel" type="Label" parent="SettingsPanel/ScrollContainer/VBoxContainer/HBoxContainer" unique_id=444542662]
unique_name_in_owner = true
custom_minimum_size = Vector2(48, 0)
layout_mode = 2
text = "20"
horizontal_alignment = 1
[node name="LowerButton" type="Button" parent="SettingsPanel/ScrollContainer/VBoxContainer/HBoxContainer" unique_id=614806355]
unique_name_in_owner = true
@@ -151,12 +163,15 @@ layout_mode = 2
[node name="CacheTitleLabel" type="Label" parent="SettingsPanel/ScrollContainer/VBoxContainer/CacheHBoxContainer" unique_id=365012193]
layout_mode = 2
size_flags_horizontal = 3
text = "Predownload songs: "
[node name="CacheLabel" type="Label" parent="SettingsPanel/ScrollContainer/VBoxContainer/CacheHBoxContainer" unique_id=1558370172]
unique_name_in_owner = true
custom_minimum_size = Vector2(48, 0)
layout_mode = 2
text = "1"
horizontal_alignment = 1
[node name="LowerCacheButton" type="Button" parent="SettingsPanel/ScrollContainer/VBoxContainer/CacheHBoxContainer" unique_id=1990385038]
unique_name_in_owner = true
@@ -179,12 +194,15 @@ layout_mode = 2
[node name="InspirationListSpeedTitle" type="Label" parent="SettingsPanel/ScrollContainer/VBoxContainer/InspirationHBoxContainer" unique_id=1397861506]
layout_mode = 2
size_flags_horizontal = 3
text = "Inspiration List Speed: "
[node name="InspirationListSpeedLabel" type="Label" parent="SettingsPanel/ScrollContainer/VBoxContainer/InspirationHBoxContainer" unique_id=867387668]
unique_name_in_owner = true
custom_minimum_size = Vector2(48, 0)
layout_mode = 2
text = "1"
horizontal_alignment = 1
[node name="LowerInspirationSpeedButton" type="Button" parent="SettingsPanel/ScrollContainer/VBoxContainer/InspirationHBoxContainer" unique_id=845209534]
unique_name_in_owner = true
@@ -221,13 +239,13 @@ popup/item_4/id = 5
[node name="FullscreenButton" type="CheckButton" parent="SettingsPanel/ScrollContainer/VBoxContainer" unique_id=400304684]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 0
size_flags_horizontal = 3
text = "Fullscreen"
[node name="QuickSyncButton" type="CheckButton" parent="SettingsPanel/ScrollContainer/VBoxContainer" unique_id=49497448]
visible = false
layout_mode = 2
size_flags_horizontal = 0
size_flags_horizontal = 3
button_pressed = true
action_mode = 0
text = "Quick sync"
@@ -236,6 +254,6 @@ text = "Quick sync"
unique_name_in_owner = true
visible = false
layout_mode = 2
size_flags_horizontal = 0
size_flags_horizontal = 3
disabled = true
text = "Local"
+21 -17
View File
@@ -1,11 +1,13 @@
[gd_scene format=3 uid="uid://dpdem7pdxweb5"]
[ext_resource type="Script" uid="uid://c3ejukwth512a" path="res://sync_window.gd" id="1_8ba47"]
[ext_resource type="Theme" path="res://popup_theme.tres" id="popup_theme"]
[sub_resource type="LabelSettings" id="LabelSettings_anbfi"]
font_size = 25
[node name="Control2" type="Control" unique_id=1302914356]
[node name="Control2" type="Control" unique_id=1302914356 node_paths=PackedStringArray("panel")]
theme = ExtResource("popup_theme")
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
@@ -13,21 +15,26 @@ anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_8ba47")
panel = NodePath("SyncPanel")
[node name="SyncPanel" type="Panel" parent="." unique_id=1714634204]
unique_name_in_owner = true
layout_mode = 0
offset_right = 1152.0
offset_right = 1500.0
offset_bottom = 640.0
[node name="ScrollContainer" type="ScrollContainer" parent="SyncPanel" unique_id=1289116349]
layout_mode = 0
offset_right = 1152.0
offset_bottom = 640.0
offset_left = 20.0
offset_top = 8.0
offset_right = 1480.0
offset_bottom = 632.0
horizontal_scroll_mode = 0
[node name="SyncVBoxContainer" type="VBoxContainer" parent="SyncPanel/ScrollContainer" unique_id=1594735567]
layout_mode = 2
size_flags_horizontal = 6
size_flags_horizontal = 3
theme_override_constants/separation = 12
[node name="SyncTitleLabel" type="Label" parent="SyncPanel/ScrollContainer/SyncVBoxContainer" unique_id=2125198054]
unique_name_in_owner = true
@@ -59,7 +66,7 @@ layout_mode = 2
[node name="SyncProgressBar" type="ProgressBar" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/ProgressHBoxContainer" unique_id=719135231]
unique_name_in_owner = true
custom_minimum_size = Vector2(1000, 0)
custom_minimum_size = Vector2(1300, 0)
layout_mode = 2
size_flags_horizontal = 6
step = 1.0
@@ -69,17 +76,13 @@ rounded = true
unique_name_in_owner = true
visible = false
layout_mode = 2
size_flags_horizontal = 6
theme_override_constants/separation = 30
[node name="VSeparator6" type="VSeparator" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer" unique_id=1432793432]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 16
[node name="SoundtracksAddedVBox" type="VBoxContainer" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer" unique_id=897546696]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 6
size_flags_horizontal = 3
[node name="SoundtracksAddedLabel" type="Label" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/SoundtracksAddedVBox" unique_id=535797768]
layout_mode = 2
@@ -93,7 +96,7 @@ layout_mode = 2
[node name="SoundtracksReAddedVBox" type="VBoxContainer" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer" unique_id=995736806]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 6
size_flags_horizontal = 3
[node name="SoundtracksReAddedLabel" type="Label" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/SoundtracksReAddedVBox" unique_id=1142636525]
layout_mode = 2
@@ -106,7 +109,7 @@ layout_mode = 2
[node name="SoundtracksChangedTitleVBox" type="VBoxContainer" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer" unique_id=294212033]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 6
size_flags_horizontal = 3
[node name="SoundtracksChangedTitleLabel" type="Label" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/SoundtracksChangedTitleVBox" unique_id=696960827]
layout_mode = 2
@@ -119,7 +122,7 @@ layout_mode = 2
[node name="SoundtracksChangedContentVBox" type="VBoxContainer" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer" unique_id=1470922281]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 6
size_flags_horizontal = 3
[node name="SoundtracksChangedContentLabel" type="Label" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/SoundtracksChangedContentVBox" unique_id=1209639099]
layout_mode = 2
@@ -132,7 +135,7 @@ layout_mode = 2
[node name="SoundtracksRemovedVBox" type="VBoxContainer" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer" unique_id=1174594338]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 6
size_flags_horizontal = 3
[node name="SoundtracksRemovedLabel" type="Label" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/SoundtracksRemovedVBox" unique_id=877713862]
layout_mode = 2
@@ -146,6 +149,7 @@ layout_mode = 2
unique_name_in_owner = true
visible = false
layout_mode = 2
size_flags_horizontal = 3
[node name="CatchedErrorsLabel" type="Label" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/CatchedErrorsVBox" unique_id=1784389644]
layout_mode = 2
+4 -2
View File
@@ -2,11 +2,13 @@
[ext_resource type="Script" uid="uid://dxpb22xbwf3t1" path="res://winner_window.gd" id="1_winner_script"]
[ext_resource type="Texture2D" uid="uid://r4as0nmtoa7p" path="res://noCharacter.png" id="2_no_character"]
[ext_resource type="Theme" path="res://popup_theme.tres" id="popup_theme"]
[sub_resource type="LabelSettings" id="LabelSettings_hr75l"]
font_size = 35
[node name="WinnerControl" type="Control" unique_id=1111111111]
[node name="WinnerControl" type="Control" unique_id=1111111111 node_paths=PackedStringArray("panel")]
theme = ExtResource("popup_theme")
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
@@ -14,6 +16,7 @@ anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_winner_script")
panel = NodePath("WinnerPanel")
[node name="WinnerPanel" type="Panel" parent="." unique_id=89212869]
unique_name_in_owner = true
@@ -40,7 +43,6 @@ stretch_mode = 5
[node name="WinnerLabel" type="Label" parent="WinnerPanel/VBoxContainer" unique_id=1437597286]
unique_name_in_owner = true
texture_filter = 1
layout_mode = 2
text = "Winner!"
label_settings = SubResource("LabelSettings_hr75l")
+4 -4
View File
@@ -8,7 +8,7 @@ dedicated_server=false
custom_features=""
export_filter="all_resources"
include_filter=""
exclude_filter="tests/*, addons/gdUnit4/*"
exclude_filter="tests/*, addons/gdUnit4/*, linux/*"
export_path="../../MusicPlayer_1.5.1.exe"
patches=PackedStringArray()
encryption_include_filters=""
@@ -83,7 +83,7 @@ dedicated_server=false
custom_features=""
export_filter="all_resources"
include_filter=""
exclude_filter="tests/*, addons/gdUnit4/*"
exclude_filter="tests/*, addons/gdUnit4/*, linux/*"
export_path="./MusicPlayer_0.9_Beta.dmg"
patches=PackedStringArray()
encryption_include_filters=""
@@ -343,7 +343,7 @@ dedicated_server=false
custom_features=""
export_filter="all_resources"
include_filter=""
exclude_filter="tests/*, addons/gdUnit4/*"
exclude_filter="tests/*, addons/gdUnit4/*, linux/*"
export_path="web/index.html"
patches=PackedStringArray()
encryption_include_filters=""
@@ -390,7 +390,7 @@ dedicated_server=false
custom_features=""
export_filter="all_resources"
include_filter=""
exclude_filter="tests/*, addons/gdUnit4/*"
exclude_filter="tests/*, addons/gdUnit4/*, linux/*"
export_path="./MusicPlayer_1.5.1.x86_64"
patches=PackedStringArray()
encryption_include_filters=""
+116 -1
View File
@@ -1 +1,116 @@
<svg height="128" width="128" xmlns="http://www.w3.org/2000/svg"><g transform="translate(32 32)"><path d="m-16-32c-8.86 0-16 7.13-16 15.99v95.98c0 8.86 7.13 15.99 16 15.99h96c8.86 0 16-7.13 16-15.99v-95.98c0-8.85-7.14-15.99-16-15.99z" fill="#363d52"/><path d="m-16-32c-8.86 0-16 7.13-16 15.99v95.98c0 8.86 7.13 15.99 16 15.99h96c8.86 0 16-7.13 16-15.99v-95.98c0-8.85-7.14-15.99-16-15.99zm0 4h96c6.64 0 12 5.35 12 11.99v95.98c0 6.64-5.35 11.99-12 11.99h-96c-6.64 0-12-5.35-12-11.99v-95.98c0-6.64 5.36-11.99 12-11.99z" fill-opacity=".4"/></g><g stroke-width="9.92746" transform="matrix(.10073078 0 0 .10073078 12.425923 2.256365)"><path d="m0 0s-.325 1.994-.515 1.976l-36.182-3.491c-2.879-.278-5.115-2.574-5.317-5.459l-.994-14.247-27.992-1.997-1.904 12.912c-.424 2.872-2.932 5.037-5.835 5.037h-38.188c-2.902 0-5.41-2.165-5.834-5.037l-1.905-12.912-27.992 1.997-.994 14.247c-.202 2.886-2.438 5.182-5.317 5.46l-36.2 3.49c-.187.018-.324-1.978-.511-1.978l-.049-7.83 30.658-4.944 1.004-14.374c.203-2.91 2.551-5.263 5.463-5.472l38.551-2.75c.146-.01.29-.016.434-.016 2.897 0 5.401 2.166 5.825 5.038l1.959 13.286h28.005l1.959-13.286c.423-2.871 2.93-5.037 5.831-5.037.142 0 .284.005.423.015l38.556 2.75c2.911.209 5.26 2.562 5.463 5.472l1.003 14.374 30.645 4.966z" fill="#fff" transform="matrix(4.162611 0 0 -4.162611 919.24059 771.67186)"/><path d="m0 0v-47.514-6.035-5.492c.108-.001.216-.005.323-.015l36.196-3.49c1.896-.183 3.382-1.709 3.514-3.609l1.116-15.978 31.574-2.253 2.175 14.747c.282 1.912 1.922 3.329 3.856 3.329h38.188c1.933 0 3.573-1.417 3.855-3.329l2.175-14.747 31.575 2.253 1.115 15.978c.133 1.9 1.618 3.425 3.514 3.609l36.182 3.49c.107.01.214.014.322.015v4.711l.015.005v54.325c5.09692 6.4164715 9.92323 13.494208 13.621 19.449-5.651 9.62-12.575 18.217-19.976 26.182-6.864-3.455-13.531-7.369-19.828-11.534-3.151 3.132-6.7 5.694-10.186 8.372-3.425 2.751-7.285 4.768-10.946 7.118 1.09 8.117 1.629 16.108 1.846 24.448-9.446 4.754-19.519 7.906-29.708 10.17-4.068-6.837-7.788-14.241-11.028-21.479-3.842.642-7.702.88-11.567.926v.006c-.027 0-.052-.006-.075-.006-.024 0-.049.006-.073.006v-.006c-3.872-.046-7.729-.284-11.572-.926-3.238 7.238-6.956 14.642-11.03 21.479-10.184-2.264-20.258-5.416-29.703-10.17.216-8.34.755-16.331 1.848-24.448-3.668-2.35-7.523-4.367-10.949-7.118-3.481-2.678-7.036-5.24-10.188-8.372-6.297 4.165-12.962 8.079-19.828 11.534-7.401-7.965-14.321-16.562-19.974-26.182 4.4426579-6.973692 9.2079702-13.9828876 13.621-19.449z" fill="#478cbf" transform="matrix(4.162611 0 0 -4.162611 104.69892 525.90697)"/><path d="m0 0-1.121-16.063c-.135-1.936-1.675-3.477-3.611-3.616l-38.555-2.751c-.094-.007-.188-.01-.281-.01-1.916 0-3.569 1.406-3.852 3.33l-2.211 14.994h-31.459l-2.211-14.994c-.297-2.018-2.101-3.469-4.133-3.32l-38.555 2.751c-1.936.139-3.476 1.68-3.611 3.616l-1.121 16.063-32.547 3.138c.015-3.498.06-7.33.06-8.093 0-34.374 43.605-50.896 97.781-51.086h.066.067c54.176.19 97.766 16.712 97.766 51.086 0 .777.047 4.593.063 8.093z" fill="#478cbf" transform="matrix(4.162611 0 0 -4.162611 784.07144 817.24284)"/><path d="m0 0c0-12.052-9.765-21.815-21.813-21.815-12.042 0-21.81 9.763-21.81 21.815 0 12.044 9.768 21.802 21.81 21.802 12.048 0 21.813-9.758 21.813-21.802" fill="#fff" transform="matrix(4.162611 0 0 -4.162611 389.21484 625.67104)"/><path d="m0 0c0-7.994-6.479-14.473-14.479-14.473-7.996 0-14.479 6.479-14.479 14.473s6.483 14.479 14.479 14.479c8 0 14.479-6.485 14.479-14.479" fill="#414042" transform="matrix(4.162611 0 0 -4.162611 367.36686 631.05679)"/><path d="m0 0c-3.878 0-7.021 2.858-7.021 6.381v20.081c0 3.52 3.143 6.381 7.021 6.381s7.028-2.861 7.028-6.381v-20.081c0-3.523-3.15-6.381-7.028-6.381" fill="#fff" transform="matrix(4.162611 0 0 -4.162611 511.99336 724.73954)"/><path d="m0 0c0-12.052 9.765-21.815 21.815-21.815 12.041 0 21.808 9.763 21.808 21.815 0 12.044-9.767 21.802-21.808 21.802-12.05 0-21.815-9.758-21.815-21.802" fill="#fff" transform="matrix(4.162611 0 0 -4.162611 634.78706 625.67104)"/><path d="m0 0c0-7.994 6.477-14.473 14.471-14.473 8.002 0 14.479 6.479 14.479 14.473s-6.477 14.479-14.479 14.479c-7.994 0-14.471-6.485-14.471-14.479" fill="#414042" transform="matrix(4.162611 0 0 -4.162611 656.64056 631.05679)"/></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512">
<defs>
<linearGradient id="sky" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#6fb1d6"/>
<stop offset="1" stop-color="#2e6a94"/>
</linearGradient>
<radialGradient id="plate" cx="0.42" cy="0.38" r="0.7">
<stop offset="0" stop-color="#ffffff"/>
<stop offset="1" stop-color="#e3ded6"/>
</radialGradient>
<radialGradient id="well" cx="0.45" cy="0.4" r="0.7">
<stop offset="0" stop-color="#fbf9f5"/>
<stop offset="1" stop-color="#e9e3d9"/>
</radialGradient>
<linearGradient id="band" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#4a4a4a"/>
<stop offset="1" stop-color="#1f1f1f"/>
</linearGradient>
<linearGradient id="cup" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#3d3d3d"/>
<stop offset="1" stop-color="#151515"/>
</linearGradient>
<linearGradient id="cookie" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#d99a52"/>
<stop offset="1" stop-color="#b8742f"/>
</linearGradient>
<linearGradient id="falu" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#c63b2f"/>
<stop offset="1" stop-color="#9e2a21"/>
</linearGradient>
</defs>
<!-- Background -->
<rect x="16" y="16" width="480" height="480" rx="108" fill="url(#sky)"/>
<!-- Plate shadow, plate, rim band and well -->
<ellipse cx="262" cy="306" rx="178" ry="174" fill="#1d4a6b" opacity="0.35"/>
<circle cx="256" cy="294" r="172" fill="url(#plate)" stroke="#d6d0c6" stroke-width="3"/>
<circle cx="256" cy="294" r="150" fill="none" stroke="#3b6ea5" stroke-width="7"/>
<circle cx="256" cy="294" r="139" fill="none" stroke="#3b6ea5" stroke-width="2"/>
<circle cx="256" cy="294" r="116" fill="url(#well)" stroke="#ddd6cb" stroke-width="2"/>
<!-- Headphone band arching over the plate -->
<path d="M 92 286 A 164 164 0 0 1 420 286" fill="none" stroke="url(#band)" stroke-width="26" stroke-linecap="round"/>
<path d="M 104 262 A 152 152 0 0 1 408 262" fill="none" stroke="#6a6a6a" stroke-width="4" stroke-linecap="round" opacity="0.6"/>
<!-- Ear cups on the plate's edges -->
<g>
<rect x="58" y="232" width="62" height="118" rx="28" fill="url(#cup)"/>
<rect x="104" y="246" width="28" height="90" rx="14" fill="#2c2c2c" stroke="#ff8c1a" stroke-width="4"/>
<rect x="70" y="252" width="10" height="56" rx="5" fill="#ffffff" opacity="0.15"/>
</g>
<g>
<rect x="392" y="232" width="62" height="118" rx="28" fill="url(#cup)"/>
<rect x="380" y="246" width="28" height="90" rx="14" fill="#2c2c2c" stroke="#ff8c1a" stroke-width="4"/>
<rect x="404" y="252" width="10" height="56" rx="5" fill="#ffffff" opacity="0.15"/>
</g>
<!-- Crumbs -->
<g fill="#c98a45">
<circle cx="178" cy="370" r="4"/>
<circle cx="190" cy="381" r="2.5"/>
<circle cx="336" cy="378" r="3.5"/>
<circle cx="326" cy="214" r="2.5"/>
<circle cx="184" cy="226" r="3"/>
</g>
<!-- House-shaped cookie: thick baked edge, then falu red icing -->
<g transform="translate(256 288) scale(1.12) translate(-256 -288)">
<!-- shadow -->
<path d="M 256 192 L 344 270 L 334 270 L 334 368 L 178 368 L 178 270 L 168 270 Z"
fill="#7a4a1e" opacity="0.28" stroke="#7a4a1e" stroke-width="16" stroke-linejoin="round"
transform="translate(5 7)"/>
<!-- chimney, baked -->
<rect x="290" y="206" width="22" height="40" rx="6" fill="#c98a45" stroke="#c98a45" stroke-width="10" stroke-linejoin="round"/>
<!-- cookie body with a thick, rounded baked edge -->
<path d="M 256 192 L 344 270 L 334 270 L 334 368 L 178 368 L 178 270 L 168 270 Z"
fill="url(#cookie)" stroke="#c98a45" stroke-width="16" stroke-linejoin="round"/>
<path d="M 256 192 L 344 270 L 334 270 L 334 368 L 178 368 L 178 270 L 168 270 Z"
fill="none" stroke="#a8682a" stroke-width="2" stroke-linejoin="round" transform="translate(0 0)" opacity="0.6"/>
<rect x="295" y="214" width="12" height="26" rx="2" fill="#3a3a3a"/>
<!-- roof icing -->
<path d="M 256 210 L 324 270 L 188 270 Z" fill="#3e3e46"/>
<!-- walls icing, falu red -->
<rect x="190" y="272" width="132" height="86" rx="4" fill="url(#falu)"/>
<!-- white roof edge piping -->
<path d="M 182 274 L 256 208 L 330 274" fill="none" stroke="#fdfbf7" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/>
<!-- white corner boards -->
<rect x="190" y="272" width="8" height="86" fill="#fdfbf7"/>
<rect x="314" y="272" width="8" height="86" fill="#fdfbf7"/>
<!-- windows with white frames and crosses -->
<rect x="210" y="288" width="30" height="30" rx="2" fill="#2f5d86" stroke="#fdfbf7" stroke-width="5"/>
<path d="M 225 288 V 318 M 210 303 H 240" stroke="#fdfbf7" stroke-width="3"/>
<rect x="272" y="288" width="30" height="30" rx="2" fill="#2f5d86" stroke="#fdfbf7" stroke-width="5"/>
<path d="M 287 288 V 318 M 272 303 H 302" stroke="#fdfbf7" stroke-width="3"/>
<!-- door with white frame -->
<rect x="242" y="320" width="28" height="38" rx="2" fill="#7e1f18" stroke="#fdfbf7" stroke-width="4"/>
<circle cx="264" cy="340" r="2.5" fill="#f2c14e"/>
<!-- gable window in the roof -->
<circle cx="256" cy="246" r="8" fill="#f2c14e" stroke="#fdfbf7" stroke-width="3"/>
<!-- dotted icing along the cookie's bottom edge -->
<g fill="#fdfbf7">
<circle cx="196" cy="368" r="3"/>
<circle cx="214" cy="368" r="3"/>
<circle cx="232" cy="368" r="3"/>
<circle cx="250" cy="368" r="3"/>
<circle cx="268" cy="368" r="3"/>
<circle cx="286" cy="368" r="3"/>
<circle cx="304" cy="368" r="3"/>
<circle cx="322" cy="368" r="3"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

+6
View File
@@ -18,6 +18,8 @@ dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.cte
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
@@ -25,6 +27,10 @@ mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
+30
View File
@@ -0,0 +1,30 @@
#!/usr/bin/env bash
# Installs Music Player Randomizer for the current user, with an app menu entry and icon.
# Run it from the unpacked release folder: ./install.sh
set -euo pipefail
cd "$(dirname "$0")"
bin_dir="${XDG_BIN_HOME:-$HOME/.local/bin}"
data_dir="${XDG_DATA_HOME:-$HOME/.local/share}"
binary="$bin_dir/music-player-randomizer"
icon_dir="$data_dir/icons/hicolor/256x256/apps"
mkdir -p "$bin_dir" "$icon_dir" "$data_dir/applications"
install -m755 MusicPlayer.x86_64 "$binary"
install -m644 music-player-randomizer.png "$icon_dir/music-player-randomizer.png"
# The menu entry needs the full path, since ~/.local/bin is not always on PATH
sed "s|^Exec=.*|Exec=$binary|" music-player-randomizer.desktop \
> "$data_dir/applications/music-player-randomizer.desktop"
# Refresh menu and icon caches where the tools exist; most desktops also notice on their own
command -v update-desktop-database > /dev/null \
&& update-desktop-database "$data_dir/applications" || true
command -v gtk-update-icon-cache > /dev/null \
&& gtk-update-icon-cache -q "$data_dir/icons/hicolor" || true
echo "Installed. Start it from your app menu, or run: $binary"
echo "To uninstall, delete:"
echo " $binary"
echo " $data_dir/applications/music-player-randomizer.desktop"
echo " $icon_dir/music-player-randomizer.png"
+8
View File
@@ -0,0 +1,8 @@
[Desktop Entry]
Type=Application
Name=Music Player Randomizer
Comment=Guess the video game soundtrack with your friends
Exec=music-player-randomizer
Icon=music-player-randomizer
Terminal=false
Categories=Game;Audio;
+35
View File
@@ -0,0 +1,35 @@
#!/usr/bin/env bash
# Packages an exported Linux build with its icon, app menu entry and install script.
#
# Usage: linux/package.sh <exported binary> <version> <output dir>
# Creates <output dir>/MusicPlayer_<version>_linux.tar.gz. Godot is taken from $GODOT,
# or `godot` on PATH, to render the icon.
set -euo pipefail
if [[ $# -ne 3 ]]; then
echo "Usage: $0 <exported binary> <version> <output dir>" >&2
exit 1
fi
binary="$(cd "$(dirname "$1")" && pwd)/$(basename "$1")"
version="$2"
out_dir="$(mkdir -p "$3" && cd "$3" && pwd)"
linux_dir="$(cd "$(dirname "$0")" && pwd)"
project_dir="$(dirname "$linux_dir")"
godot_bin="${GODOT:-godot}"
staging="$(mktemp -d)"
trap 'rm -rf "$staging"' EXIT
package_dir="$staging/MusicPlayer"
mkdir -p "$package_dir"
install -m755 "$binary" "$package_dir/MusicPlayer.x86_64"
install -m755 "$linux_dir/install.sh" "$package_dir/install.sh"
install -m644 "$linux_dir/music-player-randomizer.desktop" "$package_dir/"
# Run outside the project so Godot only renders the icon
(cd "$staging" && "$godot_bin" --headless -s "$linux_dir/render_icon.gd" -- \
"$project_dir/icon.svg" "$package_dir/music-player-randomizer.png" 256)
archive="$out_dir/MusicPlayer_${version}_linux.tar.gz"
tar -czf "$archive" -C "$staging" MusicPlayer
echo "Created $archive"
+24
View File
@@ -0,0 +1,24 @@
extends SceneTree
## Renders an SVG to a square PNG, used to package the icon for Linux.
##
## Usage: godot --headless -s render_icon.gd -- <input.svg> <output.png> <size>
func _initialize() -> void:
var args: PackedStringArray = OS.get_cmdline_user_args()
if args.size() != 3:
printerr("Usage: godot --headless -s render_icon.gd -- <input.svg> <output.png> <size>")
quit(1)
return
var svg: String = FileAccess.get_file_as_string(args[0])
var source: Image = Image.new()
# Load at 1x to read the SVG's own size, then scale so the result is <size> pixels
var error: int = source.load_svg_from_string(svg)
if error == OK:
var image: Image = Image.new()
error = image.load_svg_from_string(svg, float(args[2]) / source.get_width())
if error == OK:
error = image.save_png(args[1])
if error != OK:
printerr("Could not render %s to %s (error %d)" % [args[0], args[1], error])
quit(error)
+1
View File
@@ -0,0 +1 @@
uid://b51oybupn7ns7
+57
View File
@@ -0,0 +1,57 @@
class_name ListRow
extends PanelContainer
## One item in a list of names, such as the inspiration list or a sync result column.
##
## Every row has its own background, alternating between two shades, so it is clear
## where an item starts even when its text wraps over several lines.
const EVEN_BACKGROUND: Color = Color(1, 1, 1, 0.05)
const ODD_BACKGROUND: Color = Color(1, 1, 1, 0.11)
static var _even_style: StyleBoxFlat
static var _odd_style: StyleBoxFlat
## The label showing the item's text.
var label: Label
## The item's text.
var text: String:
get:
return label.text
## Creates the row for item number [param row] (counting from 0) in a list.
static func create(
item_text: String,
row: int,
font_size: int,
alignment: HorizontalAlignment = HORIZONTAL_ALIGNMENT_LEFT,
) -> ListRow:
var list_row: ListRow = ListRow.new()
list_row.add_theme_stylebox_override("panel", _style(row % 2 != 0))
list_row.label = Label.new()
list_row.label.add_theme_font_size_override("font_size", font_size)
list_row.label.text = item_text
list_row.label.horizontal_alignment = alignment
list_row.label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
list_row.add_child(list_row.label)
return list_row
# Rows share two styles instead of each creating its own
static func _style(odd: bool) -> StyleBoxFlat:
if _even_style == null:
_even_style = _make_style(EVEN_BACKGROUND)
_odd_style = _make_style(ODD_BACKGROUND)
return _odd_style if odd else _even_style
static func _make_style(background: Color) -> StyleBoxFlat:
var style: StyleBoxFlat = StyleBoxFlat.new()
style.bg_color = background
style.set_corner_radius_all(4)
style.content_margin_left = 8
style.content_margin_right = 8
style.content_margin_top = 2
style.content_margin_bottom = 2
return style
+1
View File
@@ -0,0 +1 @@
uid://c2o2qunso5n8l
+6 -10
View File
@@ -1,4 +1,4 @@
extends Control
extends PopupWindow
@onready
var log_panel: Panel = %LogPanel
@@ -11,19 +11,15 @@ var log_rows: Array
func add_log_row(text: String) -> void:
var log_row: String = get_time() + text
log_rows.append(log_row)
update_log()
log_box.add_child(make_log_label(log_row))
func update_log() -> void:
print("update_log")
Settings.delete_children(log_box)
for row: String in log_rows:
func make_log_label(row: String) -> Label:
var log_label: Label = Label.new()
log_label.set_texture_filter(TextureFilter.TEXTURE_FILTER_NEAREST)
log_label.add_theme_font_size_override("font_size", 20)
log_label.text = row
log_label.autowrap_mode = TextServer.AUTOWRAP_WORD
log_box.add_child(log_label)
return log_label
func get_time() -> String:
var dateTime: Dictionary = Time.get_datetime_dict_from_system()
return "" + str(dateTime.hour) + ":" + str(dateTime.minute) + ":" + str(dateTime.second) + " "
var time: Dictionary = Time.get_time_dict_from_system()
return "%02d:%02d:%02d " % [time.hour, time.minute, time.second]
+8 -33
View File
@@ -1,4 +1,4 @@
extends Control
extends PopupWindow
@onready
var character_panel: Panel = %CharacterPanel
@@ -14,7 +14,6 @@ var close_timer: Timer = %CloseTimer
var can_select_character: bool = false
signal character_selected(file_name: String)
signal new_new_character_selected(file_name: String)
var character: PackedScene = preload("res://character.tscn")
@@ -22,10 +21,6 @@ var character: PackedScene = preload("res://character.tscn")
func _ready() -> void:
close_timer.timeout.connect(_on_close_timeout)
func select_character(file_name: String) -> void:
print("select_character")
character_selected.emit(file_name)
func _on_close_timeout() -> void:
print("_on_close_timeout!")
Settings.character_select_open = false
@@ -51,40 +46,20 @@ func show_grid(player_name: String) -> void:
print("show_grid")
title_label.text = "Select character for " + player_name
close_timer.stop()
self.visible = true
open()
can_select_character = true
Settings.character_select_open = true
func close() -> void:
super()
Settings.character_select_open = false
func _on_character_selected(texture: ImageTexture) -> void:
print("_on_character_selected")
# Not close(): character_select_open stays true until close_timer fires, so the click
# that picked the character does not also select a song behind it
self.visible = false
if can_select_character:
new_new_character_selected.emit(texture)
close_timer.start()
can_select_character = false
func _input(event: InputEvent) -> void:
if self.visible == true:
if event is InputEventMouseButton && event.is_pressed():
var evLocal: InputEvent = make_input_local(event)
if !Rect2(Vector2(0, 0), character_panel.size).has_point(evLocal.position):
self.visible = false
Settings.character_select_open = false
func load_characters_local() -> void:
var characters: DirAccess = DirAccess.open("res://characters/")
if characters:
characters.list_dir_begin()
var file_name: String = characters.get_next()
while file_name != "":
file_name = file_name.replace('.import', '') # <--- remove the .import
if file_name.ends_with(".png"):
var texture: Resource = load("res://characters/" + file_name)
var texture_btn: TextureButton = TextureButton.new()
character_grid.add_child(texture_btn)
texture_btn.custom_minimum_size = Vector2(80, 40)
texture_btn.ignore_texture_size = true
texture_btn.stretch_mode = TextureButton.STRETCH_KEEP_ASPECT
texture_btn.texture_normal = texture
texture_btn.pressed.connect(select_character.bind(file_name))
file_name = characters.get_next()
+6 -3
View File
@@ -1,24 +1,27 @@
[gd_scene load_steps=3 format=3 uid="uid://cdy4kvemwaiom"]
[ext_resource type="Script" uid="uid://cvffg87wr6r3y" path="res://new_character_select.gd" id="1_h4tdp"]
[ext_resource type="Theme" path="res://popup_theme.tres" id="popup_theme"]
[sub_resource type="LabelSettings" id="LabelSettings_p4iny"]
font_size = 25
[node name="Control" type="Control"]
[node name="Control" type="Control" node_paths=PackedStringArray("panel")]
theme = ExtResource("popup_theme")
layout_mode = 3
anchors_preset = 0
script = ExtResource("1_h4tdp")
panel = NodePath("CharacterPanel")
[node name="CharacterPanel" type="Panel" parent="."]
unique_name_in_owner = true
layout_mode = 0
offset_right = 1260.0
offset_right = 1290.0
offset_bottom = 665.0
[node name="VBoxContainer" type="VBoxContainer" parent="CharacterPanel"]
layout_mode = 0
offset_right = 1263.0
offset_right = 1290.0
offset_bottom = 659.0
size_flags_horizontal = 3
-4
View File
@@ -31,10 +31,6 @@ func get_preloaded_songs() -> int:
func should_preload_more_songs() -> bool:
return number_of_tracks_to_preload > get_preloaded_songs()
func set_last_played_answered() -> void:
if song_object_array[latest_played_song].has_played && !song_object_array[latest_played_song].is_answered:
song_object_array[latest_played_song].is_answered = true
func song_is_answered(song_no: int = currently_playing_song) -> void:
# Nothing is playing yet (-1); a negative index would answer the last, unplayed song
if song_no < 0 || song_no >= song_object_array.size():
+21
View File
@@ -0,0 +1,21 @@
[gd_resource type="Theme" format=3]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_popup"]
bg_color = Color(0.180392, 0.180392, 0.180392, 1)
border_width_left = 2
border_width_top = 2
border_width_right = 2
border_width_bottom = 2
border_color = Color(0.36, 0.36, 0.36, 1)
corner_radius_top_left = 6
corner_radius_top_right = 6
corner_radius_bottom_right = 6
corner_radius_bottom_left = 6
content_margin_left = 12.0
content_margin_top = 12.0
content_margin_right = 12.0
content_margin_bottom = 12.0
[resource]
Panel/styles/panel = SubResource("StyleBoxFlat_popup")
PopupPanel/styles/panel = SubResource("StyleBoxFlat_popup")
+37
View File
@@ -0,0 +1,37 @@
class_name PopupWindow
extends Control
## A window shown on top of the main window, such as Settings or About.
##
## Clicking outside [member panel] closes it. Popups that need to prepare or clean up
## override [method open] or [method close] and call [code]super()[/code].
## The visible part of the popup. Clicks outside it close the popup.
@export var panel: Control
## Shows the popup.
func open() -> void:
visible = true
## Hides the popup.
func close() -> void:
visible = false
## Opens the popup if it is closed and closes it if it is open.
func toggle() -> void:
if visible:
close()
else:
open()
func _input(event: InputEvent) -> void:
if !visible || !(event is InputEventMouseButton) || !event.pressed:
return
var click: InputEventMouseButton = panel.make_input_local(event)
if !Rect2(Vector2.ZERO, panel.size).has_point(click.position):
close()
# The click only closes the popup; it must not also press what is behind it
get_viewport().set_input_as_handled()
+1
View File
@@ -0,0 +1 @@
uid://dwd4dcbujpv6p
-5
View File
@@ -1,5 +0,0 @@
class_name ProgressResponse
extends Node
var progress: String
var total_time: String
-1
View File
@@ -1 +0,0 @@
uid://e74iuu78stc3
+4 -1
View File
@@ -17,7 +17,7 @@ compatibility/default_parent_skeleton_in_mesh_instance_3d=true
config/name="MusicPlayer"
config/version="1.9.0"
run/main_scene="res://MainWindow.tscn"
config/features=PackedStringArray("4.7", "Forward Plus")
config/features=PackedStringArray("4.7", "GL Compatibility")
config/icon="res://icon.svg"
[autoload]
@@ -61,6 +61,9 @@ ui_select={
[rendering]
renderer/rendering_method="gl_compatibility"
renderer/rendering_method.mobile="gl_compatibility"
textures/vram_compression/import_s3tc_bptc=true
textures/vram_compression/import_etc2_astc=true
textures/default_filters/use_nearest_mipmap_filter=true
+4 -1
View File
@@ -1,8 +1,10 @@
[gd_scene load_steps=2 format=3 uid="uid://bd1by80q1v27e"]
[ext_resource type="Script" uid="uid://rolypd0cqd6h" path="res://shortcuts_window.gd" id="1_12pxi"]
[ext_resource type="Theme" path="res://popup_theme.tres" id="popup_theme"]
[node name="ShorcutsControl" type="Control"]
[node name="ShorcutsControl" type="Control" node_paths=PackedStringArray("panel")]
theme = ExtResource("popup_theme")
custom_minimum_size = Vector2(700, 700)
layout_mode = 3
anchors_preset = 15
@@ -11,6 +13,7 @@ anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_12pxi")
panel = NodePath("ShortcutsPanel")
[node name="ShortcutsPanel" type="Panel" parent="."]
unique_name_in_owner = true
+1 -14
View File
@@ -1,4 +1,4 @@
extends Control
extends PopupWindow
@onready
var shortcuts_vbox: VBoxContainer = %shortcutsVBox
@@ -29,22 +29,11 @@ var shortcuts_dic: Dictionary = {
#Color(0.302, 0.302, 0.302)
#Color(0.259, 0.259, 0.259)
func _input(event: InputEvent) -> void:
if self.visible == true:
if shortcuts_panel == null:
return
if event is InputEventMouseButton && event.is_pressed():
var evLocal: InputEvent = make_input_local(event)
if !Rect2(Vector2(0, 0), shortcuts_panel.size).has_point(evLocal.position):
self.visible = false
func _ready() -> void:
shortcuts_dic.sort()
Settings.delete_children(shortcuts_vbox)
var title: Label = Label.new()
title.set_texture_filter(TextureFilter.TEXTURE_FILTER_NEAREST)
title.add_theme_font_size_override("font_size", 20)
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
title.text = "Shortcuts"
@@ -57,7 +46,6 @@ func _ready() -> void:
hbox.alignment = BoxContainer.ALIGNMENT_CENTER
var label: Label = Label.new()
label.set_texture_filter(TextureFilter.TEXTURE_FILTER_NEAREST)
label.add_theme_font_size_override("font_size", 20)
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
label.text = shortcut
@@ -65,7 +53,6 @@ func _ready() -> void:
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
var label2: Label = Label.new()
label2.set_texture_filter(TextureFilter.TEXTURE_FILTER_NEAREST)
label2.add_theme_font_size_override("font_size", 20)
label2.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
label2.text = shortcuts_dic[shortcut]
-10
View File
@@ -1,10 +0,0 @@
class_name SyncResponse
extends Node
var soundtracks_added: Array
var soundtracks_re_added: Array
var soundtracks_changed_title: Array
var soundtracks_changed_content: Array
var soundtracks_removed: Array
var catched_errors: Array
var total_time: String
-1
View File
@@ -1 +0,0 @@
uid://dtfsuob2aidl7
+15 -39
View File
@@ -1,4 +1,4 @@
extends Control
extends PopupWindow
@onready
var sync_panel: Panel = %SyncPanel
@@ -60,9 +60,6 @@ var sync_timer: Timer = %SyncTimer
@onready
var restart_sync_button: Button = %RestartSyncButton
@onready
var first_separator: VSeparator = %VSeparator6
var changes: bool = false
var has_synced: bool = false
@@ -75,13 +72,6 @@ func _ready() -> void:
restart_sync_button.pressed.connect(start_sync)
restart_sync_button.visible = false
func _input(event: InputEvent) -> void:
if self.visible == true:
if event is InputEventMouseButton && event.is_pressed():
var evLocal: InputEvent = make_input_local(event)
if !Rect2(Vector2(0, 0), sync_panel.size).has_point(evLocal.position):
self.visible = false
func show_section_if_has_data(data: Variant, vbox: VBoxContainer, separator: VSeparator, header_text: String) -> bool:
if data != null && data.size() > 0:
vbox.visible = true
@@ -127,7 +117,7 @@ func on_sync_finished(data_received: Dictionary) -> void:
if show_section_if_has_data(data_received.soundtracks_added, soundtracks_added_vbox, soundtracks_added_separator, "Added"):
changes = true
if show_section_if_has_data(data_received.soundtracks_re_added, soundtracks_readded_vbox, soundtracks_readded_separator, "ReAdded"):
if show_section_if_has_data(data_received.soundtracks_re_added, soundtracks_readded_vbox, soundtracks_readded_separator, "Re-added"):
changes = true
if data_received.soundtracks_changed_title != null && data_received.soundtracks_changed_title.size() > 0:
@@ -136,10 +126,9 @@ func on_sync_finished(data_received: Dictionary) -> void:
make_header_label(soundtracks_changed_title_vbox, "Changed Title")
var row: int = 0
for old_soundtrack_title: String in data_received.soundtracks_changed_title:
var label: Label = make_soundtrack_label(old_soundtrack_title + "\nchanged title to\n" + data_received.soundtracks_changed_title[old_soundtrack_title])
if row % 2 != 0:
label.add_theme_color_override("font_color", Color(1, 0.5, 0))
soundtracks_changed_title_vbox.add_child(label)
var new_soundtrack_title: String = data_received.soundtracks_changed_title[old_soundtrack_title]
soundtracks_changed_title_vbox.add_child(make_soundtrack_row(
old_soundtrack_title + "\nchanged title to\n" + new_soundtrack_title, row))
row += 1
soundtracks_changed_title_vbox.visible = true
@@ -151,11 +140,9 @@ func on_sync_finished(data_received: Dictionary) -> void:
if data_received.catched_errors != null && data_received.catched_errors.size() > 0:
catched_errors_vbox.visible = true
make_list(data_received.catched_errors, catched_errors_vbox, "CatchedErrors")
make_list(data_received.catched_errors, catched_errors_vbox, "Errors")
# Hide separators that don't have a visible section after them
# The structure is: RestartSyncButton -> VSeparator6 -> VBox -> Separator -> VBox -> Separator -> ...
# Hide VSeparator6 if there are no visible sections
# Separators go only between visible columns, not after the last one
var visible_sections: Array = []
if soundtracks_added_vbox.visible:
visible_sections.append("added")
@@ -168,8 +155,6 @@ func on_sync_finished(data_received: Dictionary) -> void:
if soundtracks_removed_vbox.visible:
visible_sections.append("removed")
first_separator.visible = visible_sections.size() > 1
# Show section separators only if there's a visible section after them
for i in range(visible_sections.size()):
var section = visible_sections[i]
@@ -202,20 +187,18 @@ func list_visible(vbox: VBoxContainer, seperator: VSeparator) -> void:
func make_header_label(vbox: VBoxContainer, text: String) -> void:
var label: Label = Label.new()
label.set_texture_filter(TextureFilter.TEXTURE_FILTER_NEAREST)
label.add_theme_font_size_override("font_size", 20)
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
label.add_theme_color_override("font_color", Color(1, 0.5, 0))
label.text = text
vbox.add_child(label)
func make_soundtrack_label(text: String) -> Label:
var label: Label = Label.new()
label.set_texture_filter(TextureFilter.TEXTURE_FILTER_NEAREST)
label.add_theme_font_size_override("font_size", 18)
label.text = text
label.autowrap_mode = TextServer.AUTOWRAP_OFF
return label
# Items alternate between white and orange text as well as background shade
func make_soundtrack_row(text: String, row: int) -> ListRow:
var list_row: ListRow = ListRow.create(text, row, 18, HORIZONTAL_ALIGNMENT_CENTER)
if row % 2 != 0:
list_row.label.add_theme_color_override("font_color", Color(1, 0.5, 0))
return list_row
func reset_sync_result() -> void:
sync_progress_bar.value = 0
@@ -233,18 +216,12 @@ func reset_sync_result() -> void:
soundtracks_removed_vbox.visible = false
soundtracks_removed_separator.visible = false
catched_errors_vbox.visible = false
first_separator.visible = false
func make_list(list: Array, vbox: VBoxContainer, text: String) -> void:
Settings.delete_children(vbox)
make_header_label(vbox, text)
var row: int = 0
for soundtrack: String in list:
var label: Label = make_soundtrack_label(soundtrack)
if row % 2 != 0:
label.add_theme_color_override("font_color", Color(1, 0.5, 0))
vbox.add_child(label)
row += 1
for row: int in list.size():
vbox.add_child(make_soundtrack_row(list[row], row))
func show_sync_result() -> void:
sync_title_label.text = "Sync Result"
@@ -259,4 +236,3 @@ func show_sync_progress() -> void:
sync_hbox.visible = false
catched_errors_vbox.visible = false
restart_sync_button.visible = false
first_separator.visible = false
+22
View File
@@ -13,6 +13,7 @@ var requested_methods: Array[int] = []
var _responses: Dictionary = {}
var _successes_left: Dictionary = {}
var _held_paths: Dictionary = {}
var _previous_transport: HttpTransport
@@ -49,6 +50,20 @@ func fail_after(path: String, successes: int) -> void:
_successes_left[path] = successes
## Holds back responses to [param path] until [method release] is called, like a slow
## server.
func hold(path: String) -> void:
_held_paths[path] = []
## Delivers the responses held back for [param path] and stops holding it.
func release(path: String) -> void:
var waiting: Array = _held_paths.get(path, [])
_held_paths.erase(path)
for pending: Callable in waiting:
pending.call()
## Returns how many times [param path] has been requested.
func count(path: String) -> int:
return requested_paths.count(path)
@@ -58,6 +73,13 @@ func request(url: String, method: int, on_complete: Callable) -> void:
var path: String = url.trim_prefix(Settings.default_path)
requested_paths.append(path)
requested_methods.append(method)
if _held_paths.has(path):
_held_paths[path].append(_answer.bind(path, on_complete))
return
_answer(path, on_complete)
func _answer(path: String, on_complete: Callable) -> void:
if _successes_left.has(path):
if _successes_left[path] == 0:
on_complete.call(503, PackedByteArray())
+7
View File
@@ -36,12 +36,19 @@ func before_test() -> void:
_server.respond_bytes("/music/reset", PackedByteArray())
_server.respond_bytes("/music/played?song=0", PackedByteArray())
_before_window_starts()
_runner = scene_runner("res://MainWindow.tscn")
_window = _runner.scene()
# The debug build lowers the winning score on startup; keep it out of reach
Settings.winning_score = 20
## Override to change the fake server before the window starts and sends its first
## requests.
func _before_window_starts() -> void:
pass
func after_test() -> void:
# Let nodes queued for deletion by the window be freed before orphans are counted
await await_idle_frame()
+23
View File
@@ -226,3 +226,26 @@ func test_show_answer_before_first_song_does_not_reveal_next_song() -> void:
assert_str((_window.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 crono: PlayerObject = _add_player("Crono")
await _play_first_song()
crono.add_point()
crono.minus_point()
assert_int(log_box.get_child_count()).is_equal(2)
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] = _window.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"))
@@ -0,0 +1,41 @@
extends MainWindowTestSuite
## Startup with a slow /music/list response.
func _before_window_starts() -> void:
_server.respond_json("/music/list", [
{"SongNo": 0, "Soundtrack": "Chrono Trigger", "Song": "Corridors of Time"},
])
_next_song_is({"SongNo": 1, "Soundtrack": "Final Fantasy VI", "Song": "Dancing Mad"})
_server.hold("/music/list")
func test_preloaded_song_survives_a_slow_song_list() -> void:
_server.release("/music/list")
# The song already on the server, then the preloaded one
assert_int(Playlist.song_object_array.size()).is_equal(2)
assert_str(Playlist.song_object_array[1].song_title).is_equal("Dancing Mad")
assert_int(Playlist.get_preloaded_songs()).is_equal(1)
func test_next_plays_the_preloaded_song_after_a_slow_song_list() -> void:
_server.release("/music/list")
await _play_first_song()
assert_int(Playlist.currently_playing_song).is_equal(1)
func test_failed_song_list_is_retried_before_preloading() -> void:
_window.song_fetch_retry_seconds = 0.05
_server.respond_json("/music/list", [], 503)
_server.release("/music/list")
assert_int(_server.count("/music/rand")).is_equal(0)
_server.respond_json("/music/list", [])
await await_millis(200)
assert_int(_server.count("/music/rand")).is_equal(1)
assert_int(Playlist.get_preloaded_songs()).is_equal(1)
@@ -0,0 +1 @@
uid://sfwrqidmuj04
+126
View File
@@ -0,0 +1,126 @@
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.
const POPUP_NODES: Array[String] = [
"Search", "SyncWindow", "SettingsWindow", "QRWindow", "NewCharacterSelect",
"ShortcutsWindow", "AboutWindow", "WinnerWindow", "Log",
]
func _popup(node_name: String) -> PopupWindow:
return _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()
## A point on screen outside the popup's panel.
func _outside(popup: PopupWindow) -> Vector2:
var rect: Rect2 = popup.panel.get_global_rect()
return Vector2(rect.end.x + 5, rect.end.y + 5) if rect.end.x < 1900 \
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_backdrop_covers_the_whole_window() -> void:
_popup("SettingsWindow").open()
assert_vector(_backdrop().get_global_rect().size).is_equal(_window.get_viewport_rect().size)
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)
func test_every_popup_shows_backdrop_and_closes_on_outside_click() -> void:
for node_name: String in POPUP_NODES:
var popup: PopupWindow = _popup(node_name)
popup.open()
await _runner.simulate_frames(1)
assert_bool(_backdrop().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(
"%s: backdrop still shown" % node_name).is_false()
func test_click_inside_popup_keeps_it_open() -> void:
var settings: PopupWindow = _popup("SettingsWindow")
settings.open()
await _runner.simulate_frames(1)
await _click_at(settings.panel.get_global_rect().get_center())
assert_bool(settings.visible).is_true()
func test_closing_click_does_not_press_button_behind_popup() -> void:
var settings: PopupWindow = _popup("SettingsWindow")
var reset_button: Button = _window.get_node("ResetPlaylistButton")
settings.open()
await _runner.simulate_frames(1)
assert_bool(settings.panel.get_global_rect().intersects(reset_button.get_global_rect())) \
.is_false()
await _click_at(reset_button.get_global_rect().get_center())
assert_bool(settings.visible).is_false()
assert_int(_server.count("/music/reset")).is_equal(0)
func test_button_works_again_after_popup_closed() -> void:
var reset_button: Button = _window.get_node("ResetPlaylistButton")
await _click_at(reset_button.get_global_rect().get_center())
assert_int(_server.count("/music/reset")).is_equal(1)
func test_closing_search_clears_the_search() -> void:
var search: PopupWindow = _popup("Search")
search.open()
var search_bar: TextEdit = search.get_node("Searchbar")
search_bar.text = "mario"
search.close()
assert_str(search_bar.text).is_empty()
func test_closing_character_select_allows_song_clicks_again() -> void:
var character_select: PopupWindow = _popup("NewCharacterSelect")
character_select.show_grid("Crono")
assert_bool(Settings.character_select_open).is_true()
character_select.close()
assert_bool(Settings.character_select_open).is_false()
func test_toggle_opens_and_closes() -> void:
var shortcuts: PopupWindow = _popup("ShortcutsWindow")
shortcuts.toggle()
assert_bool(shortcuts.visible).is_true()
shortcuts.toggle()
assert_bool(shortcuts.visible).is_false()
+1
View File
@@ -0,0 +1 @@
uid://kcnjdw4ke7hd
+65
View File
@@ -0,0 +1,65 @@
extends MainWindowTestSuite
## Winning turns statistics off until a new game starts.
var _crono: PlayerObject
func before_test() -> void:
super()
_crono = _add_player("Crono")
await _play_first_song()
Settings.winning_score = 1
func _stats_checkbox() -> CheckButton:
return _window.get_node("SettingsWindow").get_node("%AddToDatabaseCheckButton")
func test_winning_turns_statistics_off() -> void:
await _give_point_with_keybind(1)
assert_bool(Settings.add_to_stats).is_false()
func test_resetting_points_turns_statistics_back_on() -> void:
await _give_point_with_keybind(1)
_press("ResetPointsButton")
assert_bool(Settings.add_to_stats).is_true()
func test_statistics_turned_off_by_hand_stay_off_after_reset() -> void:
Settings.add_to_stats = false
await _give_point_with_keybind(1)
_press("ResetPointsButton")
assert_bool(Settings.add_to_stats).is_false()
func test_settings_checkbox_shows_statistics_off_after_win() -> void:
await _give_point_with_keybind(1)
_press("SettingsButton")
assert_bool(_stats_checkbox().button_pressed).is_false()
func test_songs_after_a_win_are_not_counted() -> void:
await _give_point_with_keybind(1)
var played_before: int = _server.count("/music/played?song=0")
_press("ShowAnswerButton")
assert_int(_server.count("/music/played?song=0")).is_equal(played_before)
func test_winning_score_cannot_go_below_one() -> void:
_press("SettingsButton")
var lower_button: Button = _window.get_node("SettingsWindow").get_node("%LowerButton")
for i: int in 3:
lower_button.pressed.emit()
assert_int(Settings.winning_score).is_equal(1)
@@ -0,0 +1 @@
uid://d20y2gtespbou
+56 -1
View File
@@ -113,7 +113,7 @@ func test_changed_titles_list_old_and_new_title() -> void:
var vbox: Control = _node("SoundtracksChangedTitleVBox")
assert_bool(vbox.visible).is_true()
assert_str((vbox.get_child(1) as Label).text).is_equal(
assert_str((vbox.get_child(1) as ListRow).text).is_equal(
"FF7\nchanged title to\nFinal Fantasy VII")
@@ -143,3 +143,58 @@ func test_reopening_after_sync_shows_result_without_syncing_again() -> void:
assert_int(_server.count("/sync")).is_equal(1)
assert_str(_node("SyncTitleLabel").text).is_equal("Sync Result")
func test_errors_section_has_errors_header() -> void:
_runner.invoke("start_sync")
var result: Dictionary = _empty_result()
result.catched_errors = ["Could not read tags in broken.mp3"]
_finish_sync(result)
assert_str((_node("CatchedErrorsVBox").get_child(0) as Label).text).is_equal("Errors")
func test_long_names_wrap_instead_of_widening_the_window() -> void:
_runner.invoke("start_sync")
var long_name: String = "The Legend of Zelda: Breath of the Wild Original Soundtrack"
var result: Dictionary = _empty_result()
result.soundtracks_added = [long_name]
result.soundtracks_re_added = [long_name]
result.soundtracks_changed_content = [long_name]
result.soundtracks_removed = [long_name]
result.soundtracks_changed_title = {long_name: long_name}
result.catched_errors = [long_name + " could not be read"]
_finish_sync(result)
await _runner.simulate_frames(2)
# The minimum width is what the content needs; wider than the panel it would stick out
var panel_width: float = (_window.get_node("SyncPanel") as Control).size.x
assert_float(_node("SyncHBoxContainer").get_combined_minimum_size().x) \
.is_less(panel_width)
assert_float(_node("CatchedErrorsVBox").get_combined_minimum_size().x) \
.is_less(panel_width)
func test_visible_columns_share_the_width_equally() -> void:
_runner.invoke("start_sync")
var result: Dictionary = _empty_result()
result.soundtracks_added = ["Chrono Cross", "Xenoblade Chronicles X Original Soundtrack"]
result.soundtracks_removed = ["Bad Rip"]
_finish_sync(result)
await _runner.simulate_frames(2)
assert_float(_node("SoundtracksAddedVBox").size.x).is_equal_approx(
_node("SoundtracksRemovedVBox").size.x, 1.0)
func test_column_headers() -> void:
_runner.invoke("start_sync")
var result: Dictionary = _empty_result()
result.soundtracks_re_added = ["Super Metroid"]
_finish_sync(result)
assert_str((_node("SoundtracksReAddedVBox").get_child(0) as Label).text).is_equal("Re-added")
+26
View File
@@ -0,0 +1,26 @@
extends GdUnitTestSuite
func _background(list_row: ListRow) -> Color:
return (list_row.get_theme_stylebox("panel") as StyleBoxFlat).bg_color
func test_row_shows_text() -> void:
var list_row: ListRow = auto_free(ListRow.create("Chrono Trigger", 0, 20))
assert_str(list_row.text).is_equal("Chrono Trigger")
func test_neighbouring_rows_have_different_backgrounds() -> void:
var first: ListRow = auto_free(ListRow.create("Chrono Trigger", 0, 20))
var second: ListRow = auto_free(ListRow.create("Chrono Cross", 1, 20))
var third: ListRow = auto_free(ListRow.create("Xenogears", 2, 20))
assert_that(_background(first)).is_not_equal(_background(second))
assert_that(_background(first)).is_equal(_background(third))
func test_long_text_wraps() -> void:
var list_row: ListRow = auto_free(ListRow.create("Castlevania: Symphony of the Night", 0, 20))
assert_int(list_row.label.autowrap_mode).is_equal(TextServer.AUTOWRAP_WORD_SMART)
+1
View File
@@ -0,0 +1 @@
uid://bouligcywd5se
+19
View File
@@ -117,3 +117,22 @@ func test_every_player_gets_a_different_id() -> void:
ids[PlayerObject.new("Player %d" % i).id] = true
assert_int(ids.size()).is_equal(100)
func test_winning_score_of_one_is_won_with_first_point() -> void:
Settings.winning_score = 1
monitor_signals(_player, false)
_player.add_point()
await assert_signal(_player).is_emitted("winner_triggerd")
func test_first_point_is_match_point_when_winning_score_is_two() -> void:
Settings.winning_score = 2
monitor_signals(_player, false)
_player.add_point()
await assert_signal(_player).is_emitted("match_point_triggerd")
assert_bool(_player.is_first_point).is_false()
+6 -10
View File
@@ -1,4 +1,4 @@
extends Control
extends PopupWindow
@onready
var winner_panel: Panel = %WinnerPanel
@@ -23,6 +23,11 @@ func show_winner(winner: PlayerObject) -> void:
winner_label.text = winner.player_name + " won with " + str(winner.player_score) + \
" points!!"
winner_picture.texture = winner.character
# The "no character" placeholder is pixel art, which blurs when scaled up smoothly
if winner.character.resource_path == PlayerObject.NO_CHARACTER_PATH:
winner_picture.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
else:
winner_picture.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR
winner_picture.custom_minimum_size = Vector2(480, 200)
winner_picture.expand_mode = TextureRect.EXPAND_FIT_WIDTH_PROPORTIONAL
winner_picture.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
@@ -50,13 +55,4 @@ func show_winner(winner: PlayerObject) -> void:
placing += 1
func hide_window() -> void:
visible = false
func _input(event: InputEvent) -> void:
if visible:
if event is InputEventMouseButton and event.is_pressed():
var ev_local: InputEvent = make_input_local(event)
if !Rect2(Vector2(0, 0), winner_panel.size).has_point(ev_local.position):
visible = false