who the fuck knows

This commit is contained in:
Dan 2024-05-08 17:06:15 +01:00
parent 1c33ea2f59
commit 1da411cacd
31 changed files with 1372 additions and 78 deletions

View file

@ -0,0 +1,176 @@
@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()
paths.sort()
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)

View file

@ -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

View file

@ -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)

View file

@ -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))
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

View file

@ -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"]