First commit 🎉

This commit is contained in:
Tony Bark 2025-07-17 01:49:18 -04:00
commit 43ea213f9b
728 changed files with 37080 additions and 0 deletions

View file

@ -0,0 +1,94 @@
@tool
extends HBoxContainer
signal tag_state_changed
const RESULT_CODE = preload("res://addons/popochiu/editor/config/result_codes.gd")
var _anim_tag_state: Dictionary = {}
@onready var tag_name_label = $HBoxContainer/TagName
@onready var import_toggle = $Panel/HBoxContainer/Import
@onready var loops_toggle = $Panel/HBoxContainer/Loops
@onready var separator = $Panel/HBoxContainer/Separator
@onready var visible_toggle = $Panel/HBoxContainer/Visible
@onready var clickable_toggle = $Panel/HBoxContainer/Clickable
#region Godot ######################################################################################
func _ready():
# Common toggle icons
import_toggle.icon = get_theme_icon('Load', 'EditorIcons')
loops_toggle.icon = get_theme_icon('Loop', 'EditorIcons')
# Room-related toggle icons
visible_toggle.icon = get_theme_icon('GuiVisibilityVisible', 'EditorIcons')
clickable_toggle.icon = get_theme_icon('ToolSelect', 'EditorIcons')
#endregion
#region Public #####################################################################################
func init(tag_cfg: Dictionary):
if tag_cfg.tag_name == null or tag_cfg.tag_name == "":
printerr(RESULT_CODE.get_error_message(RESULT_CODE.ERR_UNNAMED_TAG_DETECTED))
return false
_anim_tag_state = _load_default_tag_state()
_anim_tag_state.merge(tag_cfg, true)
_setup_scene()
func show_prop_buttons():
separator.visible = true
visible_toggle.visible = true
clickable_toggle.visible = true
#endregion
#region SetGet #####################################################################################
func get_cfg() -> Dictionary:
return _anim_tag_state
#endregion
#region Private ####################################################################################
func _setup_scene():
import_toggle.button_pressed = _anim_tag_state.import
loops_toggle.button_pressed = _anim_tag_state.loops
tag_name_label.text = _anim_tag_state.tag_name
visible_toggle.button_pressed = _anim_tag_state.prop_visible
clickable_toggle.button_pressed = _anim_tag_state.prop_clickable
emit_signal("tag_state_changed")
func _load_default_tag_state() -> Dictionary:
return {
"tag_name": "",
"import": PopochiuConfig.is_default_animation_import_enabled(),
"loops": PopochiuConfig.is_default_animation_loop_enabled(),
"prop_visible": PopochiuConfig.is_default_animation_prop_visible(),
"prop_clickable": PopochiuConfig.is_default_animation_prop_clickable(),
}
func _on_import_toggled(button_pressed):
_anim_tag_state.import = button_pressed
emit_signal("tag_state_changed")
func _on_loops_toggled(button_pressed):
_anim_tag_state.loops = button_pressed
emit_signal("tag_state_changed")
func _on_visible_toggled(button_pressed):
_anim_tag_state.prop_visible = button_pressed
emit_signal("tag_state_changed")
func _on_clickable_toggled(button_pressed):
_anim_tag_state.prop_clickable = button_pressed
emit_signal("tag_state_changed")
#endregion

View file

@ -0,0 +1 @@
uid://krf8u35pkjn3

View file

