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 | |
54 | void 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 | |
67 | void 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 | |
78 | void AnimationNodeBlendTreeEditor::(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 | |
98 | Size2 AnimationNodeBlendTreeEditor::get_minimum_size() const { |
99 | return Size2(10, 200); |
100 | } |
101 | |
102 | void 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 | |
118 | void 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 | |
295 | void 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 | |
304 | void 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 | |
388 | void AnimationNodeBlendTreeEditor::(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 | |
397 | void AnimationNodeBlendTreeEditor::(const Vector2 &p_position) { |
398 | if (read_only) { |
399 | return; |
400 | } |
401 | |
402 | _popup(false, p_position); |
403 | } |
404 | |
405 | void 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 | |
417 | void 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 | |
430 | void AnimationNodeBlendTreeEditor::() { |
431 | to_node = "" ; |
432 | to_slot = -1; |
433 | } |
434 | |
435 | void 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 | |
447 | void 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 | |
468 | void 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 | |
486 | void 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 | |
501 | void 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 | |
525 | void 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 | |
565 | void 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 | |
581 | void 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 | |
587 | void 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 | |
599 | void 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 | |
617 | bool 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 | |
809 | void 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 | |
829 | void 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 | |
834 | void 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 | |
839 | void 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 | |
925 | void 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 | |
938 | void AnimationNodeBlendTreeEditor::_bind_methods() { |
939 | ClassDB::bind_method("update_graph" , &AnimationNodeBlendTreeEditor::update_graph); |
940 | ClassDB::bind_method("_update_filters" , &AnimationNodeBlendTreeEditor::_update_filters); |
941 | } |
942 | |
943 | AnimationNodeBlendTreeEditor *AnimationNodeBlendTreeEditor::singleton = nullptr; |
944 | |
945 | // AnimationNode's "node_changed" signal means almost update_input. |
946 | void 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 | |
956 | void 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 | |
1037 | void 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 | |
1044 | void AnimationNodeBlendTreeEditor::_node_rename_lineedit_changed(const String &p_text) { |
1045 | current_node_rename_text = p_text; |
1046 | } |
1047 | |
1048 | bool AnimationNodeBlendTreeEditor::can_edit(const Ref<AnimationNode> &p_node) { |
1049 | Ref<AnimationNodeBlendTree> bt = p_node; |
1050 | return bt.is_valid(); |
1051 | } |
1052 | |
1053 | void 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 | |
1076 | AnimationNodeBlendTreeEditor::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 | |