extends Node @onready var anims_tree = $"HSplitContainer/VSplitContainer/Animations" @onready var frames_tree = $"HSplitContainer/VSplitContainer/Frames" @onready var region_viewport = $"HSplitContainer/Region/RegionView/Viewport" @onready var rg_oskin_prev_char = $"HSplitContainer/Region/RegionView/Viewport/ReferenceRect/OSPrevCharacter" @onready var rg_oskin_next_char = $"HSplitContainer/Region/RegionView/Viewport/ReferenceRect/OSNextCharacter" @onready var frame_count = $"HSplitContainer/VSplitContainer/Animations/frame_count" @onready var sprite_size = $"HSplitContainer/VSplitContainer/Animations/sprite_size" @onready var region_pos = $"HSplitContainer/Region/Controls/region_pos" @onready var region_size = $"HSplitContainer/Region/Controls/region_size" @onready var sprite_offset = $"HSplitContainer/Region/Controls/sprite_offset" @onready var align_all = $"HSplitContainer/VSplitContainer/Frames/align_all" @onready var oskin = $"HSplitContainer/VSplitContainer/Frames/oskin" @onready var menu = $"../Menu/HBoxContainer" @onready var close_icon = load("res://assets/icons/close.png") @onready var frames_open_icon = load("res://assets/icons/frames_open.png") @onready var frame_open_icon = load("res://assets/icons/frame_open.png") @onready var arrow_up_icon = load("res://assets/icons/arrow_up.png") @onready var arrow_down_icon = load("res://assets/icons/arrow_down.png") var spritesheet_parser: SpritesheetParser = SpritesheetParser.new() var preview_image:ImageTexture var process_image:Image var spritesheet_animations:Array = [] var spritesheet_size:Vector2i = Vector2i.ZERO var spritesheet_frames:int = 0 var selected_animation:int = 0 var selected_frame:int = 0 var image_path = GlobalConfig.spritesheet_image var data_path = GlobalConfig.spritesheet_data # Called when the node enters the scene tree for the first time. func _ready() -> void: var frame:ReferenceRect = region_viewport.get_node("ReferenceRect") region_viewport.size_changed.connect(func(): frame.position = (region_viewport.size / 2) - Vector2i(frame.size / 2) ) link_range_to_callable(frame_count, func(val: int): for animation in spritesheet_animations: spritesheet_frames = val if selected_frame >= spritesheet_frames: selected_frame = spritesheet_frames - 1 animation.frames.resize(spritesheet_frames) set_frames_tree() , spritesheet_frames) link_pos_to_callable_e(sprite_size, func(_val: int): spritesheet_size = get_vector(sprite_size) set_region_view() ) link_pos_to_callable_e(region_pos, func(_val: int): if ts_dont_change_internal: return set_property_on_current_frame("pos", get_vector(region_pos)) set_region_view() ) link_pos_to_callable_e(region_size, func(_val: int): if ts_dont_change_internal: return set_property_on_current_frame("size", get_vector(region_size)) set_region_view() update_current_frame_child() ) link_pos_to_callable_e(sprite_offset, func(_val: int): if ts_dont_change_internal: return set_property_on_current_frame("frame_pos", get_vector(sprite_offset)) set_region_view() ) var rs_autocrop:Button = region_size.get_node("autocrop") rs_autocrop.pressed.connect(func(): var pos = get_property_on_current_frame("pos") var size = get_property_on_current_frame("size") var cropped_image = process_image.get_region(Rect2i(pos, size)) var flood_fill_result = cropped_image.get_used_rect() set_property_on_current_frame("pos", flood_fill_result.position + pos) set_property_on_current_frame("size", flood_fill_result.size) update_current_frame_child() set_region_view() set_three_stooges() ) var aa_autocrop:Button = align_all.get_node("autocrop") aa_autocrop.pressed.connect(func(): var cur_anim = spritesheet_animations[selected_animation] if cur_anim.frames.size() < 1: return for cur_frame in cur_anim.frames: if cur_frame == null: continue var cropped_image = process_image.get_region(Rect2i(cur_frame.pos, cur_frame.size)) var flood_fill_result = cropped_image.get_used_rect() cur_frame.pos = (flood_fill_result.position + cur_frame.pos).max(cur_frame.pos) cur_frame.size = flood_fill_result.size.min(cur_frame.size) set_frames_tree() set_region_view() set_three_stooges() ) var so_preset:MenuButton = sprite_offset.get_node("preset") var sopre_pop:PopupMenu = so_preset.get_popup() sopre_pop.index_pressed.connect(func(idx: int): var frame_size = get_property_on_current_frame("size") if frame_size == null: return set_property_on_current_frame("frame_pos", align_to_vector(idx, frame_size, spritesheet_size)) set_region_view() set_three_stooges() ) var aa_preset:MenuButton = align_all.get_node("preset") var aapre_pop:PopupMenu = aa_preset.get_popup() aapre_pop.index_pressed.connect(func(idx: int): var cur_anim = spritesheet_animations[selected_animation] if cur_anim.frames.size() < 1: return for cur_frame in cur_anim.frames: if cur_frame == null: continue cur_frame.frame_pos = align_to_vector(idx, cur_frame.size, spritesheet_size) set_region_view() set_three_stooges() ) var oskin_prev:CheckBox = oskin.get_node("prev") var oskin_next:CheckBox = oskin.get_node("next") oskin_prev.toggled.connect(func(on:bool): rg_oskin_prev_char.visible = on ) oskin_next.toggled.connect(func(on:bool): rg_oskin_next_char.visible = on ) var load = menu.get_node("Load") load.load_image.connect(func(path): image_path = path init_form_image() ) load.load_xml.connect(func(path): data_path = path init_form_xml() ) var save = menu.get_node("Save") save.pressed.connect(func(): var xml = spritesheet_parser.spritesheet_to_xml(spritesheet_animations, spritesheet_size, image_path) save.save(xml) ) fuck_with_frames_tree() fuck_with_anims_tree() init_form_image() init_form_xml() func init_form_xml() -> void: spritesheet_parser.parse_spritesheet(data_path) spritesheet_size = spritesheet_parser.max_dimensions spritesheet_animations = [] spritesheet_frames = 0 for animation in spritesheet_parser.spritesheet: var frames = spritesheet_parser.spritesheet[animation] for fr_ame in frames: fr_ame.frame_pos = -fr_ame.frame_pos fr_ame.erase("frame_size") spritesheet_frames = max(frames.size(), spritesheet_frames) spritesheet_animations.append({ name = animation, frames = frames }) for animation in spritesheet_animations: animation.frames.resize(spritesheet_frames) var ct:Range = frame_count.get_node("value") ct.value = spritesheet_frames set_vector(sprite_size, spritesheet_size) set_frames_tree() set_anims_tree() func init_form_image() -> void: process_image = SpritesheetParser.load_image(image_path) preview_image = ImageTexture.create_from_image(process_image) fuck_with_region_view() func align_to_vector(idx:int, cs:Vector2i, ps:Vector2i) -> Vector2i: var vector = Vector2i.ZERO match idx: 1, 4, 7: vector.x = (ps.x / 2) - (cs.x / 2) 2, 5, 8: vector.x = ps.x - cs.x match idx: 3, 4, 5: vector.y = (ps.y / 2) - (cs.y / 2) 6, 7, 8: vector.y = ps.y - cs.y return vector func link_range_to_callable(obj:BoxContainer, callable:Callable, value) -> void: var input:Range = obj.get_node("value") input.value = value input.value_changed.connect(callable) func set_vector(obj:BoxContainer, vector:Vector2) -> void: var x:Range = obj.get_node("value_w") var y:Range = obj.get_node("value_h") x.value = vector.x y.value = vector.y func get_vector(obj:BoxContainer) -> Vector2: var x:Range = obj.get_node("value_w") var y:Range = obj.get_node("value_h") return Vector2(x.value, y.value) func link_pos_to_callable(obj:BoxContainer, callable:Callable, value:Vector2i) -> void: var x:Range = obj.get_node("value_w") var y:Range = obj.get_node("value_h") x.value = value.x y.value = value.y x.value_changed.connect(callable) y.value_changed.connect(callable) func link_pos_to_callable_e(obj:BoxContainer, callable:Callable) -> void: var x:Range = obj.get_node("value_w") var y:Range = obj.get_node("value_h") x.value_changed.connect(callable) y.value_changed.connect(callable) var dont_change_internal = false func fuck_with_anims_tree() -> void: var tree:Tree = anims_tree.get_node("Tree") var add_new_name:LineEdit = anims_tree.get_node("AddNew/name") var add_new_switch:Button = anims_tree.get_node("AddNew/switch") var tree_root = tree.create_item() tree.button_clicked.connect(func(item:TreeItem, column:int, id:int, _mbi): var item_index = item.get_index() if column == 0: if id == 0: spritesheet_animations.remove_at(item_index) if selected_animation == item_index: selected_animation = max(item_index - 1, 0) if selected_animation > -1: tree.set_selected(tree_root.get_child(selected_animation), 0) set_anims_tree() set_frames_tree() ) tree.item_selected.connect(func(): if dont_change_internal: dont_change_internal = false return var item = tree.get_selected() selected_animation = item.get_index() set_frames_tree() ) add_new_switch.pressed.connect(func(): var text = add_new_name.text if text.length() < 1: return for anim in spritesheet_animations: if anim.name == text: return var frames = [] frames.resize(spritesheet_frames) spritesheet_animations.append({ name = text, frames = frames }) set_anims_tree() add_new_name.text = "" ) func set_anims_tree() -> void: var tree:Tree = anims_tree.get_node("Tree") var tree_root = tree.get_root() for child in tree_root.get_children(): child.free() for animation in spritesheet_animations: var child = tree.create_item(tree_root) child.set_cell_mode(0, TreeItem.CELL_MODE_STRING) child.set_editable(0, false) child.set_text(0, animation.name) child.add_button(0, close_icon, 0) dont_change_internal = true tree.set_selected(tree_root.get_child(selected_animation), 0) func fuck_with_frames_tree() -> void: var tree:Tree = frames_tree.get_node("Tree") var tree_root = tree.create_item() tree_root.set_cell_mode(0, TreeItem.CELL_MODE_STRING) tree_root.set_editable(0, false) tree_root.set_text(0, "No animation selected") tree.button_clicked.connect(func(item:TreeItem, column:int, id:int, _mbi): var item_index = item.get_index() var cur_anim = spritesheet_animations[selected_animation] if item_index >= cur_anim.frames.size(): return if column == 1: var frame = cur_anim.frames[item_index] var duplicate = Input.is_action_pressed("mod_enable") if id == 0: if item_index + 1 >= cur_anim.frames.size(): return var next_frame = cur_anim.frames[item_index + 1] var placeholder if next_frame != null and not duplicate: placeholder = next_frame.duplicate(false) cur_anim.frames[item_index + 1] = frame if next_frame != null and not duplicate: cur_anim.frames[item_index] = placeholder if id == 1: if item_index - 1 < 0: return var prev_frame = cur_anim.frames[item_index - 1] var placeholder = prev_frame.duplicate(false) cur_anim.frames[item_index - 1] = frame cur_anim.frames[item_index] = placeholder set_frames_tree() ) tree.item_edited.connect(func(): var text = tree_root.get_text(0) if text.length() < 1: tree_root.set_text(0, spritesheet_animations[selected_animation].name) return for anim in spritesheet_animations: if anim.name == text: tree_root.set_text(0, spritesheet_animations[selected_animation].name) return spritesheet_animations[selected_animation].name = text set_anims_tree() ) tree.item_selected.connect(func(): var item = tree.get_selected() selected_frame = item.get_index() set_region_view() set_three_stooges() ) func set_frames_tree() -> void: var tree:Tree = frames_tree.get_node("Tree") var tree_root = tree.get_root() tree_root.set_editable(0, false) tree_root.set_text(0, "No animation selected") for child in tree_root.get_children(): child.free() if spritesheet_animations.size() <= selected_animation: return var cur_anim = spritesheet_animations[selected_animation] tree_root.set_editable(0, true) tree_root.set_text(0, cur_anim.name) var frames = cur_anim.frames for frame in spritesheet_frames: var child = tree.create_item(tree_root) child.set_cell_mode(0, TreeItem.CELL_MODE_STRING) child.set_editable(0, false) child.set_text(0, "Frame #" + str(frame + 1)) child.set_cell_mode(1, TreeItem.CELL_MODE_STRING) child.set_editable(1, false) child.set_text(1, "640x480") child.set_text_alignment(1, HORIZONTAL_ALIGNMENT_RIGHT) child.add_button(1, arrow_down_icon, 0) child.add_button(1, arrow_up_icon, 1) if frame >= frames.size() - 1: child.set_button_disabled(1, 0, true) if frame <= 0: child.set_button_disabled(1, 1, true) if frames.size() <= frame or frames[frame] == null: child.set_text(1, "Empty") child.set_button_disabled(1, 1, true) child.set_button_disabled(1, 0, true) else: child.set_text(1, str(frames[frame].size.x) + "x" + str(frames[frame].size.y)) tree.set_selected(tree_root.get_child(selected_frame), 0) set_region_view() func update_current_frame_child() -> void: var tree:Tree = frames_tree.get_node("Tree") var tree_root = tree.get_root() var current_frame = tree_root.get_child(selected_frame) if spritesheet_animations.size() <= selected_animation: return var frames = spritesheet_animations[selected_animation].frames var frame = selected_frame if current_frame == null: return if frames.size() <= frame or frames[frame] == null: current_frame.set_text(1, "Empty") else: current_frame.set_text(1, str(frames[frame].size.x) + "x" + str(frames[frame].size.y)) func fuck_with_region_view() -> void: var frame:ReferenceRect = region_viewport.get_node("ReferenceRect") var character:TextureRect = frame.get_node("Character") character.texture.atlas = preview_image rg_oskin_prev_char.texture.atlas = preview_image rg_oskin_next_char.texture.atlas = preview_image func set_region_view() -> void: var frame:ReferenceRect = region_viewport.get_node("ReferenceRect") var character:TextureRect = frame.get_node("Character") var character_frame:ReferenceRect = frame.get_node("ReferenceRect") var atlas:AtlasTexture = character.texture if spritesheet_animations.size() <= selected_animation: rg_oskin_prev_char.position = Vector2.ZERO rg_oskin_prev_char.texture.region = Rect2(0,0,1,1) rg_oskin_next_char.position = Vector2.ZERO rg_oskin_next_char.texture.region = Rect2(0,0,1,1) character.position = Vector2.ZERO character_frame.position = Vector2.ZERO character_frame.size = Vector2.ZERO atlas.region = Rect2(0,0,1,1) return var cur_anim = spritesheet_animations[selected_animation] if cur_anim.frames.size() < selected_frame or cur_anim.frames[selected_frame] == null: character.position = Vector2.ZERO character_frame.position = Vector2.ZERO character_frame.size = Vector2.ZERO atlas.region = Rect2(0,0,1,1) return var cur_frame = cur_anim.frames[selected_frame] if selected_frame - 1 < 0 or cur_anim.frames[selected_frame - 1] == null: rg_oskin_prev_char.position = Vector2.ZERO rg_oskin_prev_char.texture.region = Rect2(0,0,1,1) else: var prev_frame = cur_anim.frames[selected_frame - 1] rg_oskin_prev_char.position = prev_frame.frame_pos rg_oskin_prev_char.texture.region = Rect2(prev_frame.pos, prev_frame.size) if selected_frame + 1 >= cur_anim.frames.size() or cur_anim.frames[selected_frame + 1] == null: rg_oskin_next_char.position = Vector2.ZERO rg_oskin_next_char.texture.region = Rect2(0,0,1,1) else: var next_frame = cur_anim.frames[selected_frame + 1] rg_oskin_next_char.position = next_frame.frame_pos rg_oskin_next_char.texture.region = Rect2(next_frame.pos, next_frame.size) character.position = cur_frame.frame_pos character_frame.position = cur_frame.frame_pos character_frame.size = cur_frame.size atlas.region = Rect2(cur_frame.pos, cur_frame.size) frame.size = spritesheet_size frame.position = (region_viewport.size / 2) - Vector2i(frame.size / 2) var ts_dont_change_internal = false func set_three_stooges() -> void: var character:TextureRect = region_viewport.get_node("ReferenceRect/Character") var atlas:AtlasTexture = character.texture ts_dont_change_internal = true set_vector(region_pos, atlas.region.position) set_vector(region_size, atlas.region.size) set_vector(sprite_offset, character.position) ts_dont_change_internal = false func get_property_on_current_frame(prop:String): var cur_anim = spritesheet_animations[selected_animation] if cur_anim.frames.size() < 1: return null var cur_frame = cur_anim.frames[selected_frame] if cur_frame == null: return null return cur_frame[prop] func set_property_on_current_frame(prop:String, value:Variant): var cur_anim = spritesheet_animations[selected_animation] if cur_anim.frames.size() < 1: return var cur_frame = cur_anim.frames[selected_frame] if cur_frame == null: cur_anim.frames[selected_frame] = { pos = Vector2.ZERO, size = Vector2.ONE, frame_pos = Vector2.ZERO } cur_frame = cur_anim.frames[selected_frame] cur_frame[prop] = value