diff --git a/main.gd b/main.gd new file mode 100644 index 0000000..05307a6 --- /dev/null +++ b/main.gd @@ -0,0 +1,69 @@ +extends Control + +enum Menu { + LOADER, + MAIN, + LOAD_SAVE, + WORLD_MAP, + FIELD_MAP, + PARTY, + SHOP, + BATTLE, + DEBUG, + DEBUG_AUDIO_SYSTEM, + DEBUG_BATTLE_SPRITES, +} +var menus = {} +var active_menu_stack = [] + +var is_rom_loaded := false +func _rom_loaded() -> void: + menus[Menu.PARTY] = preload('res://widgets/PartyMenu.tscn').instance() + menus[Menu.WORLD_MAP] = preload('res://test/worldmap_system.tscn').instance() + menus[Menu.DEBUG] = preload('res://test/debug_menu.tscn').instance() + menus[Menu.DEBUG].connect('pressed_worldmap', self, 'push_menu', [Menu.WORLD_MAP]) + menus[Menu.DEBUG].connect('pressed_party', self, 'push_menu', [Menu.PARTY]) + menus[Menu.DEBUG].connect('pressed_battle_sprites', self, 'push_menu', [Menu.DEBUG_BATTLE_SPRITES]) + menus[Menu.DEBUG].connect('pressed_audio', self, 'push_menu', [Menu.DEBUG_AUDIO_SYSTEM]) + menus[Menu.DEBUG_AUDIO_SYSTEM] = preload('res://test/audio_system.tscn').instance() + menus[Menu.DEBUG_BATTLE_SPRITES] = preload('res://test/battle_sprites.tscn').instance() + is_rom_loaded = true + +func _ready() -> void: + if OS.get_name() != 'HTML5' or !OS.has_feature('JavaScript'): + menus[Menu.LOADER] = preload('res://widgets/RomSelect.tscn').instance() + else: + menus[Menu.LOADER] = preload('res://widgets/WebFileSelect.tscn').instance() + push_menu(Menu.LOADER) + + RomLoader.connect('rom_loaded', self, '_rom_loaded') + menus[Menu.LOADER].connect('continue_pressed', self, 'push_debug_menu') + Common.update_window_scale() + +func push_debug_menu(): + push_menu(Menu.DEBUG) + +func push_menu(menu_type): + if len(active_menu_stack) > 0: + if active_menu_stack[-1] == menu_type: + return + remove_child(menus[active_menu_stack[-1]]) + active_menu_stack.append(menu_type) + add_child(menus[menu_type]) + +func pop_menu(): + if len(active_menu_stack) > 0: + remove_child(menus[active_menu_stack.pop_back()]) + if len(active_menu_stack) > 0: + add_child(menus[active_menu_stack[-1]]) + else: + push_menu(Menu.LOADER) + +func _input(event: InputEvent) -> void: + if event is InputEventKey and event.pressed: + match event.scancode: + KEY_BACKSPACE: + pop_menu() + KEY_D: + if is_rom_loaded: + push_menu(Menu.DEBUG) diff --git a/main.tscn b/main.tscn new file mode 100644 index 0000000..815731c --- /dev/null +++ b/main.tscn @@ -0,0 +1,17 @@ +[gd_scene load_steps=4 format=2] + +[ext_resource path="res://theme/menu_theme.tres" type="Theme" id=1] +[ext_resource path="res://widgets/ColorMenu.tscn" type="PackedScene" id=4] +[ext_resource path="res://main.gd" type="Script" id=5] + +[node name="main" type="Control"] +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_right = -640.0 +margin_bottom = -360.0 +theme = ExtResource( 1 ) +script = ExtResource( 5 ) + +[node name="ColorMenu" parent="." instance=ExtResource( 4 )] +margin_left = 384.0 +margin_right = 469.0 diff --git a/main_menu.tscn b/main_menu.tscn deleted file mode 100644 index fcf8127..0000000 --- a/main_menu.tscn +++ /dev/null @@ -1,62 +0,0 @@ -[gd_scene load_steps=7 format=2] - -[ext_resource path="res://box.tscn" type="PackedScene" id=1] -[ext_resource path="res://theme/menu_theme.tres" type="Theme" id=2] -[ext_resource path="res://widgets/ColorMenu.tscn" type="PackedScene" id=3] -[ext_resource path="res://theme/border_imagetexture.tres" type="Texture" id=4] -[ext_resource path="res://widgets/PartyMenu.tscn" type="PackedScene" id=5] -[ext_resource path="res://widgets/RomSelect.tscn" type="PackedScene" id=6] - -[node name="main_menu" type="Control"] -anchor_right = 1.0 -anchor_bottom = 1.0 -margin_right = -640.0 -margin_bottom = -360.0 -theme = ExtResource( 2 ) - -[node name="party_menu" parent="." instance=ExtResource( 5 )] -visible = false -anchor_left = 1.0 -anchor_top = 1.0 -anchor_right = 1.0 -anchor_bottom = 1.0 -margin_left = -360.0 -margin_top = -240.0 -margin_right = 0.0 -margin_bottom = 0.0 - -[node name="MarginContainer" type="MarginContainer" parent="."] -margin_right = 70.0 -margin_bottom = 32.0 -size_flags_horizontal = 0 -size_flags_vertical = 0 - -[node name="box2" parent="MarginContainer" instance=ExtResource( 1 )] -material = null -margin_right = 89.0 -margin_bottom = 37.0 -size_flags_horizontal = 3 -size_flags_vertical = 3 -texture = ExtResource( 4 ) - -[node name="MarginContainer2" type="MarginContainer" parent="MarginContainer"] -margin_right = 89.0 -margin_bottom = 37.0 -custom_constants/margin_right = 4 -custom_constants/margin_top = 4 -custom_constants/margin_left = 4 -custom_constants/margin_bottom = 4 - -[node name="Label" type="Label" parent="MarginContainer/MarginContainer2"] -margin_left = 4.0 -margin_top = 4.0 -margin_right = 85.0 -margin_bottom = 33.0 -text = "Load Save File -From here" - -[node name="RomSelect" parent="." instance=ExtResource( 6 )] - -[node name="ColorMenu" parent="." instance=ExtResource( 3 )] -margin_left = 384.0 -margin_right = 469.0 diff --git a/project.godot b/project.godot index 48c2564..10c4365 100644 --- a/project.godot +++ b/project.godot @@ -15,7 +15,7 @@ _global_script_class_icons={ [application] config/name="ChocolateBird" -run/main_scene="res://main_menu.tscn" +run/main_scene="res://main.tscn" config/use_custom_user_dir=true config/icon="res://icon.png" diff --git a/scripts/loaders/RomLoader.gd b/scripts/loaders/RomLoader.gd index 792f883..b6022f2 100644 --- a/scripts/loaders/RomLoader.gd +++ b/scripts/loaders/RomLoader.gd @@ -1,5 +1,7 @@ extends Node +signal rom_loaded + const STRUCT := preload('res://scripts/struct.gd') const STRUCT_SNES := preload('res://scripts/loaders/snes/structs.gd') var structdefs := {} @@ -12,9 +14,6 @@ const psx_ff5_productcodes = [ 'SCPS_452.14', # JP original, untested ] -var ROM_filename := 'FF5_SCC_WepTweaks_Inus_Dash.sfc' # 'Final Fantasy V (Japan).sfc' -var GBA_filename := '2564 - Final Fantasy V Advance (U)(Independent).gba' - var rom_snes := File.new() var snes_data := {} var snes_bytes: PoolByteArray @@ -64,27 +63,31 @@ func load_snes_structs(buffer: StreamPeerBuffer) -> Dictionary: func load_snes_audio_thread(data_and_buffer: Array): SoundLoader.parse_rom(data_and_buffer[0], data_and_buffer[1]) -func load_snes_rom(filename: String): +func load_snes_rom_from_bytes(bytes: PoolByteArray) -> void: + self.snes_bytes = bytes + self.snes_buffer = StreamPeerBuffer.new() + self.snes_buffer.data_array = bytes + + self.snes_data = load_snes_structs(self.snes_buffer) + #print(snes_data.job_levels) + # Give this its own buffer if threaded, avoid file pointer conflicts + self.load_snes_audio_thread([self.snes_data, self.snes_buffer]) + # var _thread_error = thread.start(self, 'load_snes_audio_thread', [self.snes_data, self.snes_buffer.duplicate()]) + StringLoader.load_snes_rom(self.snes_buffer, true) + SpriteLoader.load_from_structs(self.snes_data) + SpriteLoader.load_enemy_battle_sprites(self.snes_data, self.snes_buffer) + SpriteLoader.load_battle_bgs(self.snes_data, self.snes_buffer) + MapLoader.load_snes_rom(self.snes_buffer) + emit_signal('rom_loaded') + +func load_snes_rom(filename: String) -> void: var error := rom_snes.open(filename, File.READ) if error == OK: # Copy entire SNES ROM to a buffer for StreamPeerBuffer usage. # Unfortunately, the File API is different and slightly worse than the StreamPeer API. var rom_size := rom_snes.get_len() var bytes := rom_snes.get_buffer(rom_size) - self.snes_bytes = bytes - self.snes_buffer = StreamPeerBuffer.new() - self.snes_buffer.data_array = bytes - - self.snes_data = load_snes_structs(self.snes_buffer) - #print(snes_data.job_levels) - # Give this its own buffer if threaded, avoid file pointer conflicts - self.load_snes_audio_thread([self.snes_data, self.snes_buffer]) - # var _thread_error = thread.start(self, 'load_snes_audio_thread', [self.snes_data, self.snes_buffer.duplicate()]) - StringLoader.load_snes_rom(self.snes_buffer, true) - SpriteLoader.load_from_structs(self.snes_data) - SpriteLoader.load_enemy_battle_sprites(self.snes_data, self.snes_buffer) - SpriteLoader.load_battle_bgs(self.snes_data, self.snes_buffer) - MapLoader.load_snes_rom(self.snes_buffer) + load_snes_rom_from_bytes(bytes) func load_psx_folder(_dirname: String): pass @@ -110,7 +113,6 @@ func _ready(): STRUCT.parse_struct_definitions_from_tsv_filename('res://data/SNES_save.tsv', structdefs) STRUCT.parse_struct_definitions_from_tsv_filename('res://data/SNES_other.tsv', structdefs) var _error := psx_productcode_regex.compile('(S[A-Z]{3}_\\d{3}\\.\\d{2});(\\d)') - load_snes_rom(ROM_filename) # Debugging breakpoint pass diff --git a/test/audio_system.gd b/test/audio_system.gd index 083a022..1f9d31a 100644 --- a/test/audio_system.gd +++ b/test/audio_system.gd @@ -102,11 +102,17 @@ func _create_bgm_playback() -> void: for i in SoundLoader.BGM_NUM: evaluate_bgm(i) +func _stop_all() -> void: + if self.music_player: + self.music_player.queue_free() + self.music_player = null + SoundLoader.player.stop() # Called when the node enters the scene tree for the first time. func _ready() -> void: self._create_sfx_buttons() self._create_bgm_playback() + $btn_stop.connect('pressed', self, '_stop_all') for i in len(RomLoader.snes_data.bgm_song_pointers): var pointer = RomLoader.snes_data.bgm_song_pointers[i] print('BGM 0x%02X (%02d) at 0x%06X' % [i, i, pointer]) diff --git a/test/audio_system.tscn b/test/audio_system.tscn index 5e55f25..30876b7 100644 --- a/test/audio_system.tscn +++ b/test/audio_system.tscn @@ -27,3 +27,10 @@ margin_top = 192.0 margin_right = 102.0 margin_bottom = 214.0 text = "Play BGM" + +[node name="btn_stop" type="Button" parent="."] +margin_left = 312.0 +margin_top = 192.0 +margin_right = 374.0 +margin_bottom = 214.0 +text = "Stop All" diff --git a/test/debug_menu.gd b/test/debug_menu.gd new file mode 100644 index 0000000..1ae7b30 --- /dev/null +++ b/test/debug_menu.gd @@ -0,0 +1,18 @@ +extends Control + +signal pressed_worldmap +signal pressed_party +signal pressed_battle_sprites +signal pressed_audio + +func _on_btn_worldmap_pressed() -> void: + emit_signal('pressed_worldmap') + +func _on_btn_party_pressed() -> void: + emit_signal('pressed_party') + +func _on_btn_battle_sprites_pressed() -> void: + emit_signal('pressed_battle_sprites') + +func _on_btn_audio_pressed() -> void: + emit_signal('pressed_audio') diff --git a/test/debug_menu.tscn b/test/debug_menu.tscn new file mode 100644 index 0000000..22ccb48 --- /dev/null +++ b/test/debug_menu.tscn @@ -0,0 +1,57 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://test/debug_menu.gd" type="Script" id=1] + +[node name="debug_menu" type="Control"] +anchor_right = 1.0 +anchor_bottom = 1.0 +rect_min_size = Vector2( 384, 240 ) +size_flags_horizontal = 3 +size_flags_vertical = 3 +script = ExtResource( 1 ) + +[node name="CenterContainer" type="CenterContainer" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 + +[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer"] +margin_left = 110.0 +margin_top = 45.0 +margin_right = 273.0 +margin_bottom = 194.0 + +[node name="Label" type="Label" parent="CenterContainer/VBoxContainer"] +margin_right = 163.0 +margin_bottom = 29.0 +text = "DEBUG MENU +Press [backspace] to return" +align = 1 + +[node name="btn_worldmap" type="Button" parent="CenterContainer/VBoxContainer"] +margin_top = 37.0 +margin_right = 163.0 +margin_bottom = 59.0 +text = "World Map" + +[node name="btn_party" type="Button" parent="CenterContainer/VBoxContainer"] +margin_top = 67.0 +margin_right = 163.0 +margin_bottom = 89.0 +text = "Party Menu" + +[node name="btn_battle_sprites" type="Button" parent="CenterContainer/VBoxContainer"] +margin_top = 97.0 +margin_right = 163.0 +margin_bottom = 119.0 +text = "Battle Sprites" + +[node name="btn_audio" type="Button" parent="CenterContainer/VBoxContainer"] +margin_top = 127.0 +margin_right = 163.0 +margin_bottom = 149.0 +text = "Audio" + +[connection signal="pressed" from="CenterContainer/VBoxContainer/btn_worldmap" to="." method="_on_btn_worldmap_pressed"] +[connection signal="pressed" from="CenterContainer/VBoxContainer/btn_party" to="." method="_on_btn_party_pressed"] +[connection signal="pressed" from="CenterContainer/VBoxContainer/btn_battle_sprites" to="." method="_on_btn_battle_sprites_pressed"] +[connection signal="pressed" from="CenterContainer/VBoxContainer/btn_audio" to="." method="_on_btn_audio_pressed"] diff --git a/test/worldmap_system.gd b/test/worldmap_system.gd index 945520e..0ceb3ff 100644 --- a/test/worldmap_system.gd +++ b/test/worldmap_system.gd @@ -57,10 +57,10 @@ func _ready() -> void: # Called every frame. 'delta' is the elapsed time since the previous frame. func _process(delta: float) -> void: self.pos = self.pos.posmod(256.0) - self.minimap_tween -= sign(minimap_tween - minimap_mode) * delta / 4 + self.minimap_tween -= sign(minimap_tween - minimap_mode) * delta * 4 self.minimap_tween = clamp(minimap_tween, 0, 1) var l = ease(minimap_tween, -3.75) - l = minimap_tween +# l = minimap_tween var minimap_scale = lerp(0.125, 0.5, l) minimap.rect_scale = Vector2.ONE * minimap_scale minimap.rect_position = Vector2(lerp(8, 64, l), lerp(168, -8, l)) @@ -108,3 +108,5 @@ func _input(event: InputEvent) -> void: _set_map(4) KEY_0: self.minimap_mode = 1 - minimap_mode + KEY_H: + $lbl_help.visible = !$lbl_help.visible diff --git a/test/worldmap_system.tscn b/test/worldmap_system.tscn index 8e88937..aab4ab5 100644 --- a/test/worldmap_system.tscn +++ b/test/worldmap_system.tscn @@ -8,3 +8,13 @@ script = ExtResource( 1 ) [node name="tr_minimap" type="TextureRect" parent="."] margin_right = 40.0 margin_bottom = 40.0 + +[node name="lbl_help" type="Label" parent="."] +modulate = Color( 1, 1, 1, 0.752941 ) +margin_right = 40.0 +margin_bottom = 14.0 +text = "H: Toggle this help +1-5: Change world +0: Toggle minimap size +Arrow keys: Move around +Backspace: Return to debug menu" diff --git a/widgets/PartyMenu.tscn b/widgets/PartyMenu.tscn index e71bf0b..90e8c0d 100644 --- a/widgets/PartyMenu.tscn +++ b/widgets/PartyMenu.tscn @@ -5,8 +5,9 @@ [ext_resource path="res://widgets/PartyMenuCharacter.tscn" type="PackedScene" id=3] [node name="PartyMenu" type="Panel"] -margin_right = 264.0 +margin_right = 336.0 margin_bottom = 240.0 +rect_min_size = Vector2( 320, 240 ) theme = ExtResource( 1 ) script = ExtResource( 2 ) diff --git a/widgets/RomSelect.gd b/widgets/RomSelect.gd index fd68766..392349a 100644 --- a/widgets/RomSelect.gd +++ b/widgets/RomSelect.gd @@ -1,6 +1,8 @@ extends PanelContainer #warning-ignore-all:return_value_discarded +signal continue_pressed + const FOLDER_ICON := preload('res://theme/icons/file_folder.tres') const ALLOWED_EXTS := PoolStringArray(['bin', 'iso', 'sfc', 'srm', 'gba']) const CD_EXTS := PoolStringArray(['bin', 'iso']) # If you have a weird disc image format, you can mount it yourself, leave me out of it @@ -25,7 +27,8 @@ onready var itemlist: ItemList = $VBoxContainer/HBoxContainer/PanelContainer/Ite onready var vscroller: VScrollBar = itemlist.get_child(0) onready var folder_buttons_scroller: ScrollContainer = $VBoxContainer/ScrollContainer onready var folder_buttons: HBoxContainer = $VBoxContainer/ScrollContainer/folder_buttons -onready var btn_ok: Button = $VBoxContainer/HBoxContainer/VBoxContainer/btn_ok +onready var btn_ok: Button = $'%btn_ok' +onready var btn_continue: Button = $'%btn_continue' onready var lbl_filename_header: Label = $'%lbl_filename_header' onready var lbl_filename_content: Label = $'%lbl_filename_content' onready var lbl_filetype_header: Label = $'%lbl_filetype_header' @@ -112,6 +115,13 @@ func update_view(): for entry in files: itemlist.add_item(entry[0], entry[1]) +func load_file(entry: String): + var filename := dir.get_current_dir() + '/' + entry + var ext = entry.rsplit('.', true, 1)[1].to_lower() + match ext: + 'sfc': + RomLoader.load_snes_rom(filename) + func activate_entry(entry: String, _index: int = -1): var curr_dir := dir.get_current_dir() if not self.last_dir.begins_with(curr_dir): @@ -120,10 +130,16 @@ func activate_entry(entry: String, _index: int = -1): var error := dir.change_dir(entry) if error == OK: update_view() + init_labels() else: print_debug(error) elif dir.file_exists(entry): - pass # Load the file + load_file(entry) + +func activate_current_entry(): + var selected := itemlist.get_selected_items() + if len(selected) > 0: + activate_entry(itemlist.get_item_text(selected[0])) func view_entry(entry: String, index: int = -1): init_labels() @@ -132,6 +148,7 @@ func view_entry(entry: String, index: int = -1): lbl_filetype_header.modulate = active_modulate_color if dir.dir_exists(entry): lbl_filetype_content.text = 'Folder' + btn_ok.disabled = false elif dir.file_exists(entry): var file := File.new() var filename := dir.get_current_dir() + '/' + entry @@ -162,6 +179,7 @@ func view_entry(entry: String, index: int = -1): lbl_filesize_content.text = human_size lbl_fileinfo_header.modulate = active_modulate_color if prodcode else inactive_modulate_color lbl_fileinfo_content.text = prodcode + btn_ok.disabled = (ext != 'sfc') # Only allow opening snes roms for now func init_labels(): lbl_filename_header.modulate = inactive_modulate_color @@ -172,6 +190,7 @@ func init_labels(): lbl_filesize_content.text = '' lbl_fileinfo_header.modulate = inactive_modulate_color lbl_fileinfo_content.text = '' + btn_ok.disabled = true func _ready() -> void: init_labels() @@ -183,6 +202,9 @@ func _ready() -> void: var error = dir.open(home_path) if error == OK: update_view() + btn_ok.connect('pressed', self, 'activate_current_entry') + btn_continue.connect('pressed', self, '_on_continue_pressed') + RomLoader.connect('rom_loaded', self, '_on_rom_loaded') print(ProjectSettings.globalize_path('user://')) print(ProjectSettings.globalize_path(dir.get_current_dir())) @@ -190,6 +212,13 @@ func _ready() -> void: func _on_ItemList_item_activated(index: int) -> void: activate_entry(itemlist.get_item_text(index), index) - func _on_ItemList_item_selected(index: int) -> void: view_entry(itemlist.get_item_text(index), index) + +func _on_rom_loaded() -> void: + $'%lbl_prompt'.text = "Select a save file from your device's storage" + btn_continue.disabled = false + +func _on_continue_pressed() -> void: + emit_signal('continue_pressed') + diff --git a/widgets/RomSelect.tscn b/widgets/RomSelect.tscn index 3c50c73..cfc454a 100644 --- a/widgets/RomSelect.tscn +++ b/widgets/RomSelect.tscn @@ -17,7 +17,8 @@ margin_right = 380.0 margin_bottom = 236.0 custom_constants/separation = 2 -[node name="Label" type="Label" parent="VBoxContainer"] +[node name="lbl_prompt" type="Label" parent="VBoxContainer"] +unique_name_in_owner = true margin_right = 376.0 margin_bottom = 14.0 text = "Select a ROM from your device's storage" @@ -159,15 +160,29 @@ size_flags_vertical = 3 text = "Test" autowrap = true -[node name="btn_ok" type="Button" parent="VBoxContainer/HBoxContainer/VBoxContainer"] -margin_left = 62.0 +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/HBoxContainer/VBoxContainer"] margin_top = 163.0 -margin_right = 86.0 +margin_right = 148.0 margin_bottom = 185.0 + +[node name="btn_ok" type="Button" parent="VBoxContainer/HBoxContainer/VBoxContainer/HBoxContainer"] +unique_name_in_owner = true +margin_right = 24.0 +margin_bottom = 22.0 size_flags_horizontal = 4 size_flags_vertical = 8 disabled = true text = "OK" +[node name="btn_continue" type="Button" parent="VBoxContainer/HBoxContainer/VBoxContainer/HBoxContainer"] +unique_name_in_owner = true +margin_left = 32.0 +margin_right = 91.0 +margin_bottom = 22.0 +size_flags_horizontal = 4 +size_flags_vertical = 8 +disabled = true +text = "Continue" + [connection signal="item_activated" from="VBoxContainer/HBoxContainer/PanelContainer/ItemList" to="." method="_on_ItemList_item_activated"] [connection signal="item_selected" from="VBoxContainer/HBoxContainer/PanelContainer/ItemList" to="." method="_on_ItemList_item_selected"] diff --git a/widgets/WebFileSelect.gd b/widgets/WebFileSelect.gd new file mode 100644 index 0000000..f9b972f --- /dev/null +++ b/widgets/WebFileSelect.gd @@ -0,0 +1,164 @@ +extends PanelContainer +#warning-ignore-all:return_value_discarded + +signal continue_pressed + +const FOLDER_ICON := preload('res://theme/icons/file_folder.tres') +const ALLOWED_EXTS := PoolStringArray(['bin', 'iso', 'sfc', 'srm', 'gba']) +const CD_EXTS := PoolStringArray(['bin', 'iso']) # If you have a weird disc image format, you can mount it yourself, leave me out of it +var EXT_ICONS := { # Dicts can't be const + 'bin': preload('res://theme/icons/file_binary.tres'), + 'iso': preload('res://theme/icons/file_disc.tres'), + 'sfc': preload('res://theme/icons/file_cart.tres'), + 'gba': preload('res://theme/icons/file_cart.tres'), +} +var TYPE_DESCS := { # Dicts can't be const + 'bin': 'Binary', + 'iso': 'CD-ROM Image', + 'sfc': 'SNES ROM', + 'gba': 'GBA ROM', + 'srm': 'SNES Savefile' +} + +var uploaded_files := {} # filename: data +var uploaded_files_types := {} # filename: type + +var cached_cd_bin_paths := {} +onready var itemlist: ItemList = $VBoxContainer/HBoxContainer/PanelContainer/ItemList +onready var vscroller: VScrollBar = itemlist.get_child(0) +onready var folder_buttons_scroller: ScrollContainer = $VBoxContainer/ScrollContainer +onready var folder_buttons: HBoxContainer = $VBoxContainer/ScrollContainer/folder_buttons +onready var btn_ok: Button = $'%btn_ok' +onready var btn_continue: Button = $'%btn_continue' +onready var lbl_filename_header: Label = $'%lbl_filename_header' +onready var lbl_filename_content: Label = $'%lbl_filename_content' +onready var lbl_filetype_header: Label = $'%lbl_filetype_header' +onready var lbl_filetype_content: Label = $'%lbl_filetype_content' +onready var lbl_filesize_header: Label = $'%lbl_filesize_header' +onready var lbl_filesize_content: Label = $'%lbl_filesize_content' +onready var lbl_fileinfo_header: Label = $'%lbl_fileinfo_header' +onready var lbl_fileinfo_content: Label = $'%lbl_fileinfo_content' +const inactive_modulate_color := Color(0.5, 0.5, 0.5) +const active_modulate_color := Color.white +var last_dir := '' + +static func get_human_size(n: int) -> String: + if n > 0x100000000: + return '%.2f GiB' % (n/float(0x40000000)) + if n > 0x400000: + return '%.1f MiB' % (n/float(0x100000)) + if n > 0x1000: + return '%.1f KiB' % (n/float(0x400)) + return '%d B' % n + +static func tokenize_path(path: String) -> PoolStringArray: + return path.rstrip('/').split('/', true) # Allow empty means '/' will return [''] + +static func join_path(tokens) -> String: + var path = '/'.join(tokens) + return path if path else '/' + +func update_view(): + for child in folder_buttons.get_children(): + child.queue_free() + + itemlist.clear() + var files := [] + for entry in uploaded_files.keys(): + if entry in cached_cd_bin_paths: + files.append([entry, EXT_ICONS.iso]) + elif '.' in entry: + var extension = entry.rsplit('.', true, 1)[1] + if extension in ALLOWED_EXTS: + files.append([entry, EXT_ICONS.get(extension)]) + files.sort() + for entry in files: + itemlist.add_item(entry[0], entry[1]) + +func load_file(entry: String): + var ext = entry.rsplit('.', true, 1)[1].to_lower() + match ext: + 'sfc': + RomLoader.load_snes_rom_from_bytes(uploaded_files[entry]) + +func activate_entry(entry: String, _index: int = -1): + load_file(entry) + +func activate_current_entry(): + var selected := itemlist.get_selected_items() + if len(selected) > 0: + activate_entry(itemlist.get_item_text(selected[0])) + +func view_entry(entry: String, _index: int = -1): + init_labels() + lbl_filename_header.modulate = active_modulate_color + lbl_filename_content.text = entry + lbl_filetype_header.modulate = active_modulate_color + + var size = len(uploaded_files[entry]) + var human_size = get_human_size(size) + var ext = entry.rsplit('.', true, 1)[1].to_lower() + var type = TYPE_DESCS.get(ext, 'Unknown') + var prodcode: String = uploaded_files_types[entry] + if ext in CD_EXTS: + pass # TODO: Update cd image loader to accept PoolByteArray or a buffer view +# var cd := RomLoader.loader_cd_image.new(uploaded_files[entry]) +# for key in cd.directory: +# var s = key.trim_prefix('./') +# var re_match := RomLoader.psx_productcode_regex.search(s) +# if re_match: +# prodcode = '%s\n%s\n%s' % [cd.pvd.system_identifier.strip_edges(), cd.pvd.volume_identifier.strip_edges(), re_match.get_string(1)] +# type = 'PSX CD-ROM Image' +# cached_cd_bin_paths[entry] = prodcode +# if index >= 0: +# itemlist.set_item_icon(index, EXT_ICONS.iso) +# break + var info = ('Info:\n %s' % prodcode) if prodcode else '' + lbl_filetype_content.text = type + lbl_filesize_header.modulate = active_modulate_color + lbl_filesize_content.text = human_size + lbl_fileinfo_header.modulate = active_modulate_color if prodcode else inactive_modulate_color + lbl_fileinfo_content.text = prodcode + btn_ok.disabled = (ext != 'sfc') # Only allow opening snes roms for now + +func init_labels(): + lbl_filename_header.modulate = inactive_modulate_color + lbl_filename_content.text = '' + lbl_filetype_header.modulate = inactive_modulate_color + lbl_filetype_content.text = '' + lbl_filesize_header.modulate = inactive_modulate_color + lbl_filesize_content.text = '' + lbl_fileinfo_header.modulate = inactive_modulate_color + lbl_fileinfo_content.text = '' + +func _upload_file(filename: String, file_type: String, data): # data is Javascript ArrayBuffer, which should be castable to PoolByteArray + uploaded_files[filename] = data + uploaded_files_types[filename] = file_type + update_view() + +func _ready() -> void: + if OS.get_name() != 'HTML5' or !OS.has_feature('JavaScript'): + return + init_labels() + update_view() + btn_ok.connect('pressed', self, 'activate_current_entry') + btn_continue.connect('pressed', self, '_on_continue_pressed') + RomLoader.connect('rom_loaded', self, '_on_rom_loaded') + HTML5.connect('uploaded_file', self, '_upload_file') + + +func _on_ItemList_item_activated(index: int) -> void: + activate_entry(itemlist.get_item_text(index), index) + +func _on_ItemList_item_selected(index: int) -> void: + view_entry(itemlist.get_item_text(index), index) + +func _on_btn_upload_pressed() -> void: + HTML5.upload_file() + +func _on_rom_loaded() -> void: + $'%lbl_prompt'.text = "Select a save file from your device's storage" + btn_continue.disabled = false + +func _on_continue_pressed() -> void: + emit_signal('continue_pressed') diff --git a/widgets/WebFileSelect.tscn b/widgets/WebFileSelect.tscn new file mode 100644 index 0000000..7d5dce9 --- /dev/null +++ b/widgets/WebFileSelect.tscn @@ -0,0 +1,200 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://theme/menu_theme.tres" type="Theme" id=1] +[ext_resource path="res://widgets/WebFileSelect.gd" type="Script" id=2] + +[node name="WebFileSelect" type="PanelContainer"] +anchor_right = 1.0 +anchor_bottom = 1.0 +rect_min_size = Vector2( 384, 240 ) +theme = ExtResource( 1 ) +script = ExtResource( 2 ) + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +margin_left = 4.0 +margin_top = 4.0 +margin_right = 380.0 +margin_bottom = 236.0 +custom_constants/separation = 2 + +[node name="HBoxContainer2" type="HBoxContainer" parent="VBoxContainer"] +margin_right = 376.0 +margin_bottom = 22.0 + +[node name="lbl_prompt" type="Label" parent="VBoxContainer/HBoxContainer2"] +unique_name_in_owner = true +margin_top = 4.0 +margin_right = 238.0 +margin_bottom = 18.0 +text = "Select a ROM from your device's storage" + +[node name="btn_upload" type="Button" parent="VBoxContainer/HBoxContainer2"] +margin_left = 246.0 +margin_right = 293.0 +margin_bottom = 22.0 +text = "Upload" + +[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer"] +margin_top = 24.0 +margin_right = 376.0 +margin_bottom = 53.0 +rect_min_size = Vector2( 0, 29 ) +scroll_vertical_enabled = false + +[node name="folder_buttons" type="HBoxContainer" parent="VBoxContainer/ScrollContainer"] +custom_constants/separation = 4 + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"] +margin_top = 55.0 +margin_right = 376.0 +margin_bottom = 232.0 +size_flags_vertical = 3 + +[node name="PanelContainer" type="PanelContainer" parent="VBoxContainer/HBoxContainer"] +margin_right = 220.0 +margin_bottom = 177.0 +rect_min_size = Vector2( 120, 0 ) +size_flags_horizontal = 3 +size_flags_stretch_ratio = 1.5 + +[node name="ItemList" type="ItemList" parent="VBoxContainer/HBoxContainer/PanelContainer"] +margin_left = 4.0 +margin_top = 4.0 +margin_right = 216.0 +margin_bottom = 173.0 +rect_min_size = Vector2( 120, 160 ) + +[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/HBoxContainer"] +margin_left = 228.0 +margin_right = 376.0 +margin_bottom = 177.0 +size_flags_horizontal = 3 +custom_constants/separation = 1 + +[node name="lbl_filename_header" type="Label" parent="VBoxContainer/HBoxContainer/VBoxContainer"] +unique_name_in_owner = true +margin_right = 148.0 +margin_bottom = 14.0 +size_flags_vertical = 1 +text = "Filename:" + +[node name="mc" type="MarginContainer" parent="VBoxContainer/HBoxContainer/VBoxContainer"] +margin_top = 15.0 +margin_right = 148.0 +margin_bottom = 29.0 +custom_constants/margin_top = 0 +custom_constants/margin_left = 12 +custom_constants/margin_bottom = 0 + +[node name="lbl_filename_content" type="Label" parent="VBoxContainer/HBoxContainer/VBoxContainer/mc"] +unique_name_in_owner = true +margin_left = 12.0 +margin_right = 148.0 +margin_bottom = 14.0 +size_flags_vertical = 3 +text = "Test" +autowrap = true + +[node name="lbl_filetype_header" type="Label" parent="VBoxContainer/HBoxContainer/VBoxContainer"] +unique_name_in_owner = true +margin_top = 30.0 +margin_right = 148.0 +margin_bottom = 44.0 +size_flags_vertical = 1 +text = "Type:" + +[node name="mc2" type="MarginContainer" parent="VBoxContainer/HBoxContainer/VBoxContainer"] +margin_top = 45.0 +margin_right = 148.0 +margin_bottom = 59.0 +custom_constants/margin_top = 0 +custom_constants/margin_left = 12 +custom_constants/margin_bottom = 0 + +[node name="lbl_filetype_content" type="Label" parent="VBoxContainer/HBoxContainer/VBoxContainer/mc2"] +unique_name_in_owner = true +margin_left = 12.0 +margin_right = 148.0 +margin_bottom = 14.0 +size_flags_vertical = 1 +text = "Test" +autowrap = true + +[node name="lbl_filesize_header" type="Label" parent="VBoxContainer/HBoxContainer/VBoxContainer"] +unique_name_in_owner = true +margin_top = 60.0 +margin_right = 148.0 +margin_bottom = 74.0 +size_flags_vertical = 1 +text = "Size:" + +[node name="mc3" type="MarginContainer" parent="VBoxContainer/HBoxContainer/VBoxContainer"] +margin_top = 75.0 +margin_right = 148.0 +margin_bottom = 89.0 +custom_constants/margin_top = 0 +custom_constants/margin_left = 12 +custom_constants/margin_bottom = 0 + +[node name="lbl_filesize_content" type="Label" parent="VBoxContainer/HBoxContainer/VBoxContainer/mc3"] +unique_name_in_owner = true +margin_left = 12.0 +margin_right = 148.0 +margin_bottom = 14.0 +size_flags_vertical = 1 +text = "Test" +autowrap = true + +[node name="lbl_fileinfo_header" type="Label" parent="VBoxContainer/HBoxContainer/VBoxContainer"] +unique_name_in_owner = true +margin_top = 90.0 +margin_right = 148.0 +margin_bottom = 104.0 +size_flags_vertical = 1 +text = "Info:" + +[node name="mc4" type="MarginContainer" parent="VBoxContainer/HBoxContainer/VBoxContainer"] +margin_top = 105.0 +margin_right = 148.0 +margin_bottom = 154.0 +size_flags_vertical = 3 +custom_constants/margin_top = 0 +custom_constants/margin_left = 12 +custom_constants/margin_bottom = 0 + +[node name="lbl_fileinfo_content" type="Label" parent="VBoxContainer/HBoxContainer/VBoxContainer/mc4"] +unique_name_in_owner = true +margin_left = 12.0 +margin_right = 148.0 +margin_bottom = 49.0 +size_flags_vertical = 3 +text = "Test" +autowrap = true + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/HBoxContainer/VBoxContainer"] +margin_top = 155.0 +margin_right = 148.0 +margin_bottom = 177.0 + +[node name="btn_ok" type="Button" parent="VBoxContainer/HBoxContainer/VBoxContainer/HBoxContainer"] +unique_name_in_owner = true +margin_right = 24.0 +margin_bottom = 22.0 +size_flags_horizontal = 4 +size_flags_vertical = 8 +disabled = true +text = "OK" + +[node name="btn_continue" type="Button" parent="VBoxContainer/HBoxContainer/VBoxContainer/HBoxContainer"] +unique_name_in_owner = true +margin_left = 32.0 +margin_right = 91.0 +margin_bottom = 22.0 +size_flags_horizontal = 4 +size_flags_vertical = 8 +disabled = true +text = "Continue" + +[connection signal="pressed" from="VBoxContainer/HBoxContainer2/btn_upload" to="." method="_on_btn_upload_pressed"] +[connection signal="item_activated" from="VBoxContainer/HBoxContainer/PanelContainer/ItemList" to="." method="_on_ItemList_item_activated"] +[connection signal="item_selected" from="VBoxContainer/HBoxContainer/PanelContainer/ItemList" to="." method="_on_ItemList_item_selected"]