From 1a96b31c7efc055d4fb842af6790065ea884e089 Mon Sep 17 00:00:00 2001 From: SchimmelSpreu83 Date: Mon, 23 Mar 2026 20:40:46 +0100 Subject: [PATCH] Added AnimPlayerRefactor addon --- .../lib/anim_player_refactor.gd | 207 +++++++++++++++ .../lib/anim_player_refactor.gd.uid | 1 + .../anim_player_refactor/lib/editor_util.gd | 64 +++++ .../lib/editor_util.gd.uid | 1 + game/addons/anim_player_refactor/plugin.cfg | 7 + game/addons/anim_player_refactor/plugin.gd | 144 +++++++++++ .../addons/anim_player_refactor/plugin.gd.uid | 1 + .../scenes/autocomplete/autocomplete.gd | 17 ++ .../scenes/autocomplete/autocomplete.gd.uid | 1 + .../autocomplete/autocomplete_option.gd | 50 ++++ .../autocomplete/autocomplete_option.gd.uid | 1 + .../inspector_button/inspector_button.gd | 37 +++ .../inspector_button/inspector_button.gd.uid | 1 + .../components/anim_player_tree.gd | 193 ++++++++++++++ .../components/anim_player_tree.gd.uid | 1 + .../refactor_dialogue/components/edit_info.gd | 26 ++ .../components/edit_info.gd.uid | 1 + .../components/node_select.gd | 30 +++ .../components/node_select.gd.uid | 1 + .../refactor_dialogue/refactor_dialogue.gd | 235 ++++++++++++++++++ .../refactor_dialogue.gd.uid | 1 + .../refactor_dialogue/refactor_dialogue.tscn | 157 ++++++++++++ 22 files changed, 1177 insertions(+) create mode 100644 game/addons/anim_player_refactor/lib/anim_player_refactor.gd create mode 100644 game/addons/anim_player_refactor/lib/anim_player_refactor.gd.uid create mode 100644 game/addons/anim_player_refactor/lib/editor_util.gd create mode 100644 game/addons/anim_player_refactor/lib/editor_util.gd.uid create mode 100644 game/addons/anim_player_refactor/plugin.cfg create mode 100644 game/addons/anim_player_refactor/plugin.gd create mode 100644 game/addons/anim_player_refactor/plugin.gd.uid create mode 100644 game/addons/anim_player_refactor/scenes/autocomplete/autocomplete.gd create mode 100644 game/addons/anim_player_refactor/scenes/autocomplete/autocomplete.gd.uid create mode 100644 game/addons/anim_player_refactor/scenes/autocomplete/autocomplete_option.gd create mode 100644 game/addons/anim_player_refactor/scenes/autocomplete/autocomplete_option.gd.uid create mode 100644 game/addons/anim_player_refactor/scenes/inspector_button/inspector_button.gd create mode 100644 game/addons/anim_player_refactor/scenes/inspector_button/inspector_button.gd.uid create mode 100644 game/addons/anim_player_refactor/scenes/refactor_dialogue/components/anim_player_tree.gd create mode 100644 game/addons/anim_player_refactor/scenes/refactor_dialogue/components/anim_player_tree.gd.uid create mode 100644 game/addons/anim_player_refactor/scenes/refactor_dialogue/components/edit_info.gd create mode 100644 game/addons/anim_player_refactor/scenes/refactor_dialogue/components/edit_info.gd.uid create mode 100644 game/addons/anim_player_refactor/scenes/refactor_dialogue/components/node_select.gd create mode 100644 game/addons/anim_player_refactor/scenes/refactor_dialogue/components/node_select.gd.uid create mode 100644 game/addons/anim_player_refactor/scenes/refactor_dialogue/refactor_dialogue.gd create mode 100644 game/addons/anim_player_refactor/scenes/refactor_dialogue/refactor_dialogue.gd.uid create mode 100644 game/addons/anim_player_refactor/scenes/refactor_dialogue/refactor_dialogue.tscn diff --git a/game/addons/anim_player_refactor/lib/anim_player_refactor.gd b/game/addons/anim_player_refactor/lib/anim_player_refactor.gd new file mode 100644 index 0000000..9e533e4 --- /dev/null +++ b/game/addons/anim_player_refactor/lib/anim_player_refactor.gd @@ -0,0 +1,207 @@ +## Core utility class to handle all refactoring logic + +const EditorUtil := preload("res://addons/anim_player_refactor/lib/editor_util.gd") + +var _editor_plugin: EditorPlugin +var _undo_redo: EditorUndoRedoManager + +func _init(editor_plugin: EditorPlugin) -> void: + _editor_plugin = editor_plugin + _undo_redo = editor_plugin.get_undo_redo() + +# Nodes +func rename_node_path(anim_player: AnimationPlayer, old: NodePath, new: NodePath): + if old == new: + return + + _undo_redo.create_action("Refactor node tracks", UndoRedo.MERGE_ALL, anim_player) + + _foreach_animation(anim_player, func(animation: Animation): + for i in animation.get_track_count(): + var path := animation.track_get_path(i) + var node_path := path.get_concatenated_names() + + if node_path == old.get_concatenated_names(): + var new_path := new.get_concatenated_names() + ":" + path.get_concatenated_subnames() + animation.track_set_path(i, NodePath(new_path)) + + _undo_redo.add_do_property(animation, "tracks/%d/path" % i, new_path) + _undo_redo.add_undo_property(animation, "tracks/%d/path" % i, path) + ) + + _undo_redo.commit_action() + + +func remove_node_path(anim_player: AnimationPlayer, node_path: NodePath): + _undo_redo.create_action("Remove node tracks", UndoRedo.MERGE_ALL, anim_player) + + _foreach_animation_restore(anim_player, _undo_redo, func(animation: Animation): + var removed_tracks = 0 + + for i in range(animation.get_track_count() - 1, -1, -1): + var path = animation.track_get_path(i) + + if NodePath(path.get_concatenated_names()) == node_path: + removed_tracks += 1 + _undo_redo.add_do_method(animation, &'remove_track', i) + + return removed_tracks + ) + + _undo_redo.commit_action() + + +# Tracks +func rename_track_path(anim_player: AnimationPlayer, old: NodePath, new: NodePath): + if old == new: + return + + _undo_redo.create_action("Refactor track paths", UndoRedo.MERGE_ALL, anim_player) + + _foreach_animation(anim_player, func(animation: Animation): + for i in animation.get_track_count(): + var path = animation.track_get_path(i) + + if path == old: + animation.track_set_path(i, new) + + _undo_redo.add_do_property(animation, "tracks/%d/path" % i, new) + _undo_redo.add_undo_property(animation, "tracks/%d/path" % i, old) + ) + + _undo_redo.commit_action() + + +func remove_track_path(anim_player: AnimationPlayer, property_path: NodePath): + _undo_redo.create_action("Remove tracks", UndoRedo.MERGE_ALL, anim_player) + + _foreach_animation_restore(anim_player, _undo_redo, func(animation: Animation): + var removed_tracks = 0 + + for i in range(animation.get_track_count() - 1, -1, -1): + var path = animation.track_get_path(i) + + if path == property_path: + removed_tracks += 1 + _undo_redo.add_do_method(animation, &'remove_track', i) + + return removed_tracks + ) + + _undo_redo.commit_action() + +# Method tracks +func rename_method(anim_player, old: NodePath, new: NodePath): + if old == new: + return + + var node_path := NodePath(old.get_concatenated_names()) + var old_method := old.get_concatenated_subnames() + var new_method := new.get_concatenated_subnames() + + _undo_redo.create_action("Rename method keys", UndoRedo.MERGE_ALL, anim_player) + + _foreach_animation(anim_player, func(animation: Animation): + for i in animation.get_track_count(): + if (animation.track_get_type(i) == Animation.TYPE_METHOD and animation.track_get_path(i) == node_path): + for j in animation.track_get_key_count(i): + var name := animation.method_track_get_name(i, j) + if name == old_method: + var old_method_params := { + "method": old_method, + "args": animation.method_track_get_params(i, j) + } + + var method_params := { + "method": new_method, + "args": animation.method_track_get_params(i, j) + } + + _undo_redo.add_do_method(animation, &'track_set_key_value', i, j, method_params) + _undo_redo.add_undo_method(animation, &'track_set_key_value', i, j, old_method_params) + ) + + _undo_redo.commit_action() + + +func remove_method(anim_player: AnimationPlayer, method_path: NodePath): + _undo_redo.create_action("Remove method keys", UndoRedo.MERGE_ALL, anim_player) + + _foreach_animation_restore(anim_player, _undo_redo, func(animation: Animation): + for i in animation.get_track_count(): + if ( + animation.track_get_type(i) == Animation.TYPE_METHOD + and StringName(animation.track_get_path(i)) == method_path.get_concatenated_names() + ): + for j in range(animation.track_get_key_count(i) - 1, -1, -1): + var name := animation.method_track_get_name(i, j) + if name == method_path.get_concatenated_subnames(): + _undo_redo.add_do_method(animation, &'track_remove_key', i, j) + return 0 + ) + + _undo_redo.commit_action() + + +# Root +func change_root(anim_player: AnimationPlayer, new_path: NodePath): + var current_root: Node = anim_player.get_node(anim_player.root_node) + var new_root: Node = anim_player.get_node_or_null(new_path) + + if new_root == null: + return + + _undo_redo.create_action("Change animation player root", UndoRedo.MERGE_ALL, anim_player) + + _foreach_animation(anim_player, func(animation: Animation): + for i in animation.get_track_count(): + var path := animation.track_get_path(i) + var node := current_root.get_node_or_null(NodePath(path.get_concatenated_names())) + + if node == null: + push_warning("Invalid path: %s. Skipping root change." % path) + continue + + var updated_path = str(new_root.get_path_to(node)) + ":" + path.get_concatenated_subnames() + + _undo_redo.add_do_property(animation, "tracks/%d/path" % i, updated_path) + _undo_redo.add_undo_property(animation, "tracks/%d/path" % i, path) + ) + + _undo_redo.add_do_property(anim_player, "root_node", new_path) + _undo_redo.add_undo_property(anim_player, "root_node", anim_player.root_node) + + _undo_redo.commit_action() + + + +# Helper methods + +## Iterates over all animations in the animation player +static func _foreach_animation(anim_player: AnimationPlayer, callback: Callable): + for lib_name in anim_player.get_animation_library_list(): + var lib := anim_player.get_animation_library(lib_name) + for animation_name in lib.get_animation_list(): + var animation := lib.get_animation(animation_name) + callback.call(animation) + + +## Iterates over all animations in the animation player and adds a full revert to the undo stack +## Useful for do actions that remove tracks +static func _foreach_animation_restore(anim_player: AnimationPlayer, undo_redo: EditorUndoRedoManager, callback: Callable): + for lib_name in anim_player.get_animation_library_list(): + var lib := anim_player.get_animation_library(lib_name) + for animation_name in lib.get_animation_list(): + var animation := lib.get_animation(animation_name) + + var old_anim := animation.duplicate(true) + + var removed_tracked = callback.call(animation) + + for i in range(animation.get_track_count() - 1 - removed_tracked, -1, -1): + undo_redo.add_undo_method(animation, &'remove_track', i) + + for i in range(old_anim.get_track_count()): + undo_redo.add_undo_method(old_anim, &'copy_track', i, animation) + + undo_redo.add_undo_reference(old_anim) diff --git a/game/addons/anim_player_refactor/lib/anim_player_refactor.gd.uid b/game/addons/anim_player_refactor/lib/anim_player_refactor.gd.uid new file mode 100644 index 0000000..8a23741 --- /dev/null +++ b/game/addons/anim_player_refactor/lib/anim_player_refactor.gd.uid @@ -0,0 +1 @@ +uid://d3tmljj8n86y6 diff --git a/game/addons/anim_player_refactor/lib/editor_util.gd b/game/addons/anim_player_refactor/lib/editor_util.gd new file mode 100644 index 0000000..131ef0f --- /dev/null +++ b/game/addons/anim_player_refactor/lib/editor_util.gd @@ -0,0 +1,64 @@ +# Utility class for parsing and hacking the editor + +## Find menu button to add option to +static func find_animation_menu_button(node: Node) -> MenuButton: + var animation_editor := find_editor_control_with_class(node, "AnimationPlayerEditor") + if animation_editor: + return find_editor_control_with_class( + animation_editor, + "MenuButton", + func(node): return node.text == "Animation" + ) + + return null + + +## General utility to find a control in the editor using an iterative search +static func find_editor_control_with_class( + base: Control, + p_class_name: StringName, + condition := func(node: Node): return true + ) -> Node: + if base.get_class() == p_class_name and condition.call(base): + return base + + for child in base.get_children(): + if not child is Control: + continue + + var found = find_editor_control_with_class(child, p_class_name, condition) + if found: + return found + + return null + + + +# Finds the active animation player (either pinned or selected) +static func find_active_anim_player(base_control: Control, scene_tree: Tree) -> AnimationPlayer: + var find_anim_player_recursive: Callable + + var pin_icon := scene_tree.get_theme_icon("Pin", "EditorIcons") + + var stack: Array[TreeItem] = [] + stack.append(scene_tree.get_root()) + + while not stack.is_empty(): + var current := stack.pop_back() as TreeItem + + # Check for pin icon + for i in current.get_button_count(0): + if current.get_button(0, i) == pin_icon: + var node := base_control.get_node_or_null(current.get_metadata(0)) + if node is AnimationPlayer: + return node + + if current.is_selected(0): + var node := base_control.get_node_or_null(current.get_metadata(0)) + if node is AnimationPlayer: + return node + + for i in range(current.get_child_count() - 1, -1, -1): + stack.push_back(current.get_child(i)) + + return null diff --git a/game/addons/anim_player_refactor/lib/editor_util.gd.uid b/game/addons/anim_player_refactor/lib/editor_util.gd.uid new file mode 100644 index 0000000..508029a --- /dev/null +++ b/game/addons/anim_player_refactor/lib/editor_util.gd.uid @@ -0,0 +1 @@ +uid://c5ntcwonx5ygw diff --git a/game/addons/anim_player_refactor/plugin.cfg b/game/addons/anim_player_refactor/plugin.cfg new file mode 100644 index 0000000..39798d5 --- /dev/null +++ b/game/addons/anim_player_refactor/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="Animation Player Refactor" +description="Refactoring tools for AnimationPlayer libraries. Adds \"Refactor\" menu option under AnimationPlayer > Animation." +author="poohcom1" +version="0.1.8" +script="plugin.gd" diff --git a/game/addons/anim_player_refactor/plugin.gd b/game/addons/anim_player_refactor/plugin.gd new file mode 100644 index 0000000..d29481f --- /dev/null +++ b/game/addons/anim_player_refactor/plugin.gd @@ -0,0 +1,144 @@ +@tool +extends EditorPlugin + +const RefactorDialogue := preload("scenes/refactor_dialogue/refactor_dialogue.gd") + +const AnimPlayerInspectorButton := preload("scenes/inspector_button/inspector_button.gd") + +const EditorUtil := preload("lib/editor_util.gd") + +var activate_button: AnimPlayerInspectorButton +var refactor_dialogue: RefactorDialogue + +var anim_menu_button: MenuButton + +var _last_anim_player: AnimationPlayer +const SCENE_TREE_IDX := 0 +var _scene_tree: Tree + +func _enter_tree() -> void: + # Create dialogue + refactor_dialogue = load("res://addons/anim_player_refactor/scenes/refactor_dialogue/refactor_dialogue.tscn").instantiate() + get_editor_interface().get_base_control().add_child(refactor_dialogue) + refactor_dialogue.init(self) + # Create menu button + _add_refactor_option(func(): + refactor_dialogue.popup_centered() + refactor_dialogue.reset_size() + ) + + +func _exit_tree() -> void: + if refactor_dialogue and refactor_dialogue.is_inside_tree(): + get_editor_interface().get_base_control().remove_child(refactor_dialogue) + refactor_dialogue.queue_free() + + _remove_refactor_option() + + +func _handles(object: Object) -> bool: + if object is AnimationPlayer: + _last_anim_player = object + return false + + +# Editor methods +func get_anim_player() -> AnimationPlayer: + # Check for pinned animation + if not _scene_tree: + var _scene_tree_editor = EditorUtil.find_editor_control_with_class( + get_editor_interface().get_base_control(), + "SceneTreeEditor" + ) + + if not _scene_tree_editor: + push_error("[Animation Refactor] Could not find scene tree editor. Please report this.") + return null + + _scene_tree = _scene_tree_editor.get_child(SCENE_TREE_IDX) + + if not _scene_tree: + push_error("[Animation Refactor] Could not find scene tree editor. Please report this.") + return null + + var found_anim := EditorUtil.find_active_anim_player( + get_editor_interface().get_base_control(), + _scene_tree + ) + + if found_anim: + return found_anim + + # Get latest edited + return _last_anim_player + + +# Plugin buttons + +const TOOL_REFACTOR := 999 +const TOOL_ANIM_LIBRARY := 1 + +func _add_refactor_option(on_pressed: Callable): + var base_control := get_editor_interface().get_base_control() + if not anim_menu_button: + anim_menu_button = EditorUtil.find_animation_menu_button(base_control) + if not anim_menu_button: + push_error("Could not find Animation menu button. Please report this issue.") + return + + # Remove item up to "Manage Animations..." + var menu_popup := anim_menu_button.get_popup() + var items := [] + var count := menu_popup.item_count - 1 + + while count >= 0 and menu_popup.get_item_id(count) != TOOL_ANIM_LIBRARY: + if menu_popup.is_item_separator(count): + items.append({}) + else: + items.append({ + "shortcut": menu_popup.get_item_shortcut(count), + "id": menu_popup.get_item_id(count), + "icon": menu_popup.get_item_icon(count) + }) + + menu_popup.remove_item(count) + count -= 1 + + # Add refactor item + menu_popup.add_icon_item( + base_control.get_theme_icon(&"Reload", &"EditorIcons"), + "Refactor", + TOOL_REFACTOR, + ) + + # Re-add items + for i in range(items.size() - 1, -1, -1): + var item: Dictionary = items[i] + + if not item.is_empty(): + menu_popup.add_shortcut(item.shortcut, item.id) + menu_popup.set_item_icon(menu_popup.get_item_index(item.id), item.icon) + else: + menu_popup.add_separator() + + menu_popup.notification(NOTIFICATION_TRANSLATION_CHANGED) + + menu_popup.id_pressed.connect(_on_menu_button_pressed) + + +func _remove_refactor_option(): + if not anim_menu_button: + return + + var base_control := get_editor_interface().get_base_control() + + var menu_popup := anim_menu_button.get_popup() + menu_popup.remove_item(menu_popup.get_item_index(TOOL_REFACTOR)) + + menu_popup.id_pressed.disconnect(_on_menu_button_pressed) + + +func _on_menu_button_pressed(id: int): + if id == TOOL_REFACTOR: + refactor_dialogue.popup_centered() + diff --git a/game/addons/anim_player_refactor/plugin.gd.uid b/game/addons/anim_player_refactor/plugin.gd.uid new file mode 100644 index 0000000..f3aacc8 --- /dev/null +++ b/game/addons/anim_player_refactor/plugin.gd.uid @@ -0,0 +1 @@ +uid://b5c5vfjnrbm4n diff --git a/game/addons/anim_player_refactor/scenes/autocomplete/autocomplete.gd b/game/addons/anim_player_refactor/scenes/autocomplete/autocomplete.gd new file mode 100644 index 0000000..9fafb2b --- /dev/null +++ b/game/addons/anim_player_refactor/scenes/autocomplete/autocomplete.gd @@ -0,0 +1,17 @@ +extends CodeEdit + + +func _ready() -> void: + code_completion_enabled = true + add_code_completion_option(CodeEdit.KIND_MEMBER, "Test", "test") + add_code_completion_option(CodeEdit.KIND_MEMBER, "Boo", "boo") + code_completion_prefixes = ["t", "b"] + + code_completion_requested.connect(func(): + add_code_completion_option(CodeEdit.KIND_MEMBER, "Test", "test") + add_code_completion_option(CodeEdit.KIND_MEMBER, "Boo", "boo") + update_code_completion_options(true) + ) + + text_changed.connect(func(): request_code_completion(true)) + diff --git a/game/addons/anim_player_refactor/scenes/autocomplete/autocomplete.gd.uid b/game/addons/anim_player_refactor/scenes/autocomplete/autocomplete.gd.uid new file mode 100644 index 0000000..ef5f78b --- /dev/null +++ b/game/addons/anim_player_refactor/scenes/autocomplete/autocomplete.gd.uid @@ -0,0 +1 @@ +uid://bnlapygcgbesi diff --git a/game/addons/anim_player_refactor/scenes/autocomplete/autocomplete_option.gd b/game/addons/anim_player_refactor/scenes/autocomplete/autocomplete_option.gd new file mode 100644 index 0000000..7bd41c5 --- /dev/null +++ b/game/addons/anim_player_refactor/scenes/autocomplete/autocomplete_option.gd @@ -0,0 +1,50 @@ +## Autocomplete Class +extends OptionButton + +## LineEdit.text_change_rejected +signal text_change_rejected(rejected_substring: String) +## LineEdit.text_changed +signal text_changed(new_text: String) +## LineEdit.text_submitted +signal text_submitted(new_text: String) + +## LineEdit component +var edit: LineEdit = LineEdit.new() + +@export var get_autocomplete_options: Callable = func(text: String): return [] + +func _ready() -> void: + focus_mode = Control.FOCUS_NONE + edit.custom_minimum_size = size + get_popup().unfocusable = true + + add_child(edit) + edit.reset_size() + + edit.text_change_rejected.connect(func(arg): text_change_rejected.emit(arg)) + edit.text_changed.connect(func(arg): text_changed.emit(arg)) + edit.text_submitted.connect(func(arg): text_submitted.emit(arg)) + + edit.text_changed.connect(_update_options) + + edit.focus_entered.connect(_update_options) + edit.focus_exited.connect(clear) + + get_autocomplete_options = func(text: String): + return [ + "test", + "ashina", + "hello" + ].filter(func(el: String): return el.contains(text)) + +func _update_options(text: String = edit.text): + clear() + var options = get_autocomplete_options.call(text) + + for option in options: + if typeof(option) == TYPE_STRING: + add_item(option) + + show_popup() + + diff --git a/game/addons/anim_player_refactor/scenes/autocomplete/autocomplete_option.gd.uid b/game/addons/anim_player_refactor/scenes/autocomplete/autocomplete_option.gd.uid new file mode 100644 index 0000000..5ae783a --- /dev/null +++ b/game/addons/anim_player_refactor/scenes/autocomplete/autocomplete_option.gd.uid @@ -0,0 +1 @@ +uid://21lmomqahrgq diff --git a/game/addons/anim_player_refactor/scenes/inspector_button/inspector_button.gd b/game/addons/anim_player_refactor/scenes/inspector_button/inspector_button.gd new file mode 100644 index 0000000..50b8a17 --- /dev/null +++ b/game/addons/anim_player_refactor/scenes/inspector_button/inspector_button.gd @@ -0,0 +1,37 @@ +extends EditorInspectorPlugin +signal button_clicked + +var button: Button +var button_text: String + +func _init(text: String) -> void: + button_text = text + +func _can_handle(object: Object) -> bool: + return object is AnimationPlayer + +func _parse_end(object: Object) -> void: + button = Button.new() + button.text = button_text + button.pressed.connect(func(): button_clicked.emit()) + + var margins := MarginContainer.new() + margins.add_theme_constant_override("margin_top", 8) + margins.add_theme_constant_override("margin_left", 16) + margins.add_theme_constant_override("margin_bottom", 8) + margins.add_theme_constant_override("margin_right", 16) + margins.add_child(button) + + var container = VBoxContainer.new() + container.add_theme_constant_override("separation", 0) + container.add_child(HSeparator.new()) + container.add_child(margins) + + var container_margins := MarginContainer.new() + container_margins.add_theme_constant_override("margin_top", 8) + container_margins.add_theme_constant_override("margin_left", 4) + container_margins.add_theme_constant_override("margin_bottom", 8) + container_margins.add_theme_constant_override("margin_right", 4) + container_margins.add_child(container) + + add_custom_control(container_margins) diff --git a/game/addons/anim_player_refactor/scenes/inspector_button/inspector_button.gd.uid b/game/addons/anim_player_refactor/scenes/inspector_button/inspector_button.gd.uid new file mode 100644 index 0000000..8dbe300 --- /dev/null +++ b/game/addons/anim_player_refactor/scenes/inspector_button/inspector_button.gd.uid @@ -0,0 +1 @@ +uid://o6j17spm86vx diff --git a/game/addons/anim_player_refactor/scenes/refactor_dialogue/components/anim_player_tree.gd b/game/addons/anim_player_refactor/scenes/refactor_dialogue/components/anim_player_tree.gd new file mode 100644 index 0000000..e4921c9 --- /dev/null +++ b/game/addons/anim_player_refactor/scenes/refactor_dialogue/components/anim_player_tree.gd @@ -0,0 +1,193 @@ +@tool +extends Tree + +signal rendered + +const EditInfo := preload("edit_info.gd") + +@export var edittable_items := false + + +func _ready() -> void: + reset_size() + + +func render(editor_plugin: EditorPlugin, anim_player: AnimationPlayer) -> void: + clear() + + # Get paths + var animations = anim_player.get_animation_list() + var root_node := anim_player.get_node(anim_player.root_node) + + var track_paths := {} # Dictionary[NodePath, Dictionary[NodePath, EditInfo]] + + # Get EditInfo data + for anim_name in animations: + var animation := anim_player.get_animation(anim_name) + + for i in animation.get_track_count(): + var path := animation.track_get_path(i) + var type := animation.track_get_type(i) + + var node_path := NodePath(path.get_concatenated_names()) + var property_path := path.get_concatenated_subnames() + var node := root_node.get_node_or_null(node_path) + + var edit_infos: Array[EditInfo] = [] + + if not node_path in track_paths: + track_paths[node_path] = {} + match type: + Animation.TYPE_METHOD: + for j in animation.track_get_key_count(i): + var method_path = NodePath( + ( + path.get_concatenated_names() + + ":" + + animation.method_track_get_name(i, j) + ) + ) + + var edit_info = EditInfo.new( + EditInfo.Type.METHOD_TRACK, method_path, node_path, node, [anim_name] + ) + + edit_infos.append(edit_info) + _: + if not property_path.is_empty(): + var edit_info = EditInfo.new( + EditInfo.Type.VALUE_TRACK, path, node_path, node, [anim_name] + ) + + edit_infos.append(edit_info) + + # Combine + for info in edit_infos: + if not StringName(info.path) in track_paths[node_path]: + track_paths[node_path][StringName(info.path)] = info + else: + for name in info.animation_names: + if name in track_paths[node_path][StringName(info.path)].animation_names: continue + track_paths[node_path][StringName(info.path)].animation_names.append(name) + + # Sort + var paths := track_paths.keys() + var get_nearest_valid_node = func(path: NodePath) -> Node: + var current_path = path + while current_path.get_name_count() > 0: + var found = root_node.get_node_or_null(current_path) + if found: return found + # Pop the last name off the path and try again + var names = Array(current_path.get_names()) + names.pop_back() + current_path = NodePath("/".join(names)) + return root_node + + paths.sort_custom(func(a, b): + var node_a = get_nearest_valid_node.call(a) + var node_b = get_nearest_valid_node.call(b) + if node_a != node_b: + return !node_a.is_greater_than(node_b) + return str(a) < str(b) + ) + + var tree_root: TreeItem = create_item() + hide_root = true + + # Get icons + var gui := editor_plugin.get_editor_interface().get_base_control() + + # Render + for path in paths: + var node := root_node.get_node_or_null(path) + var icon := gui.get_theme_icon(node.get_class() if node != null else "", "EditorIcons") + + var path_item = create_item(tree_root) + path_item.set_editable(0, edittable_items) + if edittable_items: + path_item.set_text(0, path) + if path.get_concatenated_names() == ".." and node: + path_item.set_suffix(0, "(" + node.name + ")") + else: + path_item.set_text(0, node.name if node else path) + path_item.set_icon(0, icon) + path_item.set_metadata(0, EditInfo.new(EditInfo.Type.NODE, path, path, node, [])) + path_item.add_button(0, gui.get_theme_icon("Edit", "EditorIcons")) + path_item.add_button(0, gui.get_theme_icon("Remove", "EditorIcons")) + + var property_paths: Array = track_paths[path].keys() + property_paths.sort() + + for property_path in property_paths: + var info: EditInfo = track_paths[path][property_path] + var edit_type = EditInfo.Type.VALUE_TRACK + var icon_type = "KeyValue" + var invalid = false + var property := info.path.get_concatenated_subnames() + if node == null: + invalid = true + icon_type = "" + elif node.has_method(StringName(property)): + icon_type = "KeyCall" + elif str(info.path) in node or node.get_indexed(NodePath(property)) != null: + pass + else: + invalid = true + icon_type = "" + + var property_item = create_item(path_item) + property_item.set_editable(0, edittable_items) + property_item.set_text(0, property) + property_item.set_icon(0, gui.get_theme_icon(icon_type, "EditorIcons")) + property_item.set_metadata(0, info) + property_item.add_button(0, gui.get_theme_icon("Edit", "EditorIcons")) + property_item.add_button(0, gui.get_theme_icon("Remove", "EditorIcons")) + + if invalid: + property_item.set_custom_color(0, Color.RED) + property_item.set_tooltip_text(0, "Possibly invalid value: %s" % info.path) + rendered.emit() + + +func set_filter(filter: String): + var item_stack := [] + var visited := [] + + item_stack.append(get_root()) + + # Post-order traversal + while not item_stack.is_empty(): + var current: TreeItem = item_stack[item_stack.size() - 1] + var children = current.get_children() if current else [] + + var children_all_visited := true + var child_visible := false + + for child in children: + children_all_visited = children_all_visited and child in visited + child_visible = child_visible or child.visible + + if children_all_visited: + item_stack.pop_back() + if current: + if current == get_root() or filter.is_empty() or child_visible: + current.visible = true + else: + current.visible = current.get_text(0).to_lower().contains(filter.to_lower()) + visited.append(current) + else: + item_stack += children + + +## Class to cache heirarchy of nodes +## Unused +class TreeNode: + var node: Node + var path: String + var children: Dictionary + var parent: TreeNode + + func debug(level = 0): + print(" - ".repeat(level) + node.name) + for name in children: + children[name].debug(level + 1) diff --git a/game/addons/anim_player_refactor/scenes/refactor_dialogue/components/anim_player_tree.gd.uid b/game/addons/anim_player_refactor/scenes/refactor_dialogue/components/anim_player_tree.gd.uid new file mode 100644 index 0000000..e4e82c4 --- /dev/null +++ b/game/addons/anim_player_refactor/scenes/refactor_dialogue/components/anim_player_tree.gd.uid @@ -0,0 +1 @@ +uid://bk2ganon2sasp diff --git a/game/addons/anim_player_refactor/scenes/refactor_dialogue/components/edit_info.gd b/game/addons/anim_player_refactor/scenes/refactor_dialogue/components/edit_info.gd new file mode 100644 index 0000000..c738759 --- /dev/null +++ b/game/addons/anim_player_refactor/scenes/refactor_dialogue/components/edit_info.gd @@ -0,0 +1,26 @@ +## Data class for storing information on tracks +extends Object + +enum Type { VALUE_TRACK, METHOD_TRACK, NODE } + +## Type of info being edited +var type: Type + +## Full path to property. Same as node_path if type is NODE +var path: NodePath + +## Full path to node +var node_path: NodePath + +## Cached node +var node: Node + +## Animations the track is used in +var animation_names: Array[String] = [] + +func _init(type: Type, path: NodePath, node_path: NodePath, node: Node, animation_names: Array[String]) -> void: + self.type = type + self.path = path + self.node = node + self.node_path = node_path + self.animation_names = animation_names diff --git a/game/addons/anim_player_refactor/scenes/refactor_dialogue/components/edit_info.gd.uid b/game/addons/anim_player_refactor/scenes/refactor_dialogue/components/edit_info.gd.uid new file mode 100644 index 0000000..8c96a20 --- /dev/null +++ b/game/addons/anim_player_refactor/scenes/refactor_dialogue/components/edit_info.gd.uid @@ -0,0 +1 @@ +uid://cdgiasppawevq diff --git a/game/addons/anim_player_refactor/scenes/refactor_dialogue/components/node_select.gd b/game/addons/anim_player_refactor/scenes/refactor_dialogue/components/node_select.gd new file mode 100644 index 0000000..c3655a9 --- /dev/null +++ b/game/addons/anim_player_refactor/scenes/refactor_dialogue/components/node_select.gd @@ -0,0 +1,30 @@ +@tool +extends Tree + +var _editor_plugin: EditorPlugin +var _gui: Control + +func init(editor_plugin: EditorPlugin): + _editor_plugin = editor_plugin + _gui = editor_plugin.get_editor_interface().get_base_control() + +func render(anim_player: AnimationPlayer): + clear() + + _create_items(null, anim_player, anim_player.owner) + + +func _create_items(parent: TreeItem, anim_player: AnimationPlayer, node: Node): + var icon := _gui.get_theme_icon(node.get_class(), "EditorIcons") + + var item := create_item(parent) + item.set_text(0, node.name) + item.set_icon(0, icon) + item.set_metadata(0, anim_player.get_path_to(node)) + + if anim_player.get_path_to(node) == anim_player.root_node: + item.select(0) + scroll_to_item(item) + + for child in node.get_children(): + _create_items(item, anim_player, child) diff --git a/game/addons/anim_player_refactor/scenes/refactor_dialogue/components/node_select.gd.uid b/game/addons/anim_player_refactor/scenes/refactor_dialogue/components/node_select.gd.uid new file mode 100644 index 0000000..133405c --- /dev/null +++ b/game/addons/anim_player_refactor/scenes/refactor_dialogue/components/node_select.gd.uid @@ -0,0 +1 @@ +uid://4alsx2krjeo8 diff --git a/game/addons/anim_player_refactor/scenes/refactor_dialogue/refactor_dialogue.gd b/game/addons/anim_player_refactor/scenes/refactor_dialogue/refactor_dialogue.gd new file mode 100644 index 0000000..a0e294f --- /dev/null +++ b/game/addons/anim_player_refactor/scenes/refactor_dialogue/refactor_dialogue.gd @@ -0,0 +1,235 @@ +@tool +extends AcceptDialog + +const CustomEditorPlugin := preload("res://addons/anim_player_refactor/plugin.gd") +const EditorUtil := preload("res://addons/anim_player_refactor/lib/editor_util.gd") + +const AnimPlayerRefactor = preload("res://addons/anim_player_refactor/lib/anim_player_refactor.gd") +const AnimPlayerTree := preload("components/anim_player_tree.gd") +const EditInfo := preload("components/edit_info.gd") + +const NodeSelect := preload("components/node_select.gd") + +var _editor_plugin: CustomEditorPlugin +var _editor_interface: EditorInterface +var _anim_player_refactor: AnimPlayerRefactor +var _anim_player: AnimationPlayer + +@onready var anim_player_tree: AnimPlayerTree = $%AnimPlayerTree + +@onready var change_root: Button = $%ChangeRoot + +@onready var edit_dialogue: ConfirmationDialog = $%EditDialogue +@onready var edit_dialogue_input: LineEdit = $%EditInput +@onready var edit_dialogue_button: Button = $%EditDialogueButton # Just for pretty icon display +@onready var edit_full_path_toggle: CheckButton = $%EditFullPathToggle +@onready var edit_anim_list: Label = $%EditAnimationList + +@onready var node_select_dialogue: ConfirmationDialog = $%NodeSelectDialogue +@onready var node_select: NodeSelect = $%NodeSelect + +@onready var confirmation_dialogue: ConfirmationDialog = $%ConfirmationDialog + + +var is_full_path: bool: + set(val): edit_full_path_toggle.button_pressed = val + get: return edit_full_path_toggle.button_pressed + + +func init(editor_plugin: CustomEditorPlugin) -> void: + _editor_plugin = editor_plugin + _editor_interface = editor_plugin.get_editor_interface() + _anim_player_refactor = AnimPlayerRefactor.new(_editor_plugin) + node_select.init(_editor_plugin) + + +func _ready() -> void: + wrap_controls = true + about_to_popup.connect(render) + + +func render(): + _anim_player = _editor_plugin.get_anim_player() + + if not _anim_player or not _anim_player is AnimationPlayer: + push_error("AnimationPlayer is null or invalid") + return + + # Render track tree + anim_player_tree.render(_editor_plugin, _anim_player) + + # Render root node button + var root_node: Node = _anim_player.get_node(_anim_player.root_node) + var node_path := str(_anim_player.owner.get_path_to(root_node)) + if node_path == ".": + node_path = _anim_player.owner.name + else: + node_path = _anim_player.owner.name + "/" + node_path + + change_root.text = "%s (Change)" % node_path + change_root.icon = _editor_interface.get_base_control().get_theme_icon( + root_node.get_class(), "EditorIcons" + ) + + +# Rename +enum Action { Rename, Delete } +var _current_info: EditInfo + +func _on_tree_activated(): + _current_info = anim_player_tree.get_selected().get_metadata(0) + _on_action(Action.Rename) + + +func _on_tree_button_clicked(item: TreeItem, column: int, id: int, mouse_button_index: int): + _current_info = item.get_metadata(column) + if id == 0: + _on_action(Action.Rename) + elif id == 1: + _on_action(Action.Delete) + + +func _on_action(action: Action): + if action == Action.Rename: + _render_edit_dialogue() + edit_dialogue.popup_centered() + edit_dialogue_input.grab_focus() + edit_dialogue_input.caret_column = edit_dialogue_input.text.length() + elif action == Action.Delete: + var track_path = _current_info.path + var anim_player = _editor_plugin.get_anim_player() + var node = anim_player\ + .get_node(anim_player.root_node)\ + .get_node_or_null(_current_info.path) + + if node: + track_path = node.name + var property := _current_info.path.get_concatenated_subnames() + if not property.is_empty(): + track_path += ":" + property + + var msg = 'Delete all tracks with the path "%s"?' % track_path + + if _current_info.type == EditInfo.Type.NODE: + msg = 'Delete tracks belonging to the node "%s"?' % track_path + + _show_confirmation(msg, _remove) + + +func _render_edit_dialogue(): + var info := _current_info + + if info.type == EditInfo.Type.METHOD_TRACK: + is_full_path = false + edit_full_path_toggle.disabled = true + edit_full_path_toggle.visible = false + else: + edit_full_path_toggle.disabled = false + edit_full_path_toggle.visible = true + + if is_full_path: + edit_dialogue_input.text = info.path + else: + if info.type == EditInfo.Type.NODE: + edit_dialogue.title = "Rename node" + edit_dialogue_input.text = info.path.get_name(info.path.get_name_count() - 1) + elif info.type == EditInfo.Type.VALUE_TRACK: + edit_dialogue.title = "Rename Value" + edit_dialogue_input.text = info.path.get_concatenated_subnames() + elif info.type == EditInfo.Type.METHOD_TRACK: + edit_dialogue.title = "Rename method" + edit_dialogue_input.text = info.path.get_concatenated_subnames() + edit_dialogue_button.text = info.node_path + if str(info.node_path) in [".", ".."] and info.node: + # Show name for relatives paths + edit_dialogue_button.text = info.node.name + if info.node: + # Show icon + edit_dialogue_button.icon = _editor_interface.get_base_control().get_theme_icon( + info.node.get_class(), "EditorIcons" + ) + edit_anim_list.text = "" + for name in _current_info.animation_names: + edit_anim_list.text += " • " + name + "\n" + + +## Toggle full path and re-render edit dialogue +func _on_full_path_toggled(pressed: bool): + _render_edit_dialogue() + + +## Callback on rename +func _on_rename_confirmed(_arg0 = null): + var new := edit_dialogue_input.text + + edit_dialogue.hide() + if not _anim_player or not _anim_player is AnimationPlayer: + push_error("AnimationPlayer is null or invalid") + return + + if new.is_empty(): + return + + var info: EditInfo = _current_info + if info.type == EditInfo.Type.NODE: + var old := info.path + var new_path = new + if not is_full_path: + new_path = "" + for i in range(info.path.get_name_count() - 1): + new_path += info.path.get_name(i) + "/" + new_path += new + _anim_player_refactor.rename_node_path(_anim_player, old, NodePath(new_path)) + elif info.type == EditInfo.Type.VALUE_TRACK: + var old_path := info.path + var new_path := NodePath(new) + if not is_full_path: + new_path = info.node_path.get_concatenated_names() + ":" + new + _anim_player_refactor.rename_track_path(_anim_player, old_path, new_path) + elif info.type == EditInfo.Type.METHOD_TRACK: + var old_method := info.path + var new_method := NodePath( + info.path.get_concatenated_names() + ":" + new + ) + _anim_player_refactor.rename_method(_anim_player, old_method, new_method) + await get_tree().create_timer(0.1).timeout + render() + + +func _remove(): + var info: EditInfo = _current_info + match info.type: + EditInfo.Type.NODE: + _anim_player_refactor.remove_node_path(_anim_player, info.node_path) + EditInfo.Type.VALUE_TRACK: + _anim_player_refactor.remove_track_path(_anim_player, info.path) + EditInfo.Type.METHOD_TRACK: + _anim_player_refactor.remove_method(_anim_player, info.path) + await get_tree().create_timer(0.1).timeout + render() + + +# Change root +func _on_change_root_pressed(): + node_select_dialogue.popup_centered() + node_select.render(_editor_plugin.get_anim_player()) + + +func _on_node_select_confirmed(): + var path: NodePath = node_select.get_selected().get_metadata(0) + + _anim_player_refactor.change_root(_editor_plugin.get_anim_player(), path) + + await get_tree().create_timer(0.1).timeout + render() + + +# Confirmation +func _show_confirmation(text: String, on_confirmed: Callable): + for c in confirmation_dialogue.confirmed.get_connections(): + confirmation_dialogue.confirmed.disconnect(c.callable) + + confirmation_dialogue.confirmed.connect(on_confirmed, CONNECT_ONE_SHOT) + confirmation_dialogue.popup_centered() + confirmation_dialogue.dialog_text = text + diff --git a/game/addons/anim_player_refactor/scenes/refactor_dialogue/refactor_dialogue.gd.uid b/game/addons/anim_player_refactor/scenes/refactor_dialogue/refactor_dialogue.gd.uid new file mode 100644 index 0000000..f684b96 --- /dev/null +++ b/game/addons/anim_player_refactor/scenes/refactor_dialogue/refactor_dialogue.gd.uid @@ -0,0 +1 @@ +uid://ft8r8earybjw diff --git a/game/addons/anim_player_refactor/scenes/refactor_dialogue/refactor_dialogue.tscn b/game/addons/anim_player_refactor/scenes/refactor_dialogue/refactor_dialogue.tscn new file mode 100644 index 0000000..cc1dae8 --- /dev/null +++ b/game/addons/anim_player_refactor/scenes/refactor_dialogue/refactor_dialogue.tscn @@ -0,0 +1,157 @@ +[gd_scene load_steps=4 format=3 uid="uid://cyfxysds8uhnx"] + +[ext_resource type="Script" path="res://addons/anim_player_refactor/scenes/refactor_dialogue/refactor_dialogue.gd" id="1_nkqdl"] +[ext_resource type="Script" path="res://addons/anim_player_refactor/scenes/refactor_dialogue/components/anim_player_tree.gd" id="2_7pqfs"] +[ext_resource type="Script" path="res://addons/anim_player_refactor/scenes/refactor_dialogue/components/node_select.gd" id="3_87x4i"] + +[node name="RefactorDialogue" type="AcceptDialog"] +title = "Refactor Animations" +size = Vector2i(400, 599) +ok_button_text = "Close" +script = ExtResource("1_nkqdl") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +offset_left = 8.0 +offset_top = 8.0 +offset_right = 392.0 +offset_bottom = 550.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_constants/separation = 8 + +[node name="TreeContainer" type="VBoxContainer" parent="VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="VBoxContainer/TreeContainer"] +layout_mode = 2 +text = "Properties:" + +[node name="FilterInput" type="LineEdit" parent="VBoxContainer/TreeContainer"] +layout_mode = 2 +placeholder_text = "Filter..." +caret_blink = true +caret_blink_interval = 0.5 + +[node name="AnimPlayerTree" type="Tree" parent="VBoxContainer/TreeContainer"] +unique_name_in_owner = true +custom_minimum_size = Vector2(300, 400) +layout_mode = 2 +size_flags_vertical = 3 +hide_root = true +scroll_horizontal_enabled = false +script = ExtResource("2_7pqfs") + +[node name="RootNodeContainer" type="VBoxContainer" parent="VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="VBoxContainer/RootNodeContainer"] +layout_mode = 2 +text = "Root Node" + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/RootNodeContainer"] +layout_mode = 2 + +[node name="ChangeRoot" type="Button" parent="VBoxContainer/RootNodeContainer/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +text = "Change Root" + +[node name="EditDialogue" type="ConfirmationDialog" parent="."] +unique_name_in_owner = true +title = "Renaming" +position = Vector2i(0, 36) +size = Vector2i(230, 239) + +[node name="VBoxContainer" type="VBoxContainer" parent="EditDialogue"] +offset_left = 8.0 +offset_top = 8.0 +offset_right = 222.0 +offset_bottom = 190.0 + +[node name="HBoxContainer" type="HBoxContainer" parent="EditDialogue/VBoxContainer"] +layout_mode = 2 + +[node name="EditDialogueButton" type="Button" parent="EditDialogue/VBoxContainer/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +focus_mode = 0 + +[node name="EditInput" type="LineEdit" parent="EditDialogue/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_vertical = 0 + +[node name="HBoxContainer2" type="HBoxContainer" parent="EditDialogue/VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="EditDialogue/VBoxContainer/HBoxContainer2"] +layout_mode = 2 +text = "Used in:" + +[node name="EditFullPathToggle" type="CheckButton" parent="EditDialogue/VBoxContainer/HBoxContainer2"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 10 +text = "Edit full path" + +[node name="MarginContainer" type="MarginContainer" parent="EditDialogue/VBoxContainer"] +custom_minimum_size = Vector2(0, 100) +layout_mode = 2 + +[node name="ColorRect" type="ColorRect" parent="EditDialogue/VBoxContainer/MarginContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +color = Color(0, 0, 0, 0.211765) + +[node name="ScrollContainer" type="ScrollContainer" parent="EditDialogue/VBoxContainer/MarginContainer"] +layout_mode = 2 +size_flags_vertical = 3 +horizontal_scroll_mode = 0 + +[node name="MarginContainer" type="MarginContainer" parent="EditDialogue/VBoxContainer/MarginContainer/ScrollContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 2 +theme_override_constants/margin_top = 2 +theme_override_constants/margin_right = 2 +theme_override_constants/margin_bottom = 2 + +[node name="EditAnimationList" type="Label" parent="EditDialogue/VBoxContainer/MarginContainer/ScrollContainer/MarginContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Test +Test 2" + +[node name="NodeSelectDialogue" type="ConfirmationDialog" parent="."] +unique_name_in_owner = true +title = "Select a node..." +size = Vector2i(616, 557) +ok_button_text = "Change" + +[node name="NodeSelect" type="Tree" parent="NodeSelectDialogue"] +unique_name_in_owner = true +custom_minimum_size = Vector2(600, 500) +offset_left = 8.0 +offset_top = 8.0 +offset_right = 608.0 +offset_bottom = 508.0 +scroll_horizontal_enabled = false +script = ExtResource("3_87x4i") + +[node name="ConfirmationDialog" type="ConfirmationDialog" parent="."] +unique_name_in_owner = true +size = Vector2i(300, 200) +ok_button_text = "Delete" +dialog_autowrap = true + +[connection signal="about_to_popup" from="." to="VBoxContainer/TreeContainer/FilterInput" method="clear"] +[connection signal="text_changed" from="VBoxContainer/TreeContainer/FilterInput" to="VBoxContainer/TreeContainer/AnimPlayerTree" method="set_filter"] +[connection signal="button_clicked" from="VBoxContainer/TreeContainer/AnimPlayerTree" to="." method="_on_tree_button_clicked"] +[connection signal="item_activated" from="VBoxContainer/TreeContainer/AnimPlayerTree" to="." method="_on_tree_activated"] +[connection signal="rendered" from="VBoxContainer/TreeContainer/AnimPlayerTree" to="VBoxContainer/TreeContainer/FilterInput" method="clear"] +[connection signal="pressed" from="VBoxContainer/RootNodeContainer/HBoxContainer/ChangeRoot" to="." method="_on_change_root_pressed"] +[connection signal="confirmed" from="EditDialogue" to="." method="_on_rename_confirmed"] +[connection signal="text_submitted" from="EditDialogue/VBoxContainer/EditInput" to="." method="_on_rename_confirmed"] +[connection signal="toggled" from="EditDialogue/VBoxContainer/HBoxContainer2/EditFullPathToggle" to="." method="_on_full_path_toggled"] +[connection signal="confirmed" from="NodeSelectDialogue" to="." method="_on_node_select_confirmed"]