1/**************************************************************************/
2/* animation_blend_tree_editor_plugin.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "animation_blend_tree_editor_plugin.h"
32
33#include "core/config/project_settings.h"
34#include "core/input/input.h"
35#include "core/io/resource_loader.h"
36#include "core/os/keyboard.h"
37#include "editor/editor_inspector.h"
38#include "editor/editor_node.h"
39#include "editor/editor_scale.h"
40#include "editor/editor_settings.h"
41#include "editor/editor_string_names.h"
42#include "editor/editor_undo_redo_manager.h"
43#include "editor/gui/editor_file_dialog.h"
44#include "scene/animation/animation_player.h"
45#include "scene/gui/check_box.h"
46#include "scene/gui/menu_button.h"
47#include "scene/gui/panel.h"
48#include "scene/gui/progress_bar.h"
49#include "scene/gui/separator.h"
50#include "scene/gui/view_panner.h"
51#include "scene/main/window.h"
52#include "scene/resources/style_box_flat.h"
53
54void AnimationNodeBlendTreeEditor::add_custom_type(const String &p_name, const Ref<Script> &p_script) {
55 for (int i = 0; i < add_options.size(); i++) {
56 ERR_FAIL_COND(add_options[i].script == p_script);
57 }
58
59 AddOption ao;
60 ao.name = p_name;
61 ao.script = p_script;
62 add_options.push_back(ao);
63
64 _update_options_menu();
65}
66
67void AnimationNodeBlendTreeEditor::remove_custom_type(const Ref<Script> &p_script) {
68 for (int i = 0; i < add_options.size(); i++) {
69 if (add_options[i].script == p_script) {
70 add_options.remove_at(i);
71 return;
72 }
73 }
74
75 _update_options_menu();
76}
77
78void AnimationNodeBlendTreeEditor::_update_options_menu(bool p_has_input_ports) {
79 add_node->get_popup()->clear();
80 add_node->get_popup()->reset_size();
81 for (int i = 0; i < add_options.size(); i++) {
82 if (p_has_input_ports && add_options[i].input_port_count == 0) {
83 continue;
84 }
85 add_node->get_popup()->add_item(add_options[i].name, i);
86 }
87
88 Ref<AnimationNode> clipb = EditorSettings::get_singleton()->get_resource_clipboard();
89 if (clipb.is_valid()) {
90 add_node->get_popup()->add_separator();
91 add_node->get_popup()->add_item(TTR("Paste"), MENU_PASTE);
92 }
93 add_node->get_popup()->add_separator();
94 add_node->get_popup()->add_item(TTR("Load..."), MENU_LOAD_FILE);
95 use_position_from_popup_menu = false;
96}
97
98Size2 AnimationNodeBlendTreeEditor::get_minimum_size() const {
99 return Size2(10, 200);
100}
101
102void AnimationNodeBlendTreeEditor::_property_changed(const StringName &p_property, const Variant &p_value, const String &p_field, bool p_changing) {
103 AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree();
104 if (!tree) {
105 return;
106 }
107 updating = true;
108 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
109 undo_redo->create_action(vformat(TTR("Parameter Changed: %s"), p_property), UndoRedo::MERGE_ENDS);
110 undo_redo->add_do_property(tree, p_property, p_value);
111 undo_redo->add_undo_property(tree, p_property, tree->get(p_property));
112 undo_redo->add_do_method(this, "update_graph");
113 undo_redo->add_undo_method(this, "update_graph");
114 undo_redo->commit_action();
115 updating = false;
116}
117
118void AnimationNodeBlendTreeEditor::update_graph() {
119 if (updating || blend_tree.is_null()) {
120 return;
121 }
122
123 AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree();
124 if (!tree) {
125 return;
126 }
127
128 visible_properties.clear();
129
130 graph->set_scroll_offset(blend_tree->get_graph_offset() * EDSCALE);
131
132 graph->clear_connections();
133 //erase all nodes
134 for (int i = 0; i < graph->get_child_count(); i++) {
135 if (Object::cast_to<GraphNode>(graph->get_child(i))) {
136 memdelete(graph->get_child(i));
137 i--;
138 }
139 }
140
141 animations.clear();
142
143 List<StringName> nodes;
144 blend_tree->get_node_list(&nodes);
145
146 for (const StringName &E : nodes) {
147 GraphNode *node = memnew(GraphNode);
148 graph->add_child(node);
149
150 node->set_draggable(!read_only);
151
152 Ref<AnimationNode> agnode = blend_tree->get_node(E);
153 ERR_CONTINUE(!agnode.is_valid());
154
155 node->set_position_offset(blend_tree->get_node_position(E) * EDSCALE);
156
157 node->set_title(agnode->get_caption());
158 node->set_name(E);
159
160 int base = 0;
161 if (String(E) != "output") {
162 LineEdit *name = memnew(LineEdit);
163 name->set_text(E);
164 name->set_editable(!read_only);
165 name->set_expand_to_text_length_enabled(true);
166 node->add_child(name);
167 node->set_slot(0, false, 0, Color(), true, read_only ? -1 : 0, get_theme_color(SNAME("font_color"), SNAME("Label")));
168 name->connect("text_submitted", callable_mp(this, &AnimationNodeBlendTreeEditor::_node_renamed).bind(agnode), CONNECT_DEFERRED);
169 name->connect("focus_exited", callable_mp(this, &AnimationNodeBlendTreeEditor::_node_renamed_focus_out).bind(agnode), CONNECT_DEFERRED);
170 name->connect("text_changed", callable_mp(this, &AnimationNodeBlendTreeEditor::_node_rename_lineedit_changed), CONNECT_DEFERRED);
171 base = 1;
172 agnode->set_closable(true);
173 node->connect("close_request", callable_mp(this, &AnimationNodeBlendTreeEditor::_close_request).bind(E), CONNECT_DEFERRED);
174 }
175
176 for (int i = 0; i < agnode->get_input_count(); i++) {
177 Label *in_name = memnew(Label);
178 node->add_child(in_name);
179 in_name->set_text(agnode->get_input_name(i));
180 node->set_slot(base + i, true, read_only ? -1 : 0, get_theme_color(SNAME("font_color"), SNAME("Label")), false, 0, Color());
181 }
182
183 List<PropertyInfo> pinfo;
184 agnode->get_parameter_list(&pinfo);
185 for (const PropertyInfo &F : pinfo) {
186 if (!(F.usage & PROPERTY_USAGE_EDITOR)) {
187 continue;
188 }
189 String base_path = AnimationTreeEditor::get_singleton()->get_base_path() + String(E) + "/" + F.name;
190 EditorProperty *prop = EditorInspector::instantiate_property_editor(tree, F.type, base_path, F.hint, F.hint_string, F.usage);
191 if (prop) {
192 prop->set_read_only(read_only || (F.usage & PROPERTY_USAGE_READ_ONLY));
193 prop->set_object_and_property(tree, base_path);
194 prop->update_property();
195 prop->set_name_split_ratio(0);
196 prop->connect("property_changed", callable_mp(this, &AnimationNodeBlendTreeEditor::_property_changed));
197 node->add_child(prop);
198 visible_properties.push_back(prop);
199 }
200 }
201
202 node->connect("dragged", callable_mp(this, &AnimationNodeBlendTreeEditor::_node_dragged).bind(E));
203
204 if (AnimationTreeEditor::get_singleton()->can_edit(agnode)) {
205 node->add_child(memnew(HSeparator));
206 Button *open_in_editor = memnew(Button);
207 open_in_editor->set_text(TTR("Open Editor"));
208 open_in_editor->set_icon(get_editor_theme_icon(SNAME("Edit")));
209 node->add_child(open_in_editor);
210 open_in_editor->connect("pressed", callable_mp(this, &AnimationNodeBlendTreeEditor::_open_in_editor).bind(E), CONNECT_DEFERRED);
211 open_in_editor->set_h_size_flags(SIZE_SHRINK_CENTER);
212 }
213
214 if (agnode->has_filter()) {
215 node->add_child(memnew(HSeparator));
216 Button *inspect_filters = memnew(Button);
217 if (read_only) {
218 inspect_filters->set_text(TTR("Inspect Filters"));
219 } else {
220 inspect_filters->set_text(TTR("Edit Filters"));
221 }
222 inspect_filters->set_icon(get_editor_theme_icon(SNAME("AnimationFilter")));
223 node->add_child(inspect_filters);
224 inspect_filters->connect("pressed", callable_mp(this, &AnimationNodeBlendTreeEditor::_inspect_filters).bind(E), CONNECT_DEFERRED);
225 inspect_filters->set_h_size_flags(SIZE_SHRINK_CENTER);
226 }
227
228 Ref<AnimationNodeAnimation> anim = agnode;
229 if (anim.is_valid()) {
230 MenuButton *mb = memnew(MenuButton);
231 mb->set_text(anim->get_animation());
232 mb->set_icon(get_editor_theme_icon(SNAME("Animation")));
233 mb->set_disabled(read_only);
234 Array options;
235
236 node->add_child(memnew(HSeparator));
237 node->add_child(mb);
238
239 ProgressBar *pb = memnew(ProgressBar);
240
241 if (tree->has_node(tree->get_animation_player())) {
242 AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(tree->get_node(tree->get_animation_player()));
243 if (ap) {
244 List<StringName> anims;
245 ap->get_animation_list(&anims);
246
247 for (const StringName &F : anims) {
248 mb->get_popup()->add_item(F);
249 options.push_back(F);
250 }
251
252 if (ap->has_animation(anim->get_animation())) {
253 pb->set_max(ap->get_animation(anim->get_animation())->get_length());
254 }
255 }
256 }
257
258 pb->set_show_percentage(false);
259 pb->set_custom_minimum_size(Vector2(0, 14) * EDSCALE);
260 animations[E] = pb;
261 node->add_child(pb);
262
263 mb->get_popup()->connect("index_pressed", callable_mp(this, &AnimationNodeBlendTreeEditor::_anim_selected).bind(options, E), CONNECT_DEFERRED);
264 }
265
266 Ref<StyleBoxFlat> sb = node->get_theme_stylebox(SNAME("panel"), SNAME("GraphNode"));
267 Color c = sb->get_border_color();
268 Color mono_color = ((c.r + c.g + c.b) / 3) < 0.7 ? Color(1.0, 1.0, 1.0) : Color(0.0, 0.0, 0.0);
269 mono_color.a = 0.85;
270 c = mono_color;
271
272 node->add_theme_color_override("title_color", c);
273 c.a = 0.7;
274 node->add_theme_color_override("close_color", c);
275 node->add_theme_color_override("resizer_color", c);
276 }
277
278 List<AnimationNodeBlendTree::NodeConnection> node_connections;
279 blend_tree->get_node_connections(&node_connections);
280
281 for (const AnimationNodeBlendTree::NodeConnection &E : node_connections) {
282 StringName from = E.output_node;
283 StringName to = E.input_node;
284 int to_idx = E.input_index;
285
286 graph->connect_node(from, 0, to, to_idx);
287 }
288
289 float graph_minimap_opacity = EDITOR_GET("editors/visual_editors/minimap_opacity");
290 graph->set_minimap_opacity(graph_minimap_opacity);
291 float graph_lines_curvature = EDITOR_GET("editors/visual_editors/lines_curvature");
292 graph->set_connection_lines_curvature(graph_lines_curvature);
293}
294
295void AnimationNodeBlendTreeEditor::_file_opened(const String &p_file) {
296 file_loaded = ResourceLoader::load(p_file);
297 if (file_loaded.is_valid()) {
298 _add_node(MENU_LOAD_FILE_CONFIRM);
299 } else {
300 EditorNode::get_singleton()->show_warning(TTR("This type of node can't be used. Only animation nodes are allowed."));
301 }
302}
303
304void AnimationNodeBlendTreeEditor::_add_node(int p_idx) {
305 Ref<AnimationNode> anode;
306
307 String base_name;
308
309 if (p_idx == MENU_LOAD_FILE) {
310 open_file->clear_filters();
311 List<String> ext_filters;
312 ResourceLoader::get_recognized_extensions_for_type("AnimationNode", &ext_filters);
313 for (const String &E : ext_filters) {
314 open_file->add_filter("*." + E);
315 }
316 open_file->popup_file_dialog();
317 return;
318 } else if (p_idx == MENU_LOAD_FILE_CONFIRM) {
319 anode = file_loaded;
320 file_loaded.unref();
321 base_name = anode->get_class();
322 } else if (p_idx == MENU_PASTE) {
323 anode = EditorSettings::get_singleton()->get_resource_clipboard();
324 ERR_FAIL_COND(!anode.is_valid());
325 base_name = anode->get_class();
326 } else if (!add_options[p_idx].type.is_empty()) {
327 AnimationNode *an = Object::cast_to<AnimationNode>(ClassDB::instantiate(add_options[p_idx].type));
328 ERR_FAIL_NULL(an);
329 anode = Ref<AnimationNode>(an);
330 base_name = add_options[p_idx].name;
331 } else {
332 ERR_FAIL_COND(add_options[p_idx].script.is_null());
333 StringName base_type = add_options[p_idx].script->get_instance_base_type();
334 AnimationNode *an = Object::cast_to<AnimationNode>(ClassDB::instantiate(base_type));
335 ERR_FAIL_NULL(an);
336 anode = Ref<AnimationNode>(an);
337 anode->set_script(add_options[p_idx].script);
338 base_name = add_options[p_idx].name;
339 }
340
341 Ref<AnimationNodeOutput> out = anode;
342 if (out.is_valid()) {
343 EditorNode::get_singleton()->show_warning(TTR("Output node can't be added to the blend tree."));
344 return;
345 }
346
347 if (!from_node.is_empty() && anode->get_input_count() == 0) {
348 from_node = "";
349 return;
350 }
351
352 Point2 instance_pos = graph->get_scroll_offset();
353 if (use_position_from_popup_menu) {
354 instance_pos += position_from_popup_menu;
355 } else {
356 instance_pos += graph->get_size() * 0.5;
357 }
358
359 instance_pos /= graph->get_zoom();
360
361 int base = 1;
362 String name = base_name;
363 while (blend_tree->has_node(name)) {
364 base++;
365 name = base_name + " " + itos(base);
366 }
367
368 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
369 undo_redo->create_action(TTR("Add Node to BlendTree"));
370 undo_redo->add_do_method(blend_tree.ptr(), "add_node", name, anode, instance_pos / EDSCALE);
371 undo_redo->add_undo_method(blend_tree.ptr(), "remove_node", name);
372
373 if (!from_node.is_empty()) {
374 undo_redo->add_do_method(blend_tree.ptr(), "connect_node", name, 0, from_node);
375 from_node = "";
376 }
377 if (!to_node.is_empty() && to_slot != -1) {
378 undo_redo->add_do_method(blend_tree.ptr(), "connect_node", to_node, to_slot, name);
379 to_node = "";
380 to_slot = -1;
381 }
382
383 undo_redo->add_do_method(this, "update_graph");
384 undo_redo->add_undo_method(this, "update_graph");
385 undo_redo->commit_action();
386}
387
388void AnimationNodeBlendTreeEditor::_popup(bool p_has_input_ports, const Vector2 &p_node_position) {
389 _update_options_menu(p_has_input_ports);
390 use_position_from_popup_menu = true;
391 position_from_popup_menu = p_node_position;
392 add_node->get_popup()->set_position(graph->get_screen_position() + graph->get_local_mouse_position());
393 add_node->get_popup()->reset_size();
394 add_node->get_popup()->popup();
395}
396
397void AnimationNodeBlendTreeEditor::_popup_request(const Vector2 &p_position) {
398 if (read_only) {
399 return;
400 }
401
402 _popup(false, p_position);
403}
404
405void AnimationNodeBlendTreeEditor::_connection_to_empty(const String &p_from, int p_from_slot, const Vector2 &p_release_position) {
406 if (read_only) {
407 return;
408 }
409
410 Ref<AnimationNode> node = blend_tree->get_node(p_from);
411 if (node.is_valid()) {
412 from_node = p_from;
413 _popup(true, p_release_position);
414 }
415}
416
417void AnimationNodeBlendTreeEditor::_connection_from_empty(const String &p_to, int p_to_slot, const Vector2 &p_release_position) {
418 if (read_only) {
419 return;
420 }
421
422 Ref<AnimationNode> node = blend_tree->get_node(p_to);
423 if (node.is_valid()) {
424 to_node = p_to;
425 to_slot = p_to_slot;
426 _popup(false, p_release_position);
427 }
428}
429
430void AnimationNodeBlendTreeEditor::_popup_hide() {
431 to_node = "";
432 to_slot = -1;
433}
434
435void AnimationNodeBlendTreeEditor::_node_dragged(const Vector2 &p_from, const Vector2 &p_to, const StringName &p_which) {
436 updating = true;
437 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
438 undo_redo->create_action(TTR("Node Moved"));
439 undo_redo->add_do_method(blend_tree.ptr(), "set_node_position", p_which, p_to / EDSCALE);
440 undo_redo->add_undo_method(blend_tree.ptr(), "set_node_position", p_which, p_from / EDSCALE);
441 undo_redo->add_do_method(this, "update_graph");
442 undo_redo->add_undo_method(this, "update_graph");
443 undo_redo->commit_action();
444 updating = false;
445}
446
447void AnimationNodeBlendTreeEditor::_connection_request(const String &p_from, int p_from_index, const String &p_to, int p_to_index) {
448 if (read_only) {
449 return;
450 }
451
452 AnimationNodeBlendTree::ConnectionError err = blend_tree->can_connect_node(p_to, p_to_index, p_from);
453
454 if (err != AnimationNodeBlendTree::CONNECTION_OK) {
455 EditorNode::get_singleton()->show_warning(TTR("Unable to connect, port may be in use or connection may be invalid."));
456 return;
457 }
458
459 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
460 undo_redo->create_action(TTR("Nodes Connected"));
461 undo_redo->add_do_method(blend_tree.ptr(), "connect_node", p_to, p_to_index, p_from);
462 undo_redo->add_undo_method(blend_tree.ptr(), "disconnect_node", p_to, p_to_index);
463 undo_redo->add_do_method(this, "update_graph");
464 undo_redo->add_undo_method(this, "update_graph");
465 undo_redo->commit_action();
466}
467
468void AnimationNodeBlendTreeEditor::_disconnection_request(const String &p_from, int p_from_index, const String &p_to, int p_to_index) {
469 if (read_only) {
470 return;
471 }
472
473 graph->disconnect_node(p_from, p_from_index, p_to, p_to_index);
474
475 updating = true;
476 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
477 undo_redo->create_action(TTR("Nodes Disconnected"));
478 undo_redo->add_do_method(blend_tree.ptr(), "disconnect_node", p_to, p_to_index);
479 undo_redo->add_undo_method(blend_tree.ptr(), "connect_node", p_to, p_to_index, p_from);
480 undo_redo->add_do_method(this, "update_graph");
481 undo_redo->add_undo_method(this, "update_graph");
482 undo_redo->commit_action();
483 updating = false;
484}
485
486void AnimationNodeBlendTreeEditor::_anim_selected(int p_index, Array p_options, const String &p_node) {
487 String option = p_options[p_index];
488
489 Ref<AnimationNodeAnimation> anim = blend_tree->get_node(p_node);
490 ERR_FAIL_COND(!anim.is_valid());
491
492 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
493 undo_redo->create_action(TTR("Set Animation"));
494 undo_redo->add_do_method(anim.ptr(), "set_animation", option);
495 undo_redo->add_undo_method(anim.ptr(), "set_animation", anim->get_animation());
496 undo_redo->add_do_method(this, "update_graph");
497 undo_redo->add_undo_method(this, "update_graph");
498 undo_redo->commit_action();
499}
500
501void AnimationNodeBlendTreeEditor::_close_request(const String &p_which) {
502 if (read_only) {
503 return;
504 }
505
506 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
507 undo_redo->create_action(TTR("Delete Node"));
508 undo_redo->add_do_method(blend_tree.ptr(), "remove_node", p_which);
509 undo_redo->add_undo_method(blend_tree.ptr(), "add_node", p_which, blend_tree->get_node(p_which), blend_tree.ptr()->get_node_position(p_which));
510
511 List<AnimationNodeBlendTree::NodeConnection> conns;
512 blend_tree->get_node_connections(&conns);
513
514 for (const AnimationNodeBlendTree::NodeConnection &E : conns) {
515 if (E.output_node == p_which || E.input_node == p_which) {
516 undo_redo->add_undo_method(blend_tree.ptr(), "connect_node", E.input_node, E.input_index, E.output_node);
517 }
518 }
519
520 undo_redo->add_do_method(this, "update_graph");
521 undo_redo->add_undo_method(this, "update_graph");
522 undo_redo->commit_action();
523}
524
525void AnimationNodeBlendTreeEditor::_close_nodes_request(const TypedArray<StringName> &p_nodes) {
526 if (read_only) {
527 return;
528 }
529
530 List<StringName> to_erase;
531
532 if (p_nodes.is_empty()) {
533 for (int i = 0; i < graph->get_child_count(); i++) {
534 GraphNode *gn = Object::cast_to<GraphNode>(graph->get_child(i));
535 if (gn && gn->is_selected()) {
536 Ref<AnimationNode> anode = blend_tree->get_node(gn->get_name());
537 if (anode->is_closable()) {
538 to_erase.push_back(gn->get_name());
539 }
540 }
541 }
542 } else {
543 for (int i = 0; i < p_nodes.size(); i++) {
544 Ref<AnimationNode> anode = blend_tree->get_node(p_nodes[i]);
545 if (anode->is_closable()) {
546 to_erase.push_back(p_nodes[i]);
547 }
548 }
549 }
550
551 if (to_erase.is_empty()) {
552 return;
553 }
554
555 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
556 undo_redo->create_action(TTR("Delete Node(s)"));
557
558 for (const StringName &F : to_erase) {
559 _close_request(F);
560 }
561
562 undo_redo->commit_action();
563}
564
565void AnimationNodeBlendTreeEditor::_node_selected(Object *p_node) {
566 if (read_only) {
567 return;
568 }
569
570 GraphNode *gn = Object::cast_to<GraphNode>(p_node);
571 ERR_FAIL_NULL(gn);
572
573 String name = gn->get_name();
574
575 Ref<AnimationNode> anode = blend_tree->get_node(name);
576 ERR_FAIL_COND(!anode.is_valid());
577
578 EditorNode::get_singleton()->push_item(anode.ptr(), "", true);
579}
580
581void AnimationNodeBlendTreeEditor::_open_in_editor(const String &p_which) {
582 Ref<AnimationNode> an = blend_tree->get_node(p_which);
583 ERR_FAIL_COND(!an.is_valid());
584 AnimationTreeEditor::get_singleton()->enter_editor(p_which);
585}
586
587void AnimationNodeBlendTreeEditor::_filter_toggled() {
588 updating = true;
589 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
590 undo_redo->create_action(TTR("Toggle Filter On/Off"));
591 undo_redo->add_do_method(_filter_edit.ptr(), "set_filter_enabled", filter_enabled->is_pressed());
592 undo_redo->add_undo_method(_filter_edit.ptr(), "set_filter_enabled", _filter_edit->is_filter_enabled());
593 undo_redo->add_do_method(this, "_update_filters", _filter_edit);
594 undo_redo->add_undo_method(this, "_update_filters", _filter_edit);
595 undo_redo->commit_action();
596 updating = false;
597}
598
599void AnimationNodeBlendTreeEditor::_filter_edited() {
600 TreeItem *edited = filters->get_edited();
601 ERR_FAIL_NULL(edited);
602
603 NodePath edited_path = edited->get_metadata(0);
604 bool filtered = edited->is_checked(0);
605
606 updating = true;
607 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
608 undo_redo->create_action(TTR("Change Filter"));
609 undo_redo->add_do_method(_filter_edit.ptr(), "set_filter_path", edited_path, filtered);
610 undo_redo->add_undo_method(_filter_edit.ptr(), "set_filter_path", edited_path, _filter_edit->is_path_filtered(edited_path));
611 undo_redo->add_do_method(this, "_update_filters", _filter_edit);
612 undo_redo->add_undo_method(this, "_update_filters", _filter_edit);
613 undo_redo->commit_action();
614 updating = false;
615}
616
617bool AnimationNodeBlendTreeEditor::_update_filters(const Ref<AnimationNode> &anode) {
618 if (updating || _filter_edit != anode) {
619 return false;
620 }
621
622 AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree();
623 if (!tree) {
624 return false;
625 }
626
627 NodePath player_path = tree->get_animation_player();
628
629 if (!tree->has_node(player_path)) {
630 EditorNode::get_singleton()->show_warning(TTR("No animation player set, so unable to retrieve track names."));
631 return false;
632 }
633
634 AnimationPlayer *player = Object::cast_to<AnimationPlayer>(tree->get_node(player_path));
635 if (!player) {
636 EditorNode::get_singleton()->show_warning(TTR("Player path set is invalid, so unable to retrieve track names."));
637 return false;
638 }
639
640 Node *base = player->get_node(player->get_root());
641
642 if (!base) {
643 EditorNode::get_singleton()->show_warning(TTR("Animation player has no valid root node path, so unable to retrieve track names."));
644 return false;
645 }
646
647 updating = true;
648
649 HashSet<String> paths;
650 HashMap<String, RBSet<String>> types;
651 {
652 List<StringName> animation_list;
653 player->get_animation_list(&animation_list);
654
655 for (const StringName &E : animation_list) {
656 Ref<Animation> anim = player->get_animation(E);
657 for (int i = 0; i < anim->get_track_count(); i++) {
658 String track_path = anim->track_get_path(i);
659 paths.insert(track_path);
660
661 String track_type_name;
662 Animation::TrackType track_type = anim->track_get_type(i);
663 switch (track_type) {
664 case Animation::TrackType::TYPE_ANIMATION: {
665 track_type_name = TTR("Anim Clips");
666 } break;
667 case Animation::TrackType::TYPE_AUDIO: {
668 track_type_name = TTR("Audio Clips");
669 } break;
670 case Animation::TrackType::TYPE_METHOD: {
671 track_type_name = TTR("Functions");
672 } break;
673 default: {
674 } break;
675 }
676 if (!track_type_name.is_empty()) {
677 types[track_path].insert(track_type_name);
678 }
679 }
680 }
681 }
682
683 filter_enabled->set_pressed(anode->is_filter_enabled());
684 filters->clear();
685 TreeItem *root = filters->create_item();
686
687 HashMap<String, TreeItem *> parenthood;
688
689 for (const String &E : paths) {
690 NodePath path = E;
691 TreeItem *ti = nullptr;
692 String accum;
693 for (int i = 0; i < path.get_name_count(); i++) {
694 String name = path.get_name(i);
695 if (!accum.is_empty()) {
696 accum += "/";
697 }
698 accum += name;
699 if (!parenthood.has(accum)) {
700 if (ti) {
701 ti = filters->create_item(ti);
702 } else {
703 ti = filters->create_item(root);
704 }
705 parenthood[accum] = ti;
706 ti->set_text(0, name);
707 ti->set_selectable(0, false);
708 ti->set_editable(0, false);
709
710 if (base->has_node(accum)) {
711 Node *node = base->get_node(accum);
712 ti->set_icon(0, EditorNode::get_singleton()->get_object_icon(node, "Node"));
713 }
714
715 } else {
716 ti = parenthood[accum];
717 }
718 }
719
720 Node *node = nullptr;
721 if (base->has_node(accum)) {
722 node = base->get_node(accum);
723 }
724 if (!node) {
725 continue; //no node, can't edit
726 }
727
728 if (path.get_subname_count()) {
729 String concat = path.get_concatenated_subnames();
730
731 Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(node);
732 if (skeleton && skeleton->find_bone(concat) != -1) {
733 //path in skeleton
734 const String &bone = concat;
735 int idx = skeleton->find_bone(bone);
736 List<String> bone_path;
737 while (idx != -1) {
738 bone_path.push_front(skeleton->get_bone_name(idx));
739 idx = skeleton->get_bone_parent(idx);
740 }
741
742 accum += ":";
743 for (List<String>::Element *F = bone_path.front(); F; F = F->next()) {
744 if (F != bone_path.front()) {
745 accum += "/";
746 }
747
748 accum += F->get();
749 if (!parenthood.has(accum)) {
750 ti = filters->create_item(ti);
751 parenthood[accum] = ti;
752 ti->set_text(0, F->get());
753 ti->set_selectable(0, false);
754 ti->set_editable(0, false);
755 ti->set_icon(0, get_editor_theme_icon(SNAME("BoneAttachment3D")));
756 } else {
757 ti = parenthood[accum];
758 }
759 }
760
761 ti->set_editable(0, !read_only);
762 ti->set_selectable(0, true);
763 ti->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
764 ti->set_text(0, concat);
765 ti->set_checked(0, anode->is_path_filtered(path));
766 ti->set_icon(0, get_editor_theme_icon(SNAME("BoneAttachment3D")));
767 ti->set_metadata(0, path);
768
769 } else {
770 //just a property
771 ti = filters->create_item(ti);
772 ti->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
773 ti->set_text(0, concat);
774 ti->set_editable(0, !read_only);
775 ti->set_selectable(0, true);
776 ti->set_checked(0, anode->is_path_filtered(path));
777 ti->set_metadata(0, path);
778 }
779 } else {
780 if (ti) {
781 //just a node, not a property track
782 String types_text = "[";
783 if (types.has(path)) {
784 RBSet<String>::Iterator F = types[path].begin();
785 types_text += *F;
786 while (F) {
787 types_text += " / " + *F;
788 ;
789 ++F;
790 }
791 }
792 types_text += "]";
793 ti = filters->create_item(ti);
794 ti->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
795 ti->set_text(0, types_text);
796 ti->set_editable(0, !read_only);
797 ti->set_selectable(0, true);
798 ti->set_checked(0, anode->is_path_filtered(path));
799 ti->set_metadata(0, path);
800 }
801 }
802 }
803
804 updating = false;
805
806 return true;
807}
808
809void AnimationNodeBlendTreeEditor::_inspect_filters(const String &p_which) {
810 if (read_only) {
811 filter_dialog->set_title(TTR("Inspect Filtered Tracks:"));
812 } else {
813 filter_dialog->set_title(TTR("Edit Filtered Tracks:"));
814 }
815
816 filter_enabled->set_disabled(read_only);
817
818 Ref<AnimationNode> anode = blend_tree->get_node(p_which);
819 ERR_FAIL_COND(!anode.is_valid());
820
821 _filter_edit = anode;
822 if (!_update_filters(anode)) {
823 return;
824 }
825
826 filter_dialog->popup_centered(Size2(500, 500) * EDSCALE);
827}
828
829void AnimationNodeBlendTreeEditor::_update_editor_settings() {
830 graph->get_panner()->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/sub_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EDITOR_GET("editors/panning/simple_panning")));
831 graph->set_warped_panning(bool(EDITOR_GET("editors/panning/warped_mouse_panning")));
832}
833
834void AnimationNodeBlendTreeEditor::_update_theme() {
835 error_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Tree")));
836 error_label->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
837}
838
839void AnimationNodeBlendTreeEditor::_notification(int p_what) {
840 switch (p_what) {
841 case NOTIFICATION_ENTER_TREE: {
842 _update_editor_settings();
843 _update_theme();
844 } break;
845
846 case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
847 _update_editor_settings();
848 } break;
849
850 case NOTIFICATION_THEME_CHANGED: {
851 _update_theme();
852
853 if (is_visible_in_tree()) {
854 update_graph();
855 }
856 } break;
857
858 case NOTIFICATION_PROCESS: {
859 AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree();
860 if (!tree) {
861 return; // Node has been changed.
862 }
863
864 String error;
865
866 if (!tree->is_active()) {
867 error = TTR("AnimationTree is inactive.\nActivate to enable playback, check node warnings if activation fails.");
868 } else if (tree->is_state_invalid()) {
869 error = tree->get_invalid_state_reason();
870 }
871
872 if (error != error_label->get_text()) {
873 error_label->set_text(error);
874 if (!error.is_empty()) {
875 error_panel->show();
876 } else {
877 error_panel->hide();
878 }
879 }
880
881 List<AnimationNodeBlendTree::NodeConnection> conns;
882 blend_tree->get_node_connections(&conns);
883 for (const AnimationNodeBlendTree::NodeConnection &E : conns) {
884 float activity = 0;
885 StringName path = AnimationTreeEditor::get_singleton()->get_base_path() + E.input_node;
886 if (!tree->is_state_invalid()) {
887 activity = tree->get_connection_activity(path, E.input_index);
888 }
889 graph->set_connection_activity(E.output_node, 0, E.input_node, E.input_index, activity);
890 }
891
892 AnimationPlayer *player = nullptr;
893 if (tree->has_node(tree->get_animation_player())) {
894 player = Object::cast_to<AnimationPlayer>(tree->get_node(tree->get_animation_player()));
895 }
896
897 if (player) {
898 for (const KeyValue<StringName, ProgressBar *> &E : animations) {
899 Ref<AnimationNodeAnimation> an = blend_tree->get_node(E.key);
900 if (an.is_valid()) {
901 if (player->has_animation(an->get_animation())) {
902 Ref<Animation> anim = player->get_animation(an->get_animation());
903 if (anim.is_valid()) {
904 E.value->set_max(anim->get_length());
905 //StringName path = AnimationTreeEditor::get_singleton()->get_base_path() + E.input_node;
906 StringName time_path = AnimationTreeEditor::get_singleton()->get_base_path() + String(E.key) + "/time";
907 E.value->set_value(tree->get(time_path));
908 }
909 }
910 }
911 }
912 }
913
914 for (int i = 0; i < visible_properties.size(); i++) {
915 visible_properties[i]->update_property();
916 }
917 } break;
918
919 case NOTIFICATION_VISIBILITY_CHANGED: {
920 set_process(is_visible_in_tree());
921 } break;
922 }
923}
924
925void AnimationNodeBlendTreeEditor::_scroll_changed(const Vector2 &p_scroll) {
926 if (read_only) {
927 return;
928 }
929
930 if (updating) {
931 return;
932 }
933 updating = true;
934 blend_tree->set_graph_offset(p_scroll / EDSCALE);
935 updating = false;
936}
937
938void AnimationNodeBlendTreeEditor::_bind_methods() {
939 ClassDB::bind_method("update_graph", &AnimationNodeBlendTreeEditor::update_graph);
940 ClassDB::bind_method("_update_filters", &AnimationNodeBlendTreeEditor::_update_filters);
941}
942
943AnimationNodeBlendTreeEditor *AnimationNodeBlendTreeEditor::singleton = nullptr;
944
945// AnimationNode's "node_changed" signal means almost update_input.
946void AnimationNodeBlendTreeEditor::_node_changed(const StringName &p_node_name) {
947 // TODO:
948 // Here is executed during the commit of EditorNode::undo_redo, it is not possible to create an undo_redo action here.
949 // The disconnect when the number of enabled inputs decreases is done in AnimationNodeBlendTree and update_graph().
950 // This means that there is no place to register undo_redo actions.
951 // In order to implement undo_redo correctly, we may need to implement AnimationNodeEdit such as AnimationTrackKeyEdit
952 // and add it to _node_selected() with EditorNode::get_singleton()->push_item(AnimationNodeEdit).
953 update_graph();
954}
955
956void AnimationNodeBlendTreeEditor::_node_renamed(const String &p_text, Ref<AnimationNode> p_node) {
957 if (blend_tree.is_null()) {
958 return;
959 }
960
961 AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree();
962 if (!tree) {
963 return;
964 }
965
966 String prev_name = blend_tree->get_node_name(p_node);
967 ERR_FAIL_COND(prev_name.is_empty());
968 GraphNode *gn = Object::cast_to<GraphNode>(graph->get_node(prev_name));
969 ERR_FAIL_NULL(gn);
970
971 const String &new_name = p_text;
972
973 ERR_FAIL_COND(new_name.is_empty() || new_name.contains(".") || new_name.contains("/"));
974
975 if (new_name == prev_name) {
976 return; //nothing to do
977 }
978
979 const String &base_name = new_name;
980 int base = 1;
981 String name = base_name;
982 while (blend_tree->has_node(name)) {
983 base++;
984 name = base_name + " " + itos(base);
985 }
986
987 String base_path = AnimationTreeEditor::get_singleton()->get_base_path();
988
989 updating = true;
990 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
991 undo_redo->create_action(TTR("Node Renamed"));
992 undo_redo->add_do_method(blend_tree.ptr(), "rename_node", prev_name, name);
993 undo_redo->add_undo_method(blend_tree.ptr(), "rename_node", name, prev_name);
994 undo_redo->add_do_method(this, "update_graph");
995 undo_redo->add_undo_method(this, "update_graph");
996 undo_redo->commit_action();
997 updating = false;
998 gn->set_name(new_name);
999 gn->set_size(gn->get_minimum_size());
1000
1001 //change editors accordingly
1002 for (int i = 0; i < visible_properties.size(); i++) {
1003 String pname = visible_properties[i]->get_edited_property().operator String();
1004 if (pname.begins_with(base_path + prev_name)) {
1005 String new_name2 = pname.replace_first(base_path + prev_name, base_path + name);
1006 visible_properties[i]->set_object_and_property(visible_properties[i]->get_edited_object(), new_name2);
1007 }
1008 }
1009
1010 //recreate connections
1011 graph->clear_connections();
1012
1013 List<AnimationNodeBlendTree::NodeConnection> node_connections;
1014 blend_tree->get_node_connections(&node_connections);
1015
1016 for (const AnimationNodeBlendTree::NodeConnection &E : node_connections) {
1017 StringName from = E.output_node;
1018 StringName to = E.input_node;
1019 int to_idx = E.input_index;
1020
1021 graph->connect_node(from, 0, to, to_idx);
1022 }
1023
1024 //update animations
1025 for (const KeyValue<StringName, ProgressBar *> &E : animations) {
1026 if (E.key == prev_name) {
1027 animations[new_name] = animations[prev_name];
1028 animations.erase(prev_name);
1029 break;
1030 }
1031 }
1032
1033 update_graph(); // Needed to update the signal connections with the new name.
1034 current_node_rename_text = String();
1035}
1036
1037void AnimationNodeBlendTreeEditor::_node_renamed_focus_out(Ref<AnimationNode> p_node) {
1038 if (current_node_rename_text.is_empty()) {
1039 return; // The text_submitted signal triggered the graph update and freed the LineEdit.
1040 }
1041 _node_renamed(current_node_rename_text, p_node);
1042}
1043
1044void AnimationNodeBlendTreeEditor::_node_rename_lineedit_changed(const String &p_text) {
1045 current_node_rename_text = p_text;
1046}
1047
1048bool AnimationNodeBlendTreeEditor::can_edit(const Ref<AnimationNode> &p_node) {
1049 Ref<AnimationNodeBlendTree> bt = p_node;
1050 return bt.is_valid();
1051}
1052
1053void AnimationNodeBlendTreeEditor::edit(const Ref<AnimationNode> &p_node) {
1054 if (blend_tree.is_valid()) {
1055 blend_tree->disconnect("node_changed", callable_mp(this, &AnimationNodeBlendTreeEditor::_node_changed));
1056 }
1057
1058 blend_tree = p_node;
1059
1060 read_only = false;
1061
1062 if (blend_tree.is_null()) {
1063 hide();
1064 } else {
1065 read_only = EditorNode::get_singleton()->is_resource_read_only(blend_tree);
1066
1067 blend_tree->connect("node_changed", callable_mp(this, &AnimationNodeBlendTreeEditor::_node_changed));
1068
1069 update_graph();
1070 }
1071
1072 add_node->set_disabled(read_only);
1073 graph->set_arrange_nodes_button_hidden(read_only);
1074}
1075
1076AnimationNodeBlendTreeEditor::AnimationNodeBlendTreeEditor() {
1077 singleton = this;
1078 updating = false;
1079 use_position_from_popup_menu = false;
1080
1081 graph = memnew(GraphEdit);
1082 add_child(graph);
1083 graph->add_valid_right_disconnect_type(0);
1084 graph->add_valid_left_disconnect_type(0);
1085 graph->set_v_size_flags(SIZE_EXPAND_FILL);
1086 graph->connect("connection_request", callable_mp(this, &AnimationNodeBlendTreeEditor::_connection_request), CONNECT_DEFERRED);
1087 graph->connect("disconnection_request", callable_mp(this, &AnimationNodeBlendTreeEditor::_disconnection_request), CONNECT_DEFERRED);
1088 graph->connect("node_selected", callable_mp(this, &AnimationNodeBlendTreeEditor::_node_selected));
1089 graph->connect("scroll_offset_changed", callable_mp(this, &AnimationNodeBlendTreeEditor::_scroll_changed));
1090 graph->connect("close_nodes_request", callable_mp(this, &AnimationNodeBlendTreeEditor::_close_nodes_request));
1091 graph->connect("popup_request", callable_mp(this, &AnimationNodeBlendTreeEditor::_popup_request));
1092 graph->connect("connection_to_empty", callable_mp(this, &AnimationNodeBlendTreeEditor::_connection_to_empty));
1093 graph->connect("connection_from_empty", callable_mp(this, &AnimationNodeBlendTreeEditor::_connection_from_empty));
1094 float graph_minimap_opacity = EDITOR_GET("editors/visual_editors/minimap_opacity");
1095 graph->set_minimap_opacity(graph_minimap_opacity);
1096 float graph_lines_curvature = EDITOR_GET("editors/visual_editors/lines_curvature");
1097 graph->set_connection_lines_curvature(graph_lines_curvature);
1098
1099 VSeparator *vs = memnew(VSeparator);
1100 graph->get_menu_hbox()->add_child(vs);
1101 graph->get_menu_hbox()->move_child(vs, 0);
1102
1103 add_node = memnew(MenuButton);
1104 graph->get_menu_hbox()->add_child(add_node);
1105 add_node->set_text(TTR("Add Node..."));
1106 graph->get_menu_hbox()->move_child(add_node, 0);
1107 add_node->get_popup()->connect("id_pressed", callable_mp(this, &AnimationNodeBlendTreeEditor::_add_node));
1108 add_node->get_popup()->connect("popup_hide", callable_mp(this, &AnimationNodeBlendTreeEditor::_popup_hide), CONNECT_DEFERRED);
1109 add_node->connect("about_to_popup", callable_mp(this, &AnimationNodeBlendTreeEditor::_update_options_menu).bind(false));
1110 add_node->set_disabled(read_only);
1111
1112 add_options.push_back(AddOption("Animation", "AnimationNodeAnimation"));
1113 add_options.push_back(AddOption("OneShot", "AnimationNodeOneShot", 2));
1114 add_options.push_back(AddOption("Add2", "AnimationNodeAdd2", 2));
1115 add_options.push_back(AddOption("Add3", "AnimationNodeAdd3", 3));
1116 add_options.push_back(AddOption("Blend2", "AnimationNodeBlend2", 2));
1117 add_options.push_back(AddOption("Blend3", "AnimationNodeBlend3", 3));
1118 add_options.push_back(AddOption("Sub2", "AnimationNodeSub2", 2));
1119 add_options.push_back(AddOption("TimeSeek", "AnimationNodeTimeSeek", 1));
1120 add_options.push_back(AddOption("TimeScale", "AnimationNodeTimeScale", 1));
1121 add_options.push_back(AddOption("Transition", "AnimationNodeTransition"));
1122 add_options.push_back(AddOption("BlendTree", "AnimationNodeBlendTree"));
1123 add_options.push_back(AddOption("BlendSpace1D", "AnimationNodeBlendSpace1D"));
1124 add_options.push_back(AddOption("BlendSpace2D", "AnimationNodeBlendSpace2D"));
1125 add_options.push_back(AddOption("StateMachine", "AnimationNodeStateMachine"));
1126 _update_options_menu();
1127
1128 error_panel = memnew(PanelContainer);
1129 add_child(error_panel);
1130 error_label = memnew(Label);
1131 error_panel->add_child(error_label);
1132 error_label->set_text("eh");
1133
1134 filter_dialog = memnew(AcceptDialog);
1135 add_child(filter_dialog);
1136 filter_dialog->set_title(TTR("Edit Filtered Tracks:"));
1137
1138 VBoxContainer *filter_vbox = memnew(VBoxContainer);
1139 filter_dialog->add_child(filter_vbox);
1140
1141 filter_enabled = memnew(CheckBox);
1142 filter_enabled->set_text(TTR("Enable Filtering"));
1143 filter_enabled->connect("pressed", callable_mp(this, &AnimationNodeBlendTreeEditor::_filter_toggled));
1144 filter_vbox->add_child(filter_enabled);
1145
1146 filters = memnew(Tree);
1147 filter_vbox->add_child(filters);
1148 filters->set_v_size_flags(SIZE_EXPAND_FILL);
1149 filters->set_hide_root(true);
1150 filters->connect("item_edited", callable_mp(this, &AnimationNodeBlendTreeEditor::_filter_edited));
1151
1152 open_file = memnew(EditorFileDialog);
1153 add_child(open_file);
1154 open_file->set_title(TTR("Open Animation Node"));
1155 open_file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
1156 open_file->connect("file_selected", callable_mp(this, &AnimationNodeBlendTreeEditor::_file_opened));
1157}
1158