513 lines
17 KiB
GDScript
513 lines
17 KiB
GDScript
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
|