forked from newt/gmtk-2024
429 lines
12 KiB
GDScript
429 lines
12 KiB
GDScript
@tool
|
|
extends PanelContainer
|
|
|
|
const wizard_config = preload("../../config/wizard_config.gd")
|
|
const result_code = preload("../../config/result_codes.gd")
|
|
var _aseprite_file_exporter = preload("../../aseprite/file_exporter.gd").new()
|
|
var config = preload("../../config/config.gd").new()
|
|
|
|
var scene: Node
|
|
var target_node: Node
|
|
var file_system: EditorFileSystem = EditorInterface.get_resource_filesystem()
|
|
|
|
var _layer: String = ""
|
|
var _slice: String = ""
|
|
var _source: String = ""
|
|
var _file_dialog_aseprite: EditorFileDialog
|
|
var _output_folder_dialog: EditorFileDialog
|
|
var _importing := false
|
|
var _output_folder := ""
|
|
var _out_folder_default := "[Same as scene]"
|
|
var _layer_default := "[all]"
|
|
|
|
var _interface_section_state
|
|
|
|
@onready var _section_title := $dock_fields/VBoxContainer/title as Button
|
|
|
|
# general
|
|
@onready var _source_field := $dock_fields/VBoxContainer/source/button as Button
|
|
# layers
|
|
@onready var _layer_section_header := $dock_fields/VBoxContainer/extra/sections/layers/section_header as Button
|
|
@onready var _layer_section_container := $dock_fields/VBoxContainer/extra/sections/layers/section_content as MarginContainer
|
|
@onready var _layer_field := $dock_fields/VBoxContainer/extra/sections/layers/section_content/content/layer/options as OptionButton
|
|
@onready var _visible_layers_field := $dock_fields/VBoxContainer/extra/sections/layers/section_content/content/visible_layers/CheckBox as CheckBox
|
|
@onready var _ex_pattern_field := $dock_fields/VBoxContainer/extra/sections/layers/section_content/content/ex_pattern/LineEdit as LineEdit
|
|
# slice
|
|
@onready var _slice_section_header := $dock_fields/VBoxContainer/extra/sections/slices/section_header as Button
|
|
@onready var _slice_section_container := $dock_fields/VBoxContainer/extra/sections/slices/section_content as MarginContainer
|
|
@onready var _slice_field := $dock_fields/VBoxContainer/extra/sections/slices/section_content/content/slice/options as OptionButton
|
|
# output
|
|
@onready var _output_section_header := $dock_fields/VBoxContainer/extra/sections/output/section_header as Button
|
|
@onready var _output_section_container := $dock_fields/VBoxContainer/extra/sections/output/section_content as MarginContainer
|
|
@onready var _out_folder_field := $dock_fields/VBoxContainer/extra/sections/output/section_content/content/out_folder/button as Button
|
|
@onready var _out_filename_field := $dock_fields/VBoxContainer/extra/sections/output/section_content/content/out_filename/LineEdit as LineEdit
|
|
|
|
@onready var _import_button := $dock_fields/VBoxContainer/import as Button
|
|
|
|
const INTERFACE_SECTION_KEY_LAYER = "layer_section"
|
|
const INTERFACE_SECTION_KEY_SLICE = "slice_section"
|
|
const INTERFACE_SECTION_KEY_OUTPUT = "output_section"
|
|
|
|
@onready var _expandable_sections = {
|
|
INTERFACE_SECTION_KEY_LAYER: { "header": _layer_section_header, "content": _layer_section_container},
|
|
INTERFACE_SECTION_KEY_SLICE: { "header": _slice_section_header, "content": _slice_section_container},
|
|
INTERFACE_SECTION_KEY_OUTPUT: { "header": _output_section_header, "content": _output_section_container},
|
|
}
|
|
|
|
func _ready():
|
|
_pre_setup()
|
|
_setup_interface()
|
|
_setup_config()
|
|
_setup_field_listeners()
|
|
_setup()
|
|
_check_for_changes()
|
|
|
|
|
|
func _check_for_changes():
|
|
if not _source or _source == "":
|
|
return
|
|
|
|
var saved_hash = wizard_config.get_source_hash(target_node)
|
|
|
|
if saved_hash == "":
|
|
return
|
|
|
|
if saved_hash != FileAccess.get_md5(_source):
|
|
$dock_fields.show_source_change_warning()
|
|
|
|
|
|
func _setup_interface():
|
|
_hide_fields()
|
|
_show_specific_fields()
|
|
var cfg = wizard_config.load_interface_config(target_node)
|
|
_interface_section_state = cfg
|
|
|
|
_section_title.add_theme_stylebox_override("normal", _section_title.get_theme_stylebox("hover"))
|
|
|
|
for key in _expandable_sections:
|
|
_adjust_section_visibility(key)
|
|
|
|
|
|
func _setup_config():
|
|
var cfg = wizard_config.load_config(target_node)
|
|
if cfg == null:
|
|
_load_common_default_config()
|
|
else:
|
|
_load_common_config(cfg)
|
|
|
|
|
|
func _load_common_config(cfg):
|
|
if cfg.has("source"):
|
|
_set_source(cfg.source)
|
|
|
|
if cfg.get("layer", "") != "":
|
|
_layer_field.clear()
|
|
_set_layer(cfg.layer)
|
|
|
|
if cfg.get("slice", "") != "":
|
|
_slice_field.clear()
|
|
_set_slice(cfg.slice)
|
|
|
|
_set_out_folder(cfg.get("o_folder", ""))
|
|
_out_filename_field.text = cfg.get("o_name", "")
|
|
_visible_layers_field.button_pressed = cfg.get("only_visible", false)
|
|
_ex_pattern_field.text = cfg.get("o_ex_p", "")
|
|
|
|
_load_config(cfg)
|
|
|
|
|
|
func _load_common_default_config():
|
|
_ex_pattern_field.text = config.get_default_exclusion_pattern()
|
|
_visible_layers_field.button_pressed = config.should_include_only_visible_layers_by_default()
|
|
#_cleanup_hide_unused_nodes.button_pressed = config.is_set_visible_track_automatically_enabled()
|
|
_load_default_config()
|
|
|
|
|
|
func _set_source(source):
|
|
_source = source
|
|
_source_field.text = _source
|
|
_source_field.tooltip_text = _source
|
|
|
|
|
|
func _set_layer(layer):
|
|
_layer = layer
|
|
_layer_field.add_item(_layer)
|
|
|
|
|
|
func _set_slice(slice):
|
|
_slice = slice
|
|
_slice_field.add_item(_slice)
|
|
|
|
|
|
func _set_out_folder(path):
|
|
_output_folder = path
|
|
_out_folder_field.text = _output_folder if _output_folder != "" else _out_folder_default
|
|
_out_folder_field.tooltip_text = _out_folder_field.text
|
|
|
|
|
|
func _toggle_section_visibility(key: String) -> void:
|
|
_interface_section_state[key] = not _interface_section_state.get(key, false)
|
|
_adjust_section_visibility(key)
|
|
wizard_config.save_interface_config(target_node, _interface_section_state)
|
|
|
|
|
|
func _adjust_section_visibility(key: String) -> void:
|
|
var section = _expandable_sections[key]
|
|
var is_visible = _interface_section_state.get(key, false)
|
|
_adjust_icon(section.header, is_visible)
|
|
section.content.visible = is_visible
|
|
|
|
|
|
func _adjust_icon(section: Button, is_visible: bool = true) -> void:
|
|
var icon_name = "GuiTreeArrowDown" if is_visible else "GuiTreeArrowRight"
|
|
section.icon = get_theme_icon(icon_name, "EditorIcons")
|
|
|
|
|
|
func _setup_field_listeners():
|
|
_layer_section_header.button_down.connect(_on_layer_header_button_down)
|
|
_slice_section_header.button_down.connect(_on_slice_header_button_down)
|
|
_output_section_header.button_down.connect(_on_output_header_button_down)
|
|
|
|
_source_field.pressed.connect(_on_source_pressed)
|
|
_source_field.aseprite_file_dropped.connect(_on_source_aseprite_file_dropped)
|
|
|
|
_layer_field.button_down.connect(_on_layer_button_down)
|
|
_layer_field.item_selected.connect(_on_layer_item_selected)
|
|
|
|
_slice_field.button_down.connect(_on_slice_button_down)
|
|
_slice_field.item_selected.connect(_on_slice_item_selected)
|
|
|
|
_out_folder_field.dir_dropped.connect(_on_out_dir_dropped)
|
|
_out_folder_field.pressed.connect(_on_out_folder_pressed)
|
|
|
|
_import_button.pressed.connect(_on_import_pressed)
|
|
|
|
|
|
func _on_layer_header_button_down():
|
|
_toggle_section_visibility(INTERFACE_SECTION_KEY_LAYER)
|
|
|
|
|
|
func _on_slice_header_button_down():
|
|
_toggle_section_visibility(INTERFACE_SECTION_KEY_SLICE)
|
|
|
|
|
|
func _on_output_header_button_down():
|
|
_toggle_section_visibility(INTERFACE_SECTION_KEY_OUTPUT)
|
|
|
|
|
|
func _on_layer_button_down():
|
|
if _source == "":
|
|
_show_message("Please. Select source file first.")
|
|
return
|
|
|
|
var layers = _get_available_layers(ProjectSettings.globalize_path(_source))
|
|
_populate_options_field(_layer_field, layers, _layer)
|
|
|
|
|
|
func _on_layer_item_selected(index):
|
|
if index == 0:
|
|
_layer = ""
|
|
return
|
|
_layer = _layer_field.get_item_text(index)
|
|
_save_config()
|
|
|
|
|
|
func _on_slice_item_selected(index):
|
|
if index == 0:
|
|
_slice = ""
|
|
return
|
|
_slice = _slice_field.get_item_text(index)
|
|
_save_config()
|
|
|
|
|
|
func _on_slice_button_down():
|
|
if _source == "":
|
|
_show_message("Please, select source file first.")
|
|
return
|
|
|
|
var slices = _get_available_slices(ProjectSettings.globalize_path(_source))
|
|
var current = 0
|
|
_slice_field.clear()
|
|
_slice_field.add_item(_layer_default)
|
|
|
|
for s in slices:
|
|
if s == "":
|
|
continue
|
|
|
|
_slice_field.add_item(s)
|
|
if s == _slice:
|
|
current = _slice_field.get_item_count() - 1
|
|
_slice_field.select(current)
|
|
|
|
|
|
func _on_source_pressed():
|
|
_open_source_dialog()
|
|
|
|
##
|
|
## Save current import options to node metadata
|
|
##
|
|
func _save_config():
|
|
var child_config = _get_current_field_values()
|
|
|
|
var cfg := {
|
|
"source": _source,
|
|
"layer": _layer,
|
|
"slice": _slice,
|
|
"o_folder": _output_folder,
|
|
"o_name": _out_filename_field.text,
|
|
"only_visible": _visible_layers_field.button_pressed,
|
|
"o_ex_p": _ex_pattern_field.text,
|
|
}
|
|
|
|
for c in child_config:
|
|
cfg[c] = child_config[c]
|
|
|
|
wizard_config.save_config(target_node, cfg)
|
|
|
|
|
|
func _get_import_options(default_folder: String):
|
|
return {
|
|
"output_folder": _output_folder if _output_folder != "" else default_folder,
|
|
"exception_pattern": _ex_pattern_field.text,
|
|
"only_visible_layers": _visible_layers_field.button_pressed,
|
|
"output_filename": _out_filename_field.text,
|
|
"layer": _layer
|
|
}
|
|
|
|
|
|
func _open_source_dialog():
|
|
_file_dialog_aseprite = _create_aseprite_file_selection()
|
|
get_parent().add_child(_file_dialog_aseprite)
|
|
if _source != "":
|
|
_file_dialog_aseprite.current_dir = ProjectSettings.globalize_path(_source.get_base_dir())
|
|
_file_dialog_aseprite.popup_centered_ratio()
|
|
|
|
|
|
func _create_aseprite_file_selection():
|
|
var file_dialog = EditorFileDialog.new()
|
|
file_dialog.file_mode = EditorFileDialog.FILE_MODE_OPEN_FILE
|
|
file_dialog.access = EditorFileDialog.ACCESS_FILESYSTEM
|
|
file_dialog.connect("file_selected",Callable(self,"_on_aseprite_file_selected"))
|
|
file_dialog.set_filters(PackedStringArray(["*.ase","*.aseprite"]))
|
|
return file_dialog
|
|
|
|
|
|
func _on_aseprite_file_selected(path):
|
|
_set_source(ProjectSettings.localize_path(path))
|
|
_save_config()
|
|
_file_dialog_aseprite.queue_free()
|
|
|
|
|
|
func _on_source_aseprite_file_dropped(path):
|
|
_set_source(path)
|
|
_save_config()
|
|
|
|
|
|
## Helper method to populate field with values
|
|
func _populate_options_field(field: OptionButton, values: Array, current_name: String):
|
|
var current = 0
|
|
field.clear()
|
|
field.add_item("[all]")
|
|
|
|
for v in values:
|
|
if v == "":
|
|
continue
|
|
|
|
field.add_item(v)
|
|
if v == current_name:
|
|
current = field.get_item_count() - 1
|
|
field.select(current)
|
|
|
|
|
|
func _on_out_folder_pressed():
|
|
_output_folder_dialog = _create_output_folder_selection()
|
|
get_parent().add_child(_output_folder_dialog)
|
|
if _output_folder != _out_folder_default:
|
|
_output_folder_dialog.current_dir = _output_folder
|
|
_output_folder_dialog.popup_centered_ratio()
|
|
|
|
|
|
func _create_output_folder_selection():
|
|
var file_dialog = EditorFileDialog.new()
|
|
file_dialog.file_mode = EditorFileDialog.FILE_MODE_OPEN_DIR
|
|
file_dialog.access = EditorFileDialog.ACCESS_RESOURCES
|
|
file_dialog.connect("dir_selected",Callable(self,"_on_output_folder_selected"))
|
|
return file_dialog
|
|
|
|
|
|
func _on_output_folder_selected(path):
|
|
_set_out_folder(path)
|
|
_output_folder_dialog.queue_free()
|
|
|
|
|
|
func _on_out_dir_dropped(path):
|
|
_set_out_folder(path)
|
|
|
|
|
|
func _show_message(message: String):
|
|
var _warning_dialog = AcceptDialog.new()
|
|
get_parent().add_child(_warning_dialog)
|
|
_warning_dialog.dialog_text = message
|
|
_warning_dialog.popup_centered()
|
|
_warning_dialog.connect("popup_hide",Callable(_warning_dialog,"queue_free"))
|
|
|
|
|
|
func _notify_aseprite_error(aseprite_error_code):
|
|
var error = result_code.get_error_message(aseprite_error_code)
|
|
printerr(error)
|
|
_show_message(error)
|
|
|
|
|
|
func _handle_cleanup(aseprite_content):
|
|
if config.should_remove_source_files():
|
|
DirAccess.remove_absolute(aseprite_content.data_file)
|
|
file_system.call_deferred("scan")
|
|
|
|
|
|
func _on_import_pressed():
|
|
if _importing:
|
|
return
|
|
_importing = true
|
|
|
|
if _source == "":
|
|
_show_message("Aseprite file not selected")
|
|
_importing = false
|
|
return
|
|
|
|
await _do_import()
|
|
_importing = false
|
|
$dock_fields.hide_source_change_warning()
|
|
EditorInterface.save_scene()
|
|
|
|
|
|
# This is a little bit leaky as this base scene contains fields only relevant to animation players.
|
|
# However, this is the simplest thing I can do without overcomplicating stuff.
|
|
func _hide_fields():
|
|
$dock_fields/VBoxContainer/modes.hide()
|
|
$dock_fields/VBoxContainer/animation_player.hide()
|
|
$dock_fields/VBoxContainer/extra/sections/animation.hide()
|
|
|
|
|
|
## this will be called before base class does its setup
|
|
func _pre_setup():
|
|
pass
|
|
|
|
|
|
## this will be called after base class setup is complete
|
|
func _setup():
|
|
pass
|
|
|
|
|
|
func _load_default_config():
|
|
pass
|
|
|
|
|
|
func _load_config(cfg: Dictionary):
|
|
pass
|
|
|
|
|
|
## Override to return available layers
|
|
func _get_available_layers(global_source_path: String) -> Array:
|
|
return []
|
|
|
|
|
|
## Override to return available slices
|
|
func _get_available_slices(global_source_path: String) -> Array:
|
|
return []
|
|
|
|
|
|
## Override this method for extra import options to add to node metadata
|
|
func _get_current_field_values() -> Dictionary:
|
|
return {}
|
|
|
|
|
|
func _do_import():
|
|
pass
|
|
|
|
|
|
func _show_specific_fields() -> void:
|
|
pass
|