ChocolateBird/widgets/FileSelect.gd

257 lines
8.7 KiB
GDScript

extends PanelContainer
#warning-ignore-all:return_value_discarded
signal debug_pressed
signal savefile_loaded(dicts)
const globals = preload('res://globals.gd') # Make LSP shut up about non-const
const FOLDER_ICON := globals.FOLDER_ICON
const ALLOWED_EXTS := globals.ALLOWED_EXTS
const CD_EXTS := globals.CD_EXTS
const EXT_ICONS := globals.EXT_ICONS
const TYPE_DESCS := globals.TYPE_DESCS
enum {SELECT_ROM, SELECT_SAVE, COMPLETE}
var current_mode = SELECT_ROM
var cached_cd_bin_paths := {}
var dir := Directory.new()
var home_path := ''
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_load: Button = $'%btn_load'
onready var btn_debug: Button = $'%btn_debug'
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'
onready var splash_filter := $'%splash_filter'
onready var splash_label := $'%splash_label'
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():
var error = dir.list_dir_begin(true, true)
if error != OK:
print_debug(error)
return
for child in folder_buttons.get_children():
child.queue_free()
var path = ProjectSettings.globalize_path(dir.get_current_dir())
var cur_path: Array = tokenize_path(path)
var l := len(cur_path)
for i in l:
var btn := Button.new()
btn.connect('pressed', self, 'activate_entry', [join_path(cur_path.slice(0, i))])
btn.text = cur_path[i]
folder_buttons.add_child(btn)
if i == 0:
btn.text += '/'
if i == l-1:
btn.icon = FOLDER_ICON
if last_dir.begins_with(path): # i.e. we jumped up a level or more
var last_path: Array = tokenize_path(last_dir)
var l2 := len(last_path)
for i in range(l, l2):
var btn := Button.new()
btn.connect('pressed', self, 'activate_entry', [join_path(last_path.slice(0, i))])
btn.text = last_path[i]
btn.modulate = inactive_modulate_color
folder_buttons.add_child(btn)
# Doesn't work thanks to frame delay
# folder_buttons_scroller.set_h_scroll(9999)
# folder_buttons_scroller.ensure_control_visible(folder_buttons_scroller.get_h_scrollbar())
# folder_buttons_scroller.get_h_scrollbar().update()
itemlist.clear()
var directories := PoolStringArray()
var current_dir := dir.get_current_dir()
var files := []
while true:
var entry := dir.get_next()
if len(entry) == 0:
break
var filename := current_dir + '/' + entry
if filename in cached_cd_bin_paths:
files.append([entry, EXT_ICONS.iso])
elif dir.current_is_dir():
directories.append(entry)
elif '.' in entry:
var extension = entry.rsplit('.', true, 1)[1]
if extension in ALLOWED_EXTS:
files.append([entry, EXT_ICONS.get(extension)])
directories.sort()
files.sort()
for entry in directories:
itemlist.add_item(entry, FOLDER_ICON)
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', 'smc':
if current_mode == SELECT_ROM:
splash_filter.visible = true
splash_label.text = 'Loading ROM: %s'%filename
yield(get_tree(), 'idle_frame')
RomLoader.load_snes_rom(filename)
'srm':
if current_mode == SELECT_ROM:
return
var file := File.new()
match file.open(filename, File.READ):
OK:
emit_signal('savefile_loaded', SaveLoader.load_save_dicts_from_file(file))
_set_mode(COMPLETE)
var err:
print_debug('Error loading savefile: %s - %d'%[filename, err])
func activate_entry(entry: String, _index: int = -1):
var curr_dir := dir.get_current_dir()
if not self.last_dir.begins_with(curr_dir):
self.last_dir = curr_dir
if dir.dir_exists(entry):
var error := dir.change_dir(entry)
if error == OK:
update_view()
init_labels()
else:
print_debug(error)
elif dir.file_exists(entry):
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
if dir.dir_exists(entry):
lbl_filetype_content.text = 'Folder'
btn_load.disabled = false
elif dir.file_exists(entry):
var file := File.new()
var filename := dir.get_current_dir() + '/' + entry
var error := file.open(filename, File.READ)
if error != OK:
print_debug(error)
return
var size = file.get_len()
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 := ''
if ext in CD_EXTS:
var cd := RomLoader.loader_cd_image.new(file)
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[filename] = 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
match current_mode:
SELECT_ROM:
btn_load.disabled = (ext != 'sfc' and ext != 'smc') # Only allow opening snes roms for now
SELECT_SAVE, COMPLETE:
btn_load.disabled = (ext != 'srm')
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 = ''
btn_load.disabled = true
func _ready() -> void:
init_labels()
if OS.has_feature('windows'):
home_path = OS.get_environment('USERPROFILE')
else:
home_path = OS.get_environment('HOME')
# dir.open(OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS))
var error = dir.open(home_path)
if error == OK:
update_view()
btn_load.connect('pressed', self, 'activate_current_entry')
btn_debug.connect('pressed', self, '_on_debug_pressed')
RomLoader.connect('rom_loaded', self, '_on_rom_loaded')
RomLoader.connect('loading_stage_updated', self, '_on_loading_stage_updated')
print(ProjectSettings.globalize_path('user://'))
print(ProjectSettings.globalize_path(dir.get_current_dir()))
func _set_mode(new_mode) -> void:
match new_mode:
SELECT_ROM:
$'%lbl_prompt'.text = "Select a ROM from your device's storage"
btn_debug.disabled = true
SELECT_SAVE:
$'%lbl_prompt'.text = "Select a save file from your device's storage"
btn_debug.disabled = false
COMPLETE:
$'%lbl_prompt'.text = "Save file loaded"
btn_debug.disabled = false
current_mode = new_mode
update_view()
init_labels()
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_loading_stage_updated(stage: String) -> void:
splash_label.text += '\n' + stage
func _on_rom_loaded() -> void:
splash_filter.visible = false
_set_mode(SELECT_SAVE)
func _on_debug_pressed() -> void:
emit_signal('debug_pressed')