From 7e44e3d08abb0dfa65b1d78cae33a35b55625927 Mon Sep 17 00:00:00 2001 From: MeowcaTheoRange Date: Sun, 1 Dec 2024 01:36:12 -0600 Subject: [PATCH] XML tuner, config presets, fix reset bind saving bug --- assets/BOYFRIEND.png.import | 28 +- assets/background_checker.png | Bin 0 -> 149 bytes assets/background_checker.png.import | 34 ++ assets/config/bg.tres | 4 + assets/config/section_header.tres | 9 + assets/icons/arrow_down.png | Bin 0 -> 426 bytes assets/icons/arrow_down.png.import | 34 ++ assets/icons/arrow_up.png | Bin 0 -> 422 bytes assets/icons/arrow_up.png.import | 34 ++ assets/icons/checker.tres | 13 + assets/icons/close.png | Bin 289 -> 436 bytes assets/icons/frame_open.png | Bin 0 -> 423 bytes assets/icons/frame_open.png.import | 34 ++ assets/icons/frames_open.png | Bin 0 -> 426 bytes assets/icons/frames_open.png.import | 34 ++ assets/icons/preset_use.png | Bin 0 -> 432 bytes assets/icons/preset_use.png.import | 34 ++ assets/test.tres | 3 + export_presets.cfg | 2 +- project.godot | 3 +- scenes/config.tscn | 19 +- scenes/config_presets.tscn | 83 ++++ scenes/main.tscn | 10 +- scenes/xml_editor.tscn | 477 ++++++++++++++++++++++ scenes/xml_wizard.tscn | 136 +++++++ scripts/Config.gd | 7 + scripts/GlobalConfigPreInit.gd | 14 +- scripts/SpritesheetParser.gd | 24 +- scripts/animator.gd | 2 +- scripts/config/config_presets.gd | 31 ++ scripts/config/config_window.gd | 18 +- scripts/config/config_window_presets.gd | 76 ++++ scripts/menu.gd | 2 + scripts/window.gd | 10 + scripts/xmle/form_handler.gd | 513 ++++++++++++++++++++++++ scripts/xmle/load.gd | 35 ++ scripts/xmle/save.gd | 22 + scripts/xmle/xml_editor_window.gd | 17 + scripts/xmle/xml_wizard_window.gd | 17 + 39 files changed, 1706 insertions(+), 73 deletions(-) create mode 100644 assets/background_checker.png create mode 100644 assets/background_checker.png.import create mode 100644 assets/config/bg.tres create mode 100644 assets/config/section_header.tres create mode 100644 assets/icons/arrow_down.png create mode 100644 assets/icons/arrow_down.png.import create mode 100644 assets/icons/arrow_up.png create mode 100644 assets/icons/arrow_up.png.import create mode 100644 assets/icons/checker.tres create mode 100644 assets/icons/frame_open.png create mode 100644 assets/icons/frame_open.png.import create mode 100644 assets/icons/frames_open.png create mode 100644 assets/icons/frames_open.png.import create mode 100644 assets/icons/preset_use.png create mode 100644 assets/icons/preset_use.png.import create mode 100644 assets/test.tres create mode 100644 scenes/config_presets.tscn create mode 100644 scenes/xml_editor.tscn create mode 100644 scenes/xml_wizard.tscn create mode 100644 scripts/config/config_presets.gd create mode 100644 scripts/config/config_window_presets.gd create mode 100644 scripts/xmle/form_handler.gd create mode 100644 scripts/xmle/load.gd create mode 100644 scripts/xmle/save.gd create mode 100644 scripts/xmle/xml_editor_window.gd create mode 100644 scripts/xmle/xml_wizard_window.gd diff --git a/assets/BOYFRIEND.png.import b/assets/BOYFRIEND.png.import index dd313cf..06fc805 100644 --- a/assets/BOYFRIEND.png.import +++ b/assets/BOYFRIEND.png.import @@ -1,34 +1,14 @@ [remap] -importer="texture" -type="CompressedTexture2D" +importer="image" +type="Image" uid="uid://b6x537wsl3853" -path="res://.godot/imported/BOYFRIEND.png-39cdb7ee73d5290fc66317ae894c5c49.ctex" -metadata={ -"vram_texture": false -} +path="res://.godot/imported/BOYFRIEND.png-39cdb7ee73d5290fc66317ae894c5c49.image" [deps] source_file="res://assets/BOYFRIEND.png" -dest_files=["res://.godot/imported/BOYFRIEND.png-39cdb7ee73d5290fc66317ae894c5c49.ctex"] +dest_files=["res://.godot/imported/BOYFRIEND.png-39cdb7ee73d5290fc66317ae894c5c49.image"] [params] -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 diff --git a/assets/background_checker.png b/assets/background_checker.png new file mode 100644 index 0000000000000000000000000000000000000000..c29b71bb79305084022250a59a94abf6325160a5 GIT binary patch literal 149 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1qucK`l=g#}EtuNS%G}E0G|-o|Ns93nW2X+*8wS}k|4j}{|ryJ8+ZYEoCO|{#S9F5he4R}c>anM zprBZZYeY$Kep*R+Vo@qXKw@TIiJqTph=Qq};rHh6lYuI>q(*qAd3tIwZ~!^13{s4& z42(b)$V6!<8{}3EMrN=$6Oe7l$i%=0q@#d1vz-Mjo&jWoKmrhh^uuTrD;a=(oxsk( z0+cr}GB#jb05KD!o^=7loJl}72rvOnVFIfRva|rQpt=kV3_!BUOVo}8`TacuWZHVV zIEH8hukCT7F-cd9A5SbJ6wrFM7p=fS?83{1OP2NS%G}E0G|-o|Ns93nW2X+*8wS}k|4j}{|ryJ8+ZYEoCO|{#S9F5he4R}c>anM zprBZZYeY$Kep*R+Vo@qXKw@TIiJqTph=Qq};rHh6lYuI>q(*qAd3tIwZ~!^13{s4& z42(b)$V6!<8{}3EMrN=$6Oe7l$i%=0q@#d1vz-Mjo&jWoKmrhh^uuTrD;a=(oxsk( z0+cr}GB#jb05KD!o^=7loJl}72rvOnVFIfRva|rQpt=kV3_!BUOVo}8`TacuWLkQ< zIEH8hZ|!vCV=&-2^yUBjc|mhdzEfu7aNS%G}E0G|-o|Ns93nW2X+*8wS}k|4j}{|ryJ8+ZYEoCO|{#S9F5hd`K7RKu$Q zC@5Cq8c`CQpH@UL)0gU?@In*4mh^MN5@1H;xks=;9(<2_ydT-G@yGywp3i(>Eq delta 244 zcmVLp6A=a#h(ST u8#d@E^Bm5?6PlwFDQ||@`El_$m*oT3GV?wR&iHfy0000NS%G}E0G|-o|Ns93nW2X+*8wS}k|4j}{|ryJ8+ZYEoCO|{#S9F5hd`K7RKu$Q zC@5Cq8c`CQpH@Pt_SAE)%5%49g}@4_vt1 g%+cG-a6iX8_N+pN(+Z+l??5Jcy85}Sb4q9e049%FP5=M^ literal 0 HcmV?d00001 diff --git a/assets/icons/frame_open.png.import b/assets/icons/frame_open.png.import new file mode 100644 index 0000000..f7b5a40 --- /dev/null +++ b/assets/icons/frame_open.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dinhxs3byoi3s" +path="res://.godot/imported/frame_open.png-10f0b7b27982187741fba18357825080.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/icons/frame_open.png" +dest_files=["res://.godot/imported/frame_open.png-10f0b7b27982187741fba18357825080.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/icons/frames_open.png b/assets/icons/frames_open.png new file mode 100644 index 0000000000000000000000000000000000000000..7c752ad58626c58d9e99fd3d7da67eb348511896 GIT binary patch literal 426 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJV{wqX6T`Z5GB1G~mUKs7M+SzC z{oH>NS%G}E0G|-o|Ns93nW2X+*8wS}k|4j}{|ryJ8+ZYEoCO|{#S9F5hd`K7RKu$Q zC@5Cq8c`CQpH@j@|Mt44W!{RMS-3$WrciO?0T)T}8Sw{AUC;esHWC&wvAg-@ jO6DVtt7o)(>cj=5lX)069f{)P2N~+=>gTe~DWM4fU;kRh literal 0 HcmV?d00001 diff --git a/assets/icons/frames_open.png.import b/assets/icons/frames_open.png.import new file mode 100644 index 0000000..0c74b65 --- /dev/null +++ b/assets/icons/frames_open.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c5qf8o3yefmij" +path="res://.godot/imported/frames_open.png-642496cc6c7b46c1d0dd7b48beb9f71b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/icons/frames_open.png" +dest_files=["res://.godot/imported/frames_open.png-642496cc6c7b46c1d0dd7b48beb9f71b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/icons/preset_use.png b/assets/icons/preset_use.png new file mode 100644 index 0000000000000000000000000000000000000000..30a9c0ae3d97f70074d4fd0257b5c46554b46119 GIT binary patch literal 432 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJV{wqX6T`Z5GB1G~mUKs7M+SzC z{oH>NS%G}E0G|-o|Ns93nW2X+*8wS}k|4j}{|ryJ8+ZYEoCO|{#S9F5he4R}c>anM zprBZZYeY$Kep*R+Vo@qXKw@TIiJqTph=Qq};rHh6lYuI>q(*qAd3tIwZ~!^13{s4& z42(b)$V6!<8{}3EMrN=$6Oe7l$i%=0q@#d1vz-Mjo&jWoKmrhh^uuTrD;a=(oxsk( z0+cr}GB#jb05KD!o^=7loJl}72rvOnVFIfRva|rQpt=kV3_!BUOVo}8`TacuWIB7g zIEH8hm!4+iV^H8=I`HTJa_%Duxf?fG6<-xToWv9*q`^@3%;tcwdEOzO1soSbf6Y!~ n+Op4HL4)mB!R3U5(i2wR3SPi4 Dictionary: dict.anim_needs_enable = self.anim_needs_enable dict.anim_binds = self.anim_binds.duplicate(true) return dict + +func save_to_file(file:String) -> void: + var dict = self.format_config() + var config = ConfigFile.new() + for key in dict: + config.set_value("FunkPanion", key, dict[key]) + config.save(file) diff --git a/scripts/GlobalConfigPreInit.gd b/scripts/GlobalConfigPreInit.gd index 78221b0..fc1d3ca 100644 --- a/scripts/GlobalConfigPreInit.gd +++ b/scripts/GlobalConfigPreInit.gd @@ -5,7 +5,6 @@ extends Config func _ready() -> void: load_from_file() bind_keys() - print(InputMap.get_actions()) func bind_keys() -> void: bind_all_keys(self.bpm_increase, "bpm_increase") @@ -32,24 +31,21 @@ func bind_all_keys(array, action) -> void: InputMap.action_add_event(action, ev) func reset_settings() -> void: - var config = ConfigFile.new() GlobalConfig.clone_config(Config.new()) - config.save("user://config.cfg") - - bind_keys() + GlobalConfig.save_to_file() -func save_to_file() -> void: +func save_to_file(file:String = "user://config.cfg") -> void: var dict = self.format_config() var config = ConfigFile.new() for key in dict: config.set_value("FunkPanion", key, dict[key]) - config.save("user://config.cfg") + config.save(file) bind_keys() -func load_from_file() -> void: +func load_from_file(file:String = "user://config.cfg") -> void: var config = ConfigFile.new() - var err = config.load("user://config.cfg") + var err = config.load(file) if err != OK: return diff --git a/scripts/SpritesheetParser.gd b/scripts/SpritesheetParser.gd index 0cedc20..7eefb4c 100644 --- a/scripts/SpritesheetParser.gd +++ b/scripts/SpritesheetParser.gd @@ -33,7 +33,7 @@ func parse_spritesheet(path): if not animations.has(frame_name): animations[frame_name] = [] - + var array = animations[frame_name] var frame_data = {} @@ -83,7 +83,7 @@ func get_props_of_animation_frame(anim:String, frameno:int): margin = Rect2(-frame.frame_pos, frame.frame_size - frame.size) } -static func load_image(path: String): +static func load_image(path: String) -> Image: if path.begins_with('res'): return load(path) else: @@ -92,10 +92,24 @@ static func load_image(path: String): print(str("Could not load image at: ",path)) return var buffer = file.get_buffer(file.get_length()) - var image = Image.new() + var image:Image = Image.new() var error = image.load_png_from_buffer(buffer) if error != OK: print(str("Could not load image at: ",path," with error: ",error)) return - var texture = ImageTexture.create_from_image(image) - return texture + return image + +static func spritesheet_to_xml(anims:Array, size: Vector2i, imgpath: String): + var xml = "\n" + xml += "\n" % imgpath + for animation in anims: + var animation_stack = "" + for frame in animation.frames.size(): + var cur_frame = animation.frames[frame] + if cur_frame == null: continue + var frame_id = animation.name + str(frame).pad_zeros(4) + animation_stack += "\t\n" % \ + [ frame_id, cur_frame.pos.x, cur_frame.pos.y, cur_frame.size.x, cur_frame.size.y, -cur_frame.frame_pos.x, -cur_frame.frame_pos.y, size.x, size.y ] + xml += animation_stack + xml += "" + return xml diff --git a/scripts/animator.gd b/scripts/animator.gd index c5886b9..842d61d 100644 --- a/scripts/animator.gd +++ b/scripts/animator.gd @@ -9,7 +9,7 @@ var active_anims = [] func _ready() -> void: texture = AtlasTexture.new() - texture.atlas = SpritesheetParser.load_image(GlobalConfig.spritesheet_image) + texture.atlas = ImageTexture.create_from_image(SpritesheetParser.load_image(GlobalConfig.spritesheet_image)) texture.region = Rect2(0, 0, 1, 1) spritesheet_parser.parse_spritesheet(GlobalConfig.spritesheet_data) diff --git a/scripts/config/config_presets.gd b/scripts/config/config_presets.gd new file mode 100644 index 0000000..ff04ed8 --- /dev/null +++ b/scripts/config/config_presets.gd @@ -0,0 +1,31 @@ +extends Button + +var config_presets = preload("res://scenes/config_presets.tscn") + +@onready var root = get_tree().root +@onready var form = $"../.." + +func _pressed() -> void: + var preset_window = Popup.new() + var cfps = config_presets.instantiate() + cfps.conf_reference = form.temp_config + preset_window.add_child(cfps) + root.add_child(preset_window) + preset_window.popup_centered_ratio() + + var close = func(): + preset_window.hide() + root.remove_child(preset_window) + preset_window.queue_free() + cfps.queue_free() + + cfps.reset.connect(func(): + GlobalConfig.reset_settings() + close.call() + get_tree().reload_current_scene() + ) + cfps.close.connect(close) + cfps.preset_use.connect(func(): + close.call() + get_tree().reload_current_scene() + ) diff --git a/scripts/config/config_window.gd b/scripts/config/config_window.gd index af80758..4875f24 100644 --- a/scripts/config/config_window.gd +++ b/scripts/config/config_window.gd @@ -1,31 +1,17 @@ extends ScrollContainer -var main_window -var exiting_to_config = false +var main_window: Window # Called when the node enters the scene tree for the first time. func _enter_tree() -> void: - print("enter tree") main_window = get_tree().root main_window.borderless = false main_window.always_on_top = false main_window.transparent = false main_window.min_size = Vector2i(640, 480) main_window.size = Vector2i(640, 480) + main_window.title = "FunkPanion Config" ProjectSettings.set_setting("display/window/subwindows/embed_subwindows", false) - -func _exit_tree() -> void: - if exiting_to_config: return - main_window.borderless = true - main_window.always_on_top = true - main_window.transparent = true - main_window.min_size = Vector2i(100, 100) - ProjectSettings.set_setting("display/window/subwindows/embed_subwindows", true) func _on_back_button_pressed() -> void: get_tree().change_scene_to_file("res://scenes/main.tscn") - -func _on_reset_pressed() -> void: - GlobalConfig.reset_settings() - exiting_to_config = true - get_tree().reload_current_scene() diff --git a/scripts/config/config_window_presets.gd b/scripts/config/config_window_presets.gd new file mode 100644 index 0000000..4effe2c --- /dev/null +++ b/scripts/config/config_window_presets.gd @@ -0,0 +1,76 @@ +extends ScrollContainer + +signal reset +signal preset_use +signal close + +var dir +var conf_reference:Config + +@onready var tree = $"MarginContainer/VBoxContainer/Tree" +var tree_root: TreeItem +@onready var addnew_name = $"MarginContainer/VBoxContainer/AddNew/name" +@onready var addnew_switch = $"MarginContainer/VBoxContainer/AddNew/switch" + +@onready var close_icon = load("res://assets/icons/close.png") +@onready var preset_use_icon = load("res://assets/icons/preset_use.png") + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + tree.set_column_title(0, "Name") + tree.set_column_title(1, "Created") + tree_root = tree.create_item() + + dir = DirAccess.open("user://presets/") + + tree.button_clicked.connect(func(item:TreeItem, column:int, id:int, _mbi:int): + if column == 1: + if id == 0: + GlobalConfig.load_from_file(item.get_metadata(0)) + GlobalConfig.save_to_file() + preset_use.emit() + if id == 1: + dir.remove(item.get_metadata(0)) + refresh_tree() + ) + + addnew_switch.pressed.connect(func(): + if addnew_name.text.length() > 0 and addnew_name.text.is_valid_filename(): + addnew_name.text = addnew_name.text.simplify_path() + if addnew_name.text.get_extension() != "cfg": + addnew_name.text = addnew_name.text.get_basename() + ".cfg" + conf_reference.save_to_file(dir.get_current_dir().path_join(addnew_name.text)) + addnew_name.text = "" + refresh_tree() + ) + + refresh_tree() + +func refresh_tree() -> void: + for child in tree_root.get_children(): + child.free() + + if not dir: + DirAccess.make_dir_absolute("user://presets/") + dir = DirAccess.open("user://presets/") + + dir.list_dir_begin() + var file_name = dir.get_next() + while file_name != "": + if file_name.get_extension() == "cfg": + var child:TreeItem = tree.create_item(tree_root) + var abs_file_name = dir.get_current_dir().path_join(file_name) + child.set_cell_mode(0, TreeItem.CELL_MODE_STRING) + child.set_text(0, file_name.get_basename()) + var mod_time = FileAccess.get_modified_time(abs_file_name) + child.set_text(1, Time.get_datetime_string_from_unix_time(mod_time, true)) + child.add_button(1, preset_use_icon) + child.add_button(1, close_icon) + child.set_metadata(0, abs_file_name) + file_name = dir.get_next() + +func _on_close_button_pressed() -> void: + close.emit() + +func _on_reset_button_pressed() -> void: + reset.emit() diff --git a/scripts/menu.gd b/scripts/menu.gd index 258ab9b..618d19b 100644 --- a/scripts/menu.gd +++ b/scripts/menu.gd @@ -6,6 +6,8 @@ func _index_pressed(idx: int): if idx == 0: get_tree().change_scene_to_file("res://scenes/config.tscn") elif idx == 1: + get_tree().change_scene_to_file("res://scenes/xml_editor.tscn") + elif idx == 2: get_tree().quit() # Called when the node enters the scene tree for the first time. diff --git a/scripts/window.gd b/scripts/window.gd index e8b5982..7b9deac 100644 --- a/scripts/window.gd +++ b/scripts/window.gd @@ -24,6 +24,16 @@ var character_opacity = 100.0 var numerator = int(GlobalConfig.tempo_numerator) var change_array = [] +func _enter_tree() -> void: + main_window = get_tree().root + main_window.borderless = true + main_window.always_on_top = true + main_window.transparent = true + main_window.min_size = Vector2i(100, 100) + main_window.title = "FunkPanion" + main_window.set_mode(Window.MODE_WINDOWED) + ProjectSettings.set_setting("display/window/subwindows/embed_subwindows", true) + func _ready() -> void: horiz_align.alignment = GlobalConfig.spritesheet_anchor[0] vert_align.alignment = GlobalConfig.spritesheet_anchor[1] diff --git a/scripts/xmle/form_handler.gd b/scripts/xmle/form_handler.gd new file mode 100644 index 0000000..4cc3706 --- /dev/null +++ b/scripts/xmle/form_handler.gd @@ -0,0 +1,513 @@ +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 diff --git a/scripts/xmle/load.gd b/scripts/xmle/load.gd new file mode 100644 index 0000000..2214e83 --- /dev/null +++ b/scripts/xmle/load.gd @@ -0,0 +1,35 @@ +extends MenuButton + +signal load_xml(path: String) +signal load_image(path: String) + +@onready var root = get_tree().root + +func _ready() -> void: + var popup = self.get_popup() + popup.index_pressed.connect(func(idx: int): + if idx < 0: return + var file_dialog = FileDialog.new() + file_dialog.use_native_dialog = true + file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE + if idx == 0: + file_dialog.add_filter("*.xml", "Sparrow spritesheet") + elif idx == 1: + file_dialog.add_filter("*.png", "Image") + file_dialog.access = FileDialog.ACCESS_FILESYSTEM + file_dialog.current_path = OS.get_data_dir().path_join("FunkPanion/") + file_dialog.file_selected.connect(func(path_string: String): + var localized = path_string.simplify_path().replace(OS.get_data_dir().path_join("FunkPanion/"), "user://") + if idx == 0: + load_xml.emit(localized) + elif idx == 1: + load_image.emit(localized) + file_dialog.queue_free() + ) + file_dialog.canceled.connect(func(path_string: String): + root.remove_child(file_dialog) + file_dialog.queue_free() + ) + root.add_child(file_dialog) + file_dialog.popup() + ) diff --git a/scripts/xmle/save.gd b/scripts/xmle/save.gd new file mode 100644 index 0000000..137d03a --- /dev/null +++ b/scripts/xmle/save.gd @@ -0,0 +1,22 @@ +extends Button + +@onready var root = get_tree().root + +func save(content:String) -> void: + var file_dialog = FileDialog.new() + file_dialog.use_native_dialog = true + file_dialog.file_mode = FileDialog.FILE_MODE_SAVE_FILE + file_dialog.add_filter("*.xml", "Sparrow spritesheet") + file_dialog.access = FileDialog.ACCESS_FILESYSTEM + file_dialog.current_path = OS.get_data_dir().path_join("FunkPanion/") + file_dialog.file_selected.connect(func(path_string: String): + var file = FileAccess.open(path_string, FileAccess.WRITE) + file.store_string(content) + file_dialog.queue_free() + ) + file_dialog.canceled.connect(func(path_string: String): + root.remove_child(file_dialog) + file_dialog.queue_free() + ) + root.add_child(file_dialog) + file_dialog.popup() diff --git a/scripts/xmle/xml_editor_window.gd b/scripts/xmle/xml_editor_window.gd new file mode 100644 index 0000000..f086af3 --- /dev/null +++ b/scripts/xmle/xml_editor_window.gd @@ -0,0 +1,17 @@ +extends Node + +var main_window + +# Called when the node enters the scene tree for the first time. +func _enter_tree() -> void: + main_window = get_tree().root + main_window.borderless = false + main_window.always_on_top = false + main_window.transparent = false + main_window.min_size = Vector2i(800, 600) + main_window.size = Vector2i(800, 600) + main_window.title = "FP XML Tuner" + ProjectSettings.set_setting("display/window/subwindows/embed_subwindows", false) + +func _on_exit_pressed() -> void: + get_tree().change_scene_to_file("res://scenes/main.tscn") diff --git a/scripts/xmle/xml_wizard_window.gd b/scripts/xmle/xml_wizard_window.gd new file mode 100644 index 0000000..dc7534b --- /dev/null +++ b/scripts/xmle/xml_wizard_window.gd @@ -0,0 +1,17 @@ +extends Node + +var main_window + +# Called when the node enters the scene tree for the first time. +func _enter_tree() -> void: + main_window = get_tree().root + main_window.borderless = false + main_window.always_on_top = false + main_window.transparent = false + main_window.min_size = Vector2i(480, 270) + main_window.size = Vector2i(480, 270) + main_window.title = "Image Atlas Wizard" + ProjectSettings.set_setting("display/window/subwindows/embed_subwindows", false) + +func _on_exit_pressed() -> void: + get_tree().change_scene_to_file("res://scenes/main.tscn")