@ -0,0 +1,94 @@
[gd_scene load_steps=6 format=3 uid="uid://rphyltbm12m4"]
[ext_resource type="Script" path="res://addons/popochiu/editor/importers/aseprite/docks/animation_tag_row.gd" id="1"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_77wem"]
[sub_resource type="Image" id="Image_vdhps"]
data = {
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
"format": "RGBA8",
"height": 16,
"mipmaps": false,
"width": 16
}
[sub_resource type="ImageTexture" id="ImageTexture_c80ss"]
image = SubResource("Image_vdhps")
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_sd1l8"]
[node name="AnimationTagRow" type="HBoxContainer"]
offset_right = 320.0
offset_bottom = 20.0
script = ExtResource("1")
[node name="HBoxContainer" type="HBoxContainer" parent="."]
layout_mode = 2
[node name="TagName" type="Label" parent="HBoxContainer"]
layout_mode = 2
text = "Tag Name"
[node name="Panel" type="Panel" parent="."]
layout_mode = 2
size_flags_horizontal = 3
theme_override_styles/panel = SubResource("StyleBoxEmpty_77wem")
[node name="HBoxContainer" type="HBoxContainer" parent="Panel"]
layout_mode = 1
anchors_preset = 1
anchor_left = 1.0
anchor_right = 1.0
offset_left = -60.0
offset_bottom = 20.0
grow_horizontal = 0
[node name="Visible" type="Button" parent="Panel/HBoxContainer"]
visible = false
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 3
tooltip_text = "This prop will be visible"
toggle_mode = true
icon = SubResource("ImageTexture_c80ss")
flat = true
[node name="Clickable" type="Button" parent="Panel/HBoxContainer"]
visible = false
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 3
tooltip_text = "This prop will be clickable"
toggle_mode = true
icon = SubResource("ImageTexture_c80ss")
flat = true
[node name="Separator" type="Panel" parent="Panel/HBoxContainer"]
visible = false
custom_minimum_size = Vector2(1, 0)
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_sd1l8")
[node name="Import" type="Button" parent="Panel/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 3
tooltip_text = "Import this animation"
toggle_mode = true
icon = SubResource("ImageTexture_c80ss")
flat = true
[node name="Loops" type="Button" parent="Panel/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 3
tooltip_text = "Set animation as looping"
toggle_mode = true
icon = SubResource("ImageTexture_c80ss")
flat = true
[connection signal="toggled" from="Panel/HBoxContainer/Visible" to="." method="_on_visible_toggled"]
[connection signal="toggled" from="Panel/HBoxContainer/Clickable" to="." method="_on_clickable_toggled"]
[connection signal="toggled" from="Panel/HBoxContainer/Import" to="." method="_on_import_toggled"]
[connection signal="toggled" from="Panel/HBoxContainer/Loops" to="." method="_on_loops_toggled"]

View file

@ -0,0 +1,429 @@
@tool
extends PanelContainer
# TODO: review coding standards for those constants
const RESULT_CODE = preload("res://addons/popochiu/editor/config/result_codes.gd")
const LOCAL_OBJ_CONFIG = preload("res://addons/popochiu/editor/config/local_obj_config.gd")
# TODO: this can be specialized, even if for a two buttons... ?
const AnimationTagRow =\
preload("res://addons/popochiu/editor/importers/aseprite/docks/animation_tag_row.gd")
var scene: Node
var target_node: Node
var file_system: EditorFileSystem
# ---- External logic
var _animation_tag_row_scene: PackedScene =\
preload("res://addons/popochiu/editor/importers/aseprite/docks/animation_tag_row.tscn")
var _aseprite = preload("../aseprite_controller.gd").new() ## TODO: should be absolute?
# ---- References for children scripts
var _root_node: Node
var _options: Dictionary
# ---- Importer parameters variables
var _source: String = ""
var _tags_cache: Array = []
var _file_dialog_aseprite: FileDialog
var _output_folder_dialog: FileDialog
var _importing := false
var _output_folder := ""
var _out_folder_default := "[Same as scene]"
#region Godot ######################################################################################
func _ready():
_set_elements_styles()
if not PopochiuEditorConfig.aseprite_importer_enabled():
_show_info()
return
# Check access to Aseprite executable
var result = _check_aseprite()
if result == RESULT_CODE.SUCCESS:
_show_importer()
else:
PopochiuUtils.print_error(RESULT_CODE.get_error_message(result))
_show_warning()
# Load inspector dock configuration from node
var cfg = LOCAL_OBJ_CONFIG.load_config(target_node)
if cfg == null:
_load_default_config()
_set_options_visible(true)
else:
_load_config(cfg)
_set_tags_visible(cfg.get("tags_exp"))
_set_options_visible(cfg.get("op_exp"))
#endregion
#region Private ####################################################################################
func _check_aseprite() -> int:
if not _aseprite.check_command_path():
return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FULL_PATH
if not _aseprite.test_command():
return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FOUND
return RESULT_CODE.SUCCESS
func _list_tags(file: String):
if not _aseprite.check_command_path():
return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FULL_PATH
if not _aseprite.test_command():
return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FOUND
return _aseprite.list_tags(file)
## TODO: Currently unused. keeping this as reference
## to populate a checkable list of layers
func _list_layers(file: String, only_visibles = false):
if not _aseprite.check_command_path():
return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FULL_PATH
if not _aseprite.test_command():
return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FOUND
return _aseprite.list_layers(file, only_visibles)
func _load_config(cfg):
if cfg.has("source"):
_set_source(cfg.source)
_output_folder = cfg.get("o_folder", "")
get_node("%OutFolderButton").text = (
_output_folder if _output_folder != "" else _out_folder_default
)
get_node("%OutFileName").text = cfg.get("o_name", "")
get_node("%VisibleLayersCheckButton").set_pressed_no_signal(
cfg.get("only_visible_layers", false)
)
get_node("%WipeOldAnimationsCheckButton").set_pressed_no_signal(
cfg.get("wipe_old_anims", false)
)
_set_tags_visible(cfg.get("tags_exp", false))
_set_options_visible(cfg.get("op_exp", false))
_populate_tags(cfg.get("tags", []))
func _save_config():
_update_tags_cache()
var cfg := {
"source": _source,
"tags": _tags_cache,
"tags_exp": get_node("%Tags").visible,
"op_exp": get_node("%Options").visible,
"o_folder": _output_folder,
"o_name": get_node("%OutFileName").text,
"only_visible_layers": get_node("%VisibleLayersCheckButton").is_pressed(),
"wipe_old_anims": get_node("%WipeOldAnimationsCheckButton").is_pressed(),
}
LOCAL_OBJ_CONFIG.save_config(target_node, cfg)
func _load_default_config():
# Reset variables
_source = ""
_tags_cache = []
_output_folder = ""
# Empty tags list
_empty_tags_container()
# Reset inspector fields
get_node("%SourceButton").text = "[empty]"
get_node("%SourceButton").tooltip_text = ""
get_node("%OutFolderButton").text = "[empty]"
get_node("%OutFileName").clear()
get_node("%VisibleLayersCheckButton").set_pressed_no_signal(false)
get_node("%WipeOldAnimationsCheckButton").set_pressed_no_signal(
PopochiuConfig.is_default_wipe_old_anims_enabled()
)
func _set_source(source):
_source = source
get_node("%SourceButton").text = _source
get_node("%SourceButton").tooltip_text = _source
func _on_source_pressed():
_open_source_dialog()
func _on_aseprite_file_selected(path):
_set_source(ProjectSettings.localize_path(path))
_populate_tags(_get_tags_from_source())
_save_config()
_file_dialog_aseprite.queue_free()
func _on_rescan_pressed():
_populate_tags(\
_merge_with_cache(_get_tags_from_source())\
)
_save_config()
func _on_import_pressed():
if _importing:
return
_importing = true
_root_node = get_tree().get_edited_scene_root()
if _source == "":
_show_message("Aseprite file not selected")
_importing = false
return
_options = {
"source": ProjectSettings.globalize_path(_source),
"tags": _tags_cache,
"output_folder": (
_output_folder if _output_folder != "" else _root_node.scene_file_path.get_base_dir()
),
"output_filename": get_node("%OutFileName").text,
"only_visible_layers": get_node("%VisibleLayersCheckButton").is_pressed(),
"wipe_old_animations": get_node("%WipeOldAnimationsCheckButton").is_pressed(),
}
_save_config()
func _on_reset_pressed():
var _confirmation_dialog = _show_confirmation(\
"This will reset the importer preferences." + \
"This cannot be undone! Are you sure?", "Confirmation required!")
_confirmation_dialog.get_ok_button().connect("pressed", Callable(self, "_reset_prefs_metadata"))
func _reset_prefs_metadata():
if target_node.has_meta(LOCAL_OBJ_CONFIG.LOCAL_OBJ_CONFIG_META_NAME):
target_node.remove_meta(LOCAL_OBJ_CONFIG.LOCAL_OBJ_CONFIG_META_NAME)
_load_default_config()
notify_property_list_changed()
func _open_source_dialog():
_file_dialog_aseprite = _create_aseprite_file_selection()
get_parent().add_child(_file_dialog_aseprite)
if _source != "":
_file_dialog_aseprite.set_current_dir(
ProjectSettings.globalize_path(
_source.get_base_dir()
)
)
_file_dialog_aseprite.popup_centered_ratio()
func _create_aseprite_file_selection():
var file_dialog = FileDialog.new()
file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
file_dialog.access = FileDialog.ACCESS_FILESYSTEM
file_dialog.title = "Select Aseprite animation file"
file_dialog.connect("file_selected", Callable(self, "_on_aseprite_file_selected"))
file_dialog.set_filters(PackedStringArray(["*.ase","*.aseprite"]))
return file_dialog
func _populate_tags(tags: Array):
## reset tags container
_empty_tags_container()
# Add each tag found
for t in tags:
if t.tag_name == "":
continue
var tag_row: AnimationTagRow = _animation_tag_row_scene.instantiate()
get_node("%Tags").add_child(tag_row)
tag_row.init(t)
tag_row.connect("tag_state_changed", Callable(self, "_save_config"))
_customize_tag_ui(tag_row)
# Invoke customization hook implementable in child classes
_update_tags_cache()
func _customize_tag_ui(tagrow: AnimationTagRow):
## This can be implemented by child classes if necessary
pass
func _empty_tags_container():
# Clean the inspector tags container empty
for tl in get_node("%Tags").get_children():
get_node("%Tags").remove_child(tl)
tl.queue_free()
func _update_tags_cache():
_tags_cache = _get_tags_from_ui()
func _merge_with_cache(tags: Array) -> Array:
var tags_cache_index = {}
var result = []
for t in _tags_cache:
tags_cache_index[t.tag_name] = t
for i in tags.size():
result.push_back(
tags_cache_index[tags[i].tag_name]
if tags_cache_index.has(tags[i].tag_name)
else tags[i]
)
return result
func _get_tags_from_ui() -> Array:
var tags_list = []
for tag_row in get_node("%Tags").get_children():
var tag_row_cfg = tag_row.get_cfg()
if tag_row_cfg.tag_name == "":
continue
tags_list.push_back(tag_row_cfg)
return tags_list
func _get_tags_from_source() -> Array:
var tags_found = _list_tags(ProjectSettings.globalize_path(_source))
if typeof(tags_found) == TYPE_INT:
PopochiuUtils.print_error(RESULT_CODE.get_error_message(tags_found))
return []
var tags_list = []
for t in tags_found:
if t == "":
continue
tags_list.push_back({
tag_name = t
})
return tags_list
func _show_message(
message: String, title: String = "", object: Object = null, method := ""
):
var warning_dialog = AcceptDialog.new()
if title != "":
warning_dialog.title = title
warning_dialog.dialog_text = message
warning_dialog.popup_window = true
var callback := Callable(warning_dialog, "queue_free")
if is_instance_valid(object) and not method.is_empty():
callback = func():
object.call(method)
warning_dialog.confirmed.connect(callback)
warning_dialog.close_requested.connect(callback)
PopochiuEditorHelper.show_dialog(warning_dialog)
func _show_confirmation(message: String, title: String = ""):
var _confirmation_dialog = ConfirmationDialog.new()
get_parent().add_child(_confirmation_dialog)
if title != "":
_confirmation_dialog.title = title
_confirmation_dialog.dialog_text = message
_confirmation_dialog.popup_centered()
_confirmation_dialog.connect("close_requested", Callable(_confirmation_dialog, "queue_free"))
return _confirmation_dialog
func _on_options_title_toggled(button_pressed):
_set_options_visible(button_pressed)
_save_config()
func _set_options_visible(is_visible):
get_node("%Options").visible = is_visible
get_node("%OptionsTitle").icon = (
PopochiuEditorConfig.get_icon(PopochiuEditorConfig.Icons.EXPANDED) if is_visible
else PopochiuEditorConfig.get_icon(PopochiuEditorConfig.Icons.COLLAPSED)
)
func _on_tags_title_toggled(button_pressed: bool) -> void:
_set_tags_visible(button_pressed)
_save_config()
func _set_tags_visible(is_visible: bool) -> void:
get_node("%Tags").visible = is_visible
get_node("%TagsTitle").icon = (
PopochiuEditorConfig.get_icon(PopochiuEditorConfig.Icons.EXPANDED) if is_visible
else PopochiuEditorConfig.get_icon(PopochiuEditorConfig.Icons.COLLAPSED)
)
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 = FileDialog.new()
file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_DIR
file_dialog.access = FileDialog.ACCESS_RESOURCES
file_dialog.title = "Select destination folder"
file_dialog.connect("dir_selected", Callable(self, "_on_output_folder_selected"))
return file_dialog
func _on_output_folder_selected(path):
_output_folder = path
get_node("%OutFolderButton").text = (
_output_folder if _output_folder != "" else _out_folder_default
)
_output_folder_dialog.queue_free()
_save_config()
func _set_elements_styles():
# Set sections title colors according to current theme
var section_color = get_theme_color("prop_section", "Editor")
var section_style = StyleBoxFlat.new()
section_style.set_bg_color(section_color)
get_node("%TagsTitleBar").set("theme_override_styles/panel", section_style)
get_node("%OptionsTitleBar").set("theme_override_styles/panel", section_style)
# Set style of warning panel
get_node("%WarningPanel").add_theme_stylebox_override(
"panel",
get_node("%WarningPanel").get_theme_stylebox("sub_inspector_bg11", "Editor")
)
get_node("%WarningLabel").add_theme_color_override("font_color", Color("c46c71"))
func _show_info():
get_node("%Info").visible = true
get_node("%Warning").visible = false
get_node("%Importer").visible = false
func _show_warning():
get_node("%Info").visible = false
get_node("%Warning").visible = true
get_node("%Importer").visible = false
func _show_importer():
get_node("%Info").visible = false
get_node("%Warning").visible = false
get_node("%Importer").visible = true
# TODO: Introduce layer selection list, more or less as tags
#endregion

View file

@ -0,0 +1 @@
uid://c5o55inhq2abl

View file

@ -0,0 +1,225 @@
[gd_scene load_steps=4 format=3 uid="uid://bcanby6n3eahm"]
[sub_resource type="StyleBoxEmpty" id="1"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_wwoxk"]
bg_color = Color(0, 0, 0, 1)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ctsm1"]
content_margin_left = 4.0
content_margin_top = 4.0
content_margin_right = 4.0
content_margin_bottom = 4.0
bg_color = Color(1, 0.364706, 0.364706, 1)
draw_center = false
corner_detail = 1
[node name="AsepriteImporterInspectorDock" type="PanelContainer"]
offset_right = 14.0
offset_bottom = 14.0
theme_override_styles/panel = SubResource("1")
[node name="Margin" type="MarginContainer" parent="."]
layout_mode = 2
theme_override_constants/margin_top = 2
theme_override_constants/margin_bottom = 2
[node name="Importer" type="VBoxContainer" parent="Margin"]
unique_name_in_owner = true
visible = false
layout_mode = 2
[node name="Source" type="HBoxContainer" parent="Margin/Importer"]
layout_mode = 2
tooltip_text = "Location of the Aseprite (*.ase, *.aseprite) source file."
[node name="Label" type="Label" parent="Margin/Importer/Source"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "Aseprite File"
[node name="SourceButton" type="Button" parent="Margin/Importer/Source"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "[empty]"
clip_text = true
[node name="RescanButton" type="Button" parent="Margin/Importer/Source"]
unique_name_in_owner = true
layout_mode = 2
text = "Rescan"
[node name="TagsTitleBar" type="PanelContainer" parent="Margin/Importer"]
unique_name_in_owner = true
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_wwoxk")
[node name="TagsTitle" type="Button" parent="Margin/Importer/TagsTitleBar"]
unique_name_in_owner = true
layout_mode = 2
theme_override_colors/font_pressed_color = Color(0.8, 0.807843, 0.827451, 1)
toggle_mode = true
text = "Animation tags"
[node name="Tags" type="VBoxContainer" parent="Margin/Importer"]
unique_name_in_owner = true
layout_mode = 2
[node name="OptionsTitleBar" type="PanelContainer" parent="Margin/Importer"]
unique_name_in_owner = true
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_wwoxk")
[node name="OptionsTitle" type="Button" parent="Margin/Importer/OptionsTitleBar"]
unique_name_in_owner = true
layout_mode = 2
theme_override_colors/font_pressed_color = Color(0.8, 0.807843, 0.827451, 1)
toggle_mode = true
text = "Options"
[node name="Options" type="VBoxContainer" parent="Margin/Importer"]
unique_name_in_owner = true
layout_mode = 2
[node name="OutFolder" type="HBoxContainer" parent="Margin/Importer/Options"]
layout_mode = 2
tooltip_text = "Location where the spritesheet file should be saved."
[node name="Label" type="Label" parent="Margin/Importer/Options/OutFolder"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "Output folder"
[node name="OutFolderButton" type="Button" parent="Margin/Importer/Options/OutFolder"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "[empty]"
clip_text = true
[node name="OutFile" type="HBoxContainer" parent="Margin/Importer/Options"]
layout_mode = 2
tooltip_text = "Base filename for spritesheet. In case the layer option is used, this works as a prefix to the layer name."
[node name="Label" type="Label" parent="Margin/Importer/Options/OutFile"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "Output file name"
[node name="OutFileName" type="LineEdit" parent="Margin/Importer/Options/OutFile"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
[node name="VisibleLayers" type="HBoxContainer" parent="Margin/Importer/Options"]
layout_mode = 2
tooltip_text = "If active, layers not visible in the source file won't be included in the final image."
[node name="Label" type="Label" parent="Margin/Importer/Options/VisibleLayers"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "Only visible layers"
[node name="VisibleLayersCheckButton" type="CheckButton" parent="Margin/Importer/Options/VisibleLayers"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
[node name="WipeOldAnimations" type="HBoxContainer" parent="Margin/Importer/Options"]
layout_mode = 2
tooltip_text = "If active, layers not visible in the source file won't be included in the final image."
[node name="Label" type="Label" parent="Margin/Importer/Options/WipeOldAnimations"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
tooltip_text = "Set this to OFF if you want to add new animations on top of old ones. Anims with same name will be updated."
mouse_filter = 0
text = "Wipe old animations"
[node name="WipeOldAnimationsCheckButton" type="CheckButton" parent="Margin/Importer/Options/WipeOldAnimations"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
[node name="Import" type="Button" parent="Margin/Importer"]
layout_mode = 2
text = "Import"
[node name="Reset" type="Button" parent="Margin/Importer"]
layout_mode = 2
text = "Reset Preferences"
[node name="Warning" type="VBoxContainer" parent="Margin"]
unique_name_in_owner = true
visible = false
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="Margin/Warning"]
layout_mode = 2
[node name="WarningPanel" type="Panel" parent="Margin/Warning/HBoxContainer"]
unique_name_in_owner = true
custom_minimum_size = Vector2(222, 50)
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_styles/panel = SubResource("StyleBoxFlat_ctsm1")
[node name="WarningLabel" type="Label" parent="Margin/Warning/HBoxContainer/WarningPanel"]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 42)
layout_mode = 0
anchor_right = 1.0
anchor_bottom = 1.0
size_flags_horizontal = 3
size_flags_vertical = 6
theme_override_colors/font_color = Color(0.768627, 0.423529, 0.443137, 1)
text = "Error loading Aseprite Importer!
Check Output panel for details."
horizontal_alignment = 1
[node name="Info" type="VBoxContainer" parent="Margin"]
unique_name_in_owner = true
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="Margin/Info"]
layout_mode = 2
[node name="InfoPanel" type="Panel" parent="Margin/Info/HBoxContainer"]
custom_minimum_size = Vector2(222, 50)
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_styles/panel = SubResource("StyleBoxFlat_ctsm1")
[node name="InfoLabel" type="Label" parent="Margin/Info/HBoxContainer/InfoPanel"]
custom_minimum_size = Vector2(0, 42)
layout_mode = 0
anchor_right = 1.0
anchor_bottom = 1.0
size_flags_horizontal = 3
size_flags_vertical = 6
text = "Aseprite Importer disabled.
Can be enabled in Editor Settings."
[connection signal="pressed" from="Margin/Importer/Source/SourceButton" to="." method="_on_source_pressed"]
[connection signal="pressed" from="Margin/Importer/Source/RescanButton" to="." method="_on_rescan_pressed"]
[connection signal="toggled" from="Margin/Importer/TagsTitleBar/TagsTitle" to="." method="_on_tags_title_toggled"]
[connection signal="toggled" from="Margin/Importer/OptionsTitleBar/OptionsTitle" to="." method="_on_options_title_toggled"]
[connection signal="pressed" from="Margin/Importer/Options/OutFolder/OutFolderButton" to="." method="_on_out_folder_pressed"]
[connection signal="focus_exited" from="Margin/Importer/Options/OutFile/OutFileName" to="." method="_save_config"]
[connection signal="pressed" from="Margin/Importer/Options/VisibleLayers/VisibleLayersCheckButton" to="." method="_save_config"]
[connection signal="pressed" from="Margin/Importer/Options/WipeOldAnimations/WipeOldAnimationsCheckButton" to="." method="_save_config"]
[connection signal="pressed" from="Margin/Importer/Import" to="." method="_on_import_pressed"]
[connection signal="pressed" from="Margin/Importer/Reset" to="." method="_on_reset_pressed"]

View file

@ -0,0 +1,55 @@
@tool
extends "res://addons/popochiu/editor/importers/aseprite/docks/aseprite_importer_inspector_dock.gd"
var _animation_player_path: String
var _animation_creator = preload(
"res://addons/popochiu/editor/importers/aseprite/animation_creator.gd"
).new()
#region Godot ######################################################################################
func _ready():
if not target_node.has_node("AnimationPlayer"):
PopochiuUtils.print_error(
RESULT_CODE.get_error_message(RESULT_CODE.ERR_NO_ANIMATION_PLAYER_FOUND)
)
return
_animation_player_path = target_node.get_node("AnimationPlayer").get_path()
# Instantiate animation creator
_animation_creator.init(_aseprite, file_system)
super()
#endregion
#region Private ####################################################################################
func _on_import_pressed():
# Set everything up
# This will populate _root_node and _options class variables
super()
if _animation_player_path == "" or not _root_node.has_node(_animation_player_path):
_show_message("AnimationPlayer not found")
_importing = false
return
var result = await _animation_creator.create_character_animations(
target_node, _root_node.get_node(_animation_player_path), _options
)
_importing = false
if typeof(result) == TYPE_INT and result != RESULT_CODE.SUCCESS:
PopochiuUtils.print_error(RESULT_CODE.get_error_message(result))
_show_message("Some errors occurred. Please check output panel.", "Warning!")
else:
_show_message("%d animation tags processed." % [_tags_cache.size()], "Done!")
func _customize_tag_ui(tag_row: AnimationTagRow):
# Nothing special has to be done for Character tags
pass
#endregion

View file

@ -0,0 +1,117 @@
@tool
extends "res://addons/popochiu/editor/importers/aseprite/docks/aseprite_importer_inspector_dock.gd"
var _animation_creator = preload(\
"res://addons/popochiu/editor/importers/aseprite/animation_creator.gd").new()
#region Godot ######################################################################################
func _ready():
# Instantiate animation creator
_animation_creator.init(_aseprite, file_system)
super()
#endregion
#region Private ####################################################################################
func _on_import_pressed():
# Set everything up
# This will populate _root_node and _options class variables
super()
var props_container = _root_node.get_node("Props")
var result: int = RESULT_CODE.SUCCESS
# Create a prop for each tag that must be imported
# and populate it with the right sprite
for tag in _options.get("tags"):
# Ignore unwanted tags
if not tag.import: continue
# Always convert to PascalCase as a standard
# TODO: check Godot 4 standards, I can't find info
var prop_name: String = tag.tag_name.to_pascal_case()
# In case the prop is there, use the one we already have
var prop = props_container.get_node_or_null(prop_name)
if prop == null:
# Create a new prop if necessary, specifying the
# interaction flags.
prop = _create_prop(prop_name, tag.prop_clickable, tag.prop_visible)
else:
# Force flags (a bit redundant but they may have been changed
# in the Importer interface, for already imported props)
prop.clickable = tag.prop_clickable
prop.visible = tag.prop_visible
prop.set_meta("ANIM_NAME", tag.tag_name)
for prop in props_container.get_children():
if not prop.has_meta("ANIM_NAME"): continue
# TODO: check if animation player exists in prop, if not add it
# same for Sprite2D even if it should be there...
# Make the output folder match the prop's folder
_options.output_folder = prop.scene_file_path.get_base_dir()
# Import a single tag animation
result = await _animation_creator.create_prop_animations(
prop,
prop.get_meta("ANIM_NAME"),
_options
)
for prop in props_container.get_children():
if not prop.has_meta("ANIM_NAME"): continue
# Save the prop
result = await _save_prop(prop)
# TODO: maybe check if this is better done with signals
_importing = false
if typeof(result) == TYPE_INT and result != RESULT_CODE.SUCCESS:
PopochiuUtils.print_error(RESULT_CODE.get_error_message(result))
_show_message("Some errors occurred. Please check output panel.", "Warning!")
else:
await get_tree().create_timer(0.1).timeout
# Once the popup is closed, call _clean_props()
_show_message(
"%d animation tags processed." % [_tags_cache.size()],
"Done!"
)
func _customize_tag_ui(tag_row: AnimationTagRow):
# Show props-related buttons if we are in a room
tag_row.show_prop_buttons()
func _create_prop(name: String, is_clickable: bool = true, is_visible: bool = true):
var factory = PopochiuPropFactory.new()
var param := PopochiuPropFactory.PopochiuPropFactoryParam.new()
param.obj_name = name
param.room = _root_node
param.is_interactive = is_clickable
param.is_visible = is_visible
if factory.create(param) != ResultCodes.SUCCESS:
return
return factory.get_obj_scene()
func _save_prop(prop: PopochiuProp):
var packed_scene: PackedScene = PackedScene.new()
packed_scene.pack(prop)
if ResourceSaver.save(packed_scene, prop.scene_file_path) != OK:
PopochiuUtils.print_error(
"Couldn't save animations for prop %s at %s" %
[prop.name, prop.scene_file_path]
)
return ResultCodes.ERR_CANT_SAVE_OBJ_SCENE
return ResultCodes.SUCCESS
#endregion