1/**************************************************************************/
2/* animation_player_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_player_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/io/resource_saver.h"
37#include "core/os/keyboard.h"
38#include "editor/editor_node.h"
39#include "editor/editor_scale.h"
40#include "editor/editor_settings.h"
41#include "editor/editor_undo_redo_manager.h"
42#include "editor/gui/editor_file_dialog.h"
43#include "editor/inspector_dock.h"
44#include "editor/plugins/canvas_item_editor_plugin.h" // For onion skinning.
45#include "editor/plugins/node_3d_editor_plugin.h" // For onion skinning.
46#include "editor/scene_tree_dock.h"
47#include "scene/gui/separator.h"
48#include "scene/main/window.h"
49#include "scene/resources/animation.h"
50#include "scene/resources/image_texture.h"
51#include "scene/scene_string_names.h"
52#include "servers/rendering_server.h"
53
54///////////////////////////////////
55
56void AnimationPlayerEditor::_node_removed(Node *p_node) {
57 if (player && player == p_node) {
58 player = nullptr;
59
60 set_process(false);
61
62 track_editor->set_animation(Ref<Animation>(), true);
63 track_editor->set_root(nullptr);
64 track_editor->show_select_node_warning(true);
65 _update_player();
66 }
67}
68
69void AnimationPlayerEditor::_notification(int p_what) {
70 switch (p_what) {
71 case NOTIFICATION_PROCESS: {
72 if (!player) {
73 return;
74 }
75
76 updating = true;
77
78 if (player->is_playing()) {
79 {
80 String animname = player->get_assigned_animation();
81
82 if (player->has_animation(animname)) {
83 Ref<Animation> anim = player->get_animation(animname);
84 if (!anim.is_null()) {
85 frame->set_max((double)anim->get_length());
86 }
87 }
88 }
89 frame->set_value(player->get_current_animation_position());
90 track_editor->set_anim_pos(player->get_current_animation_position());
91 } else if (!player->is_valid()) {
92 // Reset timeline when the player has been stopped externally
93 frame->set_value(0);
94 } else if (last_active) {
95 // Need the last frame after it stopped.
96 frame->set_value(player->get_current_animation_position());
97 track_editor->set_anim_pos(player->get_current_animation_position());
98 stop->set_icon(stop_icon);
99 }
100
101 last_active = player->is_playing();
102 updating = false;
103 } break;
104
105 case NOTIFICATION_ENTER_TREE: {
106 tool_anim->get_popup()->connect("id_pressed", callable_mp(this, &AnimationPlayerEditor::_animation_tool_menu));
107
108 onion_skinning->get_popup()->connect("id_pressed", callable_mp(this, &AnimationPlayerEditor::_onion_skinning_menu));
109
110 blend_editor.next->connect("item_selected", callable_mp(this, &AnimationPlayerEditor::_blend_editor_next_changed));
111
112 get_tree()->connect("node_removed", callable_mp(this, &AnimationPlayerEditor::_node_removed));
113
114 add_theme_style_override("panel", EditorNode::get_singleton()->get_editor_theme()->get_stylebox(SNAME("panel"), SNAME("Panel")));
115 } break;
116
117 case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
118 add_theme_style_override("panel", EditorNode::get_singleton()->get_editor_theme()->get_stylebox(SNAME("panel"), SNAME("Panel")));
119 } break;
120
121 case NOTIFICATION_TRANSLATION_CHANGED:
122 case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
123 case NOTIFICATION_THEME_CHANGED: {
124 stop_icon = get_editor_theme_icon(SNAME("Stop"));
125 pause_icon = get_editor_theme_icon(SNAME("Pause"));
126 if (player && player->is_playing()) {
127 stop->set_icon(pause_icon);
128 } else {
129 stop->set_icon(stop_icon);
130 }
131
132 autoplay->set_icon(get_editor_theme_icon(SNAME("AutoPlay")));
133 play->set_icon(get_editor_theme_icon(SNAME("PlayStart")));
134 play_from->set_icon(get_editor_theme_icon(SNAME("Play")));
135 play_bw->set_icon(get_editor_theme_icon(SNAME("PlayStartBackwards")));
136 play_bw_from->set_icon(get_editor_theme_icon(SNAME("PlayBackwards")));
137
138 autoplay_icon = get_editor_theme_icon(SNAME("AutoPlay"));
139 reset_icon = get_editor_theme_icon(SNAME("Reload"));
140 {
141 Ref<Image> autoplay_img = autoplay_icon->get_image();
142 Ref<Image> reset_img = reset_icon->get_image();
143 Size2 icon_size = autoplay_img->get_size();
144 Ref<Image> autoplay_reset_img = Image::create_empty(icon_size.x * 2, icon_size.y, false, autoplay_img->get_format());
145 autoplay_reset_img->blit_rect(autoplay_img, Rect2i(Point2i(), icon_size), Point2i());
146 autoplay_reset_img->blit_rect(reset_img, Rect2i(Point2i(), icon_size), Point2i(icon_size.x, 0));
147 autoplay_reset_icon = ImageTexture::create_from_image(autoplay_reset_img);
148 }
149
150 onion_toggle->set_icon(get_editor_theme_icon(SNAME("Onion")));
151 onion_skinning->set_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl")));
152
153 pin->set_icon(get_editor_theme_icon(SNAME("Pin")));
154
155 tool_anim->add_theme_style_override("normal", get_theme_stylebox(SNAME("normal"), SNAME("Button")));
156 track_editor->get_edit_menu()->add_theme_style_override("normal", get_theme_stylebox(SNAME("normal"), SNAME("Button")));
157
158#define ITEM_ICON(m_item, m_icon) tool_anim->get_popup()->set_item_icon(tool_anim->get_popup()->get_item_index(m_item), get_editor_theme_icon(SNAME(m_icon)))
159
160 ITEM_ICON(TOOL_NEW_ANIM, "New");
161 ITEM_ICON(TOOL_ANIM_LIBRARY, "AnimationLibrary");
162 ITEM_ICON(TOOL_DUPLICATE_ANIM, "Duplicate");
163 ITEM_ICON(TOOL_RENAME_ANIM, "Rename");
164 ITEM_ICON(TOOL_EDIT_TRANSITIONS, "Blend");
165 ITEM_ICON(TOOL_EDIT_RESOURCE, "Edit");
166 ITEM_ICON(TOOL_REMOVE_ANIM, "Remove");
167
168 _update_animation_list_icons();
169 } break;
170 }
171}
172
173void AnimationPlayerEditor::_autoplay_pressed() {
174 if (updating) {
175 return;
176 }
177 if (animation->has_selectable_items() == 0) {
178 return;
179 }
180
181 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
182 String current = animation->get_item_text(animation->get_selected());
183 if (player->get_autoplay() == current) {
184 //unset
185 undo_redo->create_action(TTR("Toggle Autoplay"));
186 undo_redo->add_do_method(player, "set_autoplay", "");
187 undo_redo->add_undo_method(player, "set_autoplay", player->get_autoplay());
188 undo_redo->add_do_method(this, "_animation_player_changed", player);
189 undo_redo->add_undo_method(this, "_animation_player_changed", player);
190 undo_redo->commit_action();
191
192 } else {
193 //set
194 undo_redo->create_action(TTR("Toggle Autoplay"));
195 undo_redo->add_do_method(player, "set_autoplay", current);
196 undo_redo->add_undo_method(player, "set_autoplay", player->get_autoplay());
197 undo_redo->add_do_method(this, "_animation_player_changed", player);
198 undo_redo->add_undo_method(this, "_animation_player_changed", player);
199 undo_redo->commit_action();
200 }
201}
202
203void AnimationPlayerEditor::_play_pressed() {
204 String current = _get_current();
205
206 if (!current.is_empty()) {
207 if (current == player->get_assigned_animation()) {
208 player->stop(); //so it won't blend with itself
209 }
210 ERR_FAIL_COND_EDMSG(!_validate_tracks(player->get_animation(current)), "Animation tracks may have any invalid key, abort playing.");
211 player->play(current);
212 }
213
214 //unstop
215 stop->set_icon(pause_icon);
216}
217
218void AnimationPlayerEditor::_play_from_pressed() {
219 String current = _get_current();
220
221 if (!current.is_empty()) {
222 float time = player->get_current_animation_position();
223 if (current == player->get_assigned_animation() && player->is_playing()) {
224 player->stop(); //so it won't blend with itself
225 }
226 ERR_FAIL_COND_EDMSG(!_validate_tracks(player->get_animation(current)), "Animation tracks may have any invalid key, abort playing.");
227 player->seek(time);
228 player->play(current);
229 }
230
231 //unstop
232 stop->set_icon(pause_icon);
233}
234
235String AnimationPlayerEditor::_get_current() const {
236 String current;
237 if (animation->get_selected() >= 0 && animation->get_selected() < animation->get_item_count() && !animation->is_item_separator(animation->get_selected())) {
238 current = animation->get_item_text(animation->get_selected());
239 }
240 return current;
241}
242void AnimationPlayerEditor::_play_bw_pressed() {
243 String current = _get_current();
244 if (!current.is_empty()) {
245 if (current == player->get_assigned_animation()) {
246 player->stop(); //so it won't blend with itself
247 }
248 ERR_FAIL_COND_EDMSG(!_validate_tracks(player->get_animation(current)), "Animation tracks may have any invalid key, abort playing.");
249 player->play_backwards(current);
250 }
251
252 //unstop
253 stop->set_icon(pause_icon);
254}
255
256void AnimationPlayerEditor::_play_bw_from_pressed() {
257 String current = _get_current();
258
259 if (!current.is_empty()) {
260 float time = player->get_current_animation_position();
261 if (current == player->get_assigned_animation()) {
262 player->stop(); //so it won't blend with itself
263 }
264 ERR_FAIL_COND_EDMSG(!_validate_tracks(player->get_animation(current)), "Animation tracks may have any invalid key, abort playing.");
265 player->seek(time);
266 player->play_backwards(current);
267 }
268
269 //unstop
270 stop->set_icon(pause_icon);
271}
272
273void AnimationPlayerEditor::_stop_pressed() {
274 if (!player) {
275 return;
276 }
277
278 if (player->is_playing()) {
279 player->pause();
280 } else {
281 String current = _get_current();
282 player->stop();
283 player->set_assigned_animation(current);
284 frame->set_value(0);
285 track_editor->set_anim_pos(0);
286 }
287 stop->set_icon(stop_icon);
288}
289
290void AnimationPlayerEditor::_animation_selected(int p_which) {
291 if (updating) {
292 return;
293 }
294 // when selecting an animation, the idea is that the only interesting behavior
295 // ui-wise is that it should play/blend the next one if currently playing
296 String current = _get_current();
297
298 if (!current.is_empty()) {
299 player->set_assigned_animation(current);
300
301 Ref<Animation> anim = player->get_animation(current);
302 {
303 bool animation_library_is_foreign = EditorNode::get_singleton()->is_resource_read_only(anim);
304
305 track_editor->set_animation(anim, animation_library_is_foreign);
306 Node *root = player->get_node(player->get_root());
307 if (root) {
308 track_editor->set_root(root);
309 }
310 }
311 frame->set_max((double)anim->get_length());
312
313 } else {
314 track_editor->set_animation(Ref<Animation>(), true);
315 track_editor->set_root(nullptr);
316 }
317
318 autoplay->set_pressed(current == player->get_autoplay());
319
320 AnimationPlayerEditor::get_singleton()->get_track_editor()->update_keying();
321 _animation_key_editor_seek(timeline_position, false);
322
323 emit_signal("animation_selected", current);
324}
325
326void AnimationPlayerEditor::_animation_new() {
327 int count = 1;
328 String base = "new_animation";
329 String current_library_name = "";
330 if (animation->has_selectable_items()) {
331 String current_animation_name = animation->get_item_text(animation->get_selected());
332 Ref<Animation> current_animation = player->get_animation(current_animation_name);
333 if (current_animation.is_valid()) {
334 current_library_name = player->find_animation_library(current_animation);
335 }
336 }
337 String attempt_prefix = (current_library_name == "") ? "" : current_library_name + "/";
338 while (true) {
339 String attempt = base;
340 if (count > 1) {
341 attempt += vformat("_%d", count);
342 }
343 if (player->has_animation(attempt_prefix + attempt)) {
344 count++;
345 continue;
346 }
347 base = attempt;
348 break;
349 }
350
351 _update_name_dialog_library_dropdown();
352
353 name_dialog_op = TOOL_NEW_ANIM;
354 name_dialog->set_title(TTR("Create New Animation"));
355 name_dialog->popup_centered(Size2(300, 90));
356 name_title->set_text(TTR("New Animation Name:"));
357 name->set_text(base);
358 name->select_all();
359 name->grab_focus();
360}
361
362void AnimationPlayerEditor::_animation_rename() {
363 if (!animation->has_selectable_items()) {
364 return;
365 }
366 int selected = animation->get_selected();
367 String selected_name = animation->get_item_text(selected);
368
369 // Remove library prefix if present.
370 if (selected_name.contains("/")) {
371 selected_name = selected_name.get_slice("/", 1);
372 }
373
374 name_dialog->set_title(TTR("Rename Animation"));
375 name_title->set_text(TTR("Change Animation Name:"));
376 name->set_text(selected_name);
377 name_dialog_op = TOOL_RENAME_ANIM;
378 name_dialog->popup_centered(Size2(300, 90));
379 name->select_all();
380 name->grab_focus();
381 library->hide();
382}
383
384void AnimationPlayerEditor::_animation_remove() {
385 if (!animation->has_selectable_items()) {
386 return;
387 }
388
389 String current = animation->get_item_text(animation->get_selected());
390
391 delete_dialog->set_text(vformat(TTR("Delete Animation '%s'?"), current));
392 delete_dialog->popup_centered();
393}
394
395void AnimationPlayerEditor::_animation_remove_confirmed() {
396 String current = animation->get_item_text(animation->get_selected());
397 Ref<Animation> anim = player->get_animation(current);
398
399 Ref<AnimationLibrary> al = player->get_animation_library(player->find_animation_library(anim));
400 ERR_FAIL_COND(al.is_null());
401
402 // For names of form lib_name/anim_name, remove library name prefix.
403 if (current.contains("/")) {
404 current = current.get_slice("/", 1);
405 }
406 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
407 undo_redo->create_action(TTR("Remove Animation"));
408 if (player->get_autoplay() == current) {
409 undo_redo->add_do_method(player, "set_autoplay", "");
410 undo_redo->add_undo_method(player, "set_autoplay", current);
411 // Avoid having the autoplay icon linger around if there is only one animation in the player.
412 undo_redo->add_do_method(this, "_animation_player_changed", player);
413 }
414 undo_redo->add_do_method(al.ptr(), "remove_animation", current);
415 undo_redo->add_undo_method(al.ptr(), "add_animation", current, anim);
416 undo_redo->add_do_method(this, "_animation_player_changed", player);
417 undo_redo->add_undo_method(this, "_animation_player_changed", player);
418 if (animation->has_selectable_items() && animation->get_selectable_item(false) == animation->get_selectable_item(true)) { // Last item remaining.
419 undo_redo->add_do_method(this, "_stop_onion_skinning");
420 undo_redo->add_undo_method(this, "_start_onion_skinning");
421 }
422 undo_redo->commit_action();
423}
424
425void AnimationPlayerEditor::_select_anim_by_name(const String &p_anim) {
426 int idx = -1;
427 for (int i = 0; i < animation->get_item_count(); i++) {
428 if (animation->get_item_text(i) == p_anim) {
429 idx = i;
430 break;
431 }
432 }
433
434 ERR_FAIL_COND(idx == -1);
435
436 animation->select(idx);
437
438 _animation_selected(idx);
439}
440
441float AnimationPlayerEditor::_get_editor_step() const {
442 // Returns the effective snapping value depending on snapping modifiers, or 0 if snapping is disabled.
443 if (track_editor->is_snap_enabled()) {
444 const String current = player->get_assigned_animation();
445 const Ref<Animation> anim = player->get_animation(current);
446 ERR_FAIL_COND_V(!anim.is_valid(), 0.0);
447
448 // Use more precise snapping when holding Shift
449 return Input::get_singleton()->is_key_pressed(Key::SHIFT) ? anim->get_step() * 0.25 : anim->get_step();
450 }
451
452 return 0.0f;
453}
454
455void AnimationPlayerEditor::_animation_name_edited() {
456 if (player->is_playing()) {
457 player->stop();
458 }
459
460 String new_name = name->get_text();
461 if (!AnimationLibrary::is_valid_animation_name(new_name)) {
462 error_dialog->set_text(TTR("Invalid animation name!"));
463 error_dialog->popup_centered();
464 return;
465 }
466
467 if (name_dialog_op == TOOL_RENAME_ANIM && animation->has_selectable_items() && animation->get_item_text(animation->get_selected()) == new_name) {
468 name_dialog->hide();
469 return;
470 }
471
472 String test_name_prefix = "";
473 if (library->is_visible() && library->get_selected_id() != -1) {
474 test_name_prefix = library->get_item_metadata(library->get_selected_id());
475 test_name_prefix += (test_name_prefix != "") ? "/" : "";
476 }
477
478 if (player->has_animation(test_name_prefix + new_name)) {
479 error_dialog->set_text(vformat(TTR("Animation '%s' already exists!"), test_name_prefix + new_name));
480 error_dialog->popup_centered();
481 return;
482 }
483
484 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
485 switch (name_dialog_op) {
486 case TOOL_RENAME_ANIM: {
487 String current = animation->get_item_text(animation->get_selected());
488 Ref<Animation> anim = player->get_animation(current);
489
490 Ref<AnimationLibrary> al = player->get_animation_library(player->find_animation_library(anim));
491 ERR_FAIL_COND(al.is_null());
492
493 // Extract library prefix if present.
494 String new_library_prefix = "";
495 if (current.contains("/")) {
496 new_library_prefix = current.get_slice("/", 0) + "/";
497 current = current.get_slice("/", 1);
498 }
499
500 undo_redo->create_action(TTR("Rename Animation"));
501 undo_redo->add_do_method(al.ptr(), "rename_animation", current, new_name);
502 undo_redo->add_do_method(anim.ptr(), "set_name", new_name);
503 undo_redo->add_undo_method(al.ptr(), "rename_animation", new_name, current);
504 undo_redo->add_undo_method(anim.ptr(), "set_name", current);
505 undo_redo->add_do_method(this, "_animation_player_changed", player);
506 undo_redo->add_undo_method(this, "_animation_player_changed", player);
507 undo_redo->commit_action();
508
509 _select_anim_by_name(new_library_prefix + new_name);
510 } break;
511
512 case TOOL_NEW_ANIM: {
513 Ref<Animation> new_anim = Ref<Animation>(memnew(Animation));
514 new_anim->set_name(new_name);
515 String library_name;
516 Ref<AnimationLibrary> al;
517 if (library->is_visible()) {
518 library_name = library->get_item_metadata(library->get_selected());
519 // It's possible that [Global] was selected, but doesn't exist yet.
520 if (player->has_animation_library(library_name)) {
521 al = player->get_animation_library(library_name);
522 }
523
524 } else {
525 if (player->has_animation_library("")) {
526 al = player->get_animation_library("");
527 library_name = "";
528 }
529 }
530
531 undo_redo->create_action(TTR("Add Animation"));
532
533 bool lib_added = false;
534 if (al.is_null()) {
535 al.instantiate();
536 lib_added = true;
537 undo_redo->add_do_method(player, "add_animation_library", "", al);
538 library_name = "";
539 }
540
541 undo_redo->add_do_method(al.ptr(), "add_animation", new_name, new_anim);
542 undo_redo->add_undo_method(al.ptr(), "remove_animation", new_name);
543 undo_redo->add_do_method(this, "_animation_player_changed", player);
544 undo_redo->add_undo_method(this, "_animation_player_changed", player);
545 if (!animation->has_selectable_items()) {
546 undo_redo->add_do_method(this, "_start_onion_skinning");
547 undo_redo->add_undo_method(this, "_stop_onion_skinning");
548 }
549 if (lib_added) {
550 undo_redo->add_undo_method(player, "remove_animation_library", "");
551 }
552 undo_redo->commit_action();
553
554 if (library_name != "") {
555 library_name = library_name + "/";
556 }
557 _select_anim_by_name(library_name + new_name);
558
559 } break;
560
561 case TOOL_DUPLICATE_ANIM: {
562 String current = animation->get_item_text(animation->get_selected());
563 Ref<Animation> anim = player->get_animation(current);
564
565 Ref<Animation> new_anim = _animation_clone(anim);
566 new_anim->set_name(new_name);
567
568 String library_name;
569 Ref<AnimationLibrary> al;
570 if (library->is_visible()) {
571 library_name = library->get_item_metadata(library->get_selected());
572 // It's possible that [Global] was selected, but doesn't exist yet.
573 if (player->has_animation_library(library_name)) {
574 al = player->get_animation_library(library_name);
575 }
576 } else {
577 if (player->has_animation_library("")) {
578 al = player->get_animation_library("");
579 library_name = "";
580 }
581 }
582
583 undo_redo->create_action(TTR("Duplicate Animation"));
584
585 bool lib_added = false;
586 if (al.is_null()) {
587 al.instantiate();
588 lib_added = true;
589 undo_redo->add_do_method(player, "add_animation_library", "", al);
590 library_name = "";
591 }
592
593 undo_redo->add_do_method(al.ptr(), "add_animation", new_name, new_anim);
594 undo_redo->add_undo_method(al.ptr(), "remove_animation", new_name);
595 undo_redo->add_do_method(this, "_animation_player_changed", player);
596 undo_redo->add_undo_method(this, "_animation_player_changed", player);
597 if (lib_added) {
598 undo_redo->add_undo_method(player, "remove_animation_library", "");
599 }
600 undo_redo->commit_action();
601
602 if (library_name != "") {
603 library_name = library_name + "/";
604 }
605 _select_anim_by_name(library_name + new_name);
606 } break;
607 }
608
609 name_dialog->hide();
610}
611
612void AnimationPlayerEditor::_blend_editor_next_changed(const int p_idx) {
613 if (!animation->has_selectable_items()) {
614 return;
615 }
616
617 String current = animation->get_item_text(animation->get_selected());
618
619 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
620 undo_redo->create_action(TTR("Blend Next Changed"));
621 undo_redo->add_do_method(player, "animation_set_next", current, blend_editor.next->get_item_text(p_idx));
622 undo_redo->add_undo_method(player, "animation_set_next", current, player->animation_get_next(current));
623 undo_redo->add_do_method(this, "_animation_player_changed", player);
624 undo_redo->add_undo_method(this, "_animation_player_changed", player);
625 undo_redo->commit_action();
626}
627
628void AnimationPlayerEditor::_animation_blend() {
629 if (updating_blends) {
630 return;
631 }
632
633 blend_editor.tree->clear();
634
635 if (!animation->has_selectable_items()) {
636 return;
637 }
638
639 String current = animation->get_item_text(animation->get_selected());
640
641 blend_editor.dialog->popup_centered(Size2(400, 400) * EDSCALE);
642
643 blend_editor.tree->set_hide_root(true);
644 blend_editor.tree->set_column_expand_ratio(0, 10);
645 blend_editor.tree->set_column_clip_content(0, true);
646 blend_editor.tree->set_column_expand_ratio(1, 3);
647 blend_editor.tree->set_column_clip_content(1, true);
648
649 List<StringName> anims;
650 player->get_animation_list(&anims);
651 TreeItem *root = blend_editor.tree->create_item();
652 updating_blends = true;
653
654 int i = 0;
655 bool anim_found = false;
656 blend_editor.next->clear();
657 blend_editor.next->add_item("", i);
658
659 for (const StringName &to : anims) {
660 TreeItem *blend = blend_editor.tree->create_item(root);
661 blend->set_editable(0, false);
662 blend->set_editable(1, true);
663 blend->set_text(0, to);
664 blend->set_cell_mode(1, TreeItem::CELL_MODE_RANGE);
665 blend->set_range_config(1, 0, 3600, 0.001);
666 blend->set_range(1, player->get_blend_time(current, to));
667
668 i++;
669 blend_editor.next->add_item(to, i);
670 if (to == player->animation_get_next(current)) {
671 blend_editor.next->select(i);
672 anim_found = true;
673 }
674 }
675
676 // make sure we reset it else it becomes out of sync and could contain a deleted animation
677 if (!anim_found) {
678 blend_editor.next->select(0);
679 player->animation_set_next(current, blend_editor.next->get_item_text(0));
680 }
681
682 updating_blends = false;
683}
684
685void AnimationPlayerEditor::_blend_edited() {
686 if (updating_blends) {
687 return;
688 }
689
690 if (!animation->has_selectable_items()) {
691 return;
692 }
693
694 String current = animation->get_item_text(animation->get_selected());
695
696 TreeItem *selected = blend_editor.tree->get_edited();
697 if (!selected) {
698 return;
699 }
700
701 updating_blends = true;
702 String to = selected->get_text(0);
703 float blend_time = selected->get_range(1);
704 float prev_blend_time = player->get_blend_time(current, to);
705
706 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
707 undo_redo->create_action(TTR("Change Blend Time"));
708 undo_redo->add_do_method(player, "set_blend_time", current, to, blend_time);
709 undo_redo->add_undo_method(player, "set_blend_time", current, to, prev_blend_time);
710 undo_redo->add_do_method(this, "_animation_player_changed", player);
711 undo_redo->add_undo_method(this, "_animation_player_changed", player);
712 undo_redo->commit_action();
713 updating_blends = false;
714}
715
716void AnimationPlayerEditor::ensure_visibility() {
717 if (player && pin->is_pressed()) {
718 return; // another player is pinned, don't reset
719 }
720
721 _animation_edit();
722}
723
724Dictionary AnimationPlayerEditor::get_state() const {
725 Dictionary d;
726
727 d["visible"] = is_visible_in_tree();
728 if (EditorNode::get_singleton()->get_edited_scene() && is_visible_in_tree() && player) {
729 d["player"] = EditorNode::get_singleton()->get_edited_scene()->get_path_to(player);
730 d["animation"] = player->get_assigned_animation();
731 d["track_editor_state"] = track_editor->get_state();
732 }
733
734 return d;
735}
736
737void AnimationPlayerEditor::set_state(const Dictionary &p_state) {
738 if (!p_state.has("visible") || !p_state["visible"]) {
739 return;
740 }
741 if (!EditorNode::get_singleton()->get_edited_scene()) {
742 return;
743 }
744
745 if (p_state.has("player")) {
746 Node *n = EditorNode::get_singleton()->get_edited_scene()->get_node(p_state["player"]);
747 if (Object::cast_to<AnimationPlayer>(n) && EditorNode::get_singleton()->get_editor_selection()->is_selected(n)) {
748 if (player) {
749 if (player->is_connected("animation_libraries_updated", callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated))) {
750 player->disconnect("animation_libraries_updated", callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated));
751 }
752 }
753 player = Object::cast_to<AnimationPlayer>(n);
754 if (player) {
755 if (!player->is_connected("animation_libraries_updated", callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated))) {
756 player->connect("animation_libraries_updated", callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated));
757 }
758 }
759
760 _update_player();
761 EditorNode::get_singleton()->make_bottom_panel_item_visible(this);
762 set_process(true);
763 ensure_visibility();
764
765 if (p_state.has("animation")) {
766 String anim = p_state["animation"];
767 if (!anim.is_empty() && player->has_animation(anim)) {
768 _select_anim_by_name(anim);
769 _animation_edit();
770 }
771 }
772 }
773 }
774
775 if (p_state.has("track_editor_state")) {
776 track_editor->set_state(p_state["track_editor_state"]);
777 }
778}
779
780void AnimationPlayerEditor::_animation_resource_edit() {
781 String current = _get_current();
782 if (current != String()) {
783 Ref<Animation> anim = player->get_animation(current);
784 EditorNode::get_singleton()->edit_resource(anim);
785 }
786}
787
788void AnimationPlayerEditor::_animation_edit() {
789 String current = _get_current();
790 if (current != String()) {
791 Ref<Animation> anim = player->get_animation(current);
792
793 bool animation_library_is_foreign = EditorNode::get_singleton()->is_resource_read_only(anim);
794
795 track_editor->set_animation(anim, animation_library_is_foreign);
796
797 Node *root = player->get_node(player->get_root());
798 if (root) {
799 track_editor->set_root(root);
800 }
801 } else {
802 track_editor->set_animation(Ref<Animation>(), true);
803 track_editor->set_root(nullptr);
804 }
805}
806
807void AnimationPlayerEditor::_scale_changed(const String &p_scale) {
808 player->set_speed_scale(p_scale.to_float());
809}
810
811void AnimationPlayerEditor::_update_animation() {
812 // the purpose of _update_animation is to reflect the current state
813 // of the animation player in the current editor..
814
815 updating = true;
816
817 if (player->is_playing()) {
818 stop->set_icon(pause_icon);
819 } else {
820 stop->set_icon(stop_icon);
821 }
822
823 scale->set_text(String::num(player->get_speed_scale(), 2));
824 String current = player->get_assigned_animation();
825
826 for (int i = 0; i < animation->get_item_count(); i++) {
827 if (animation->get_item_text(i) == current) {
828 animation->select(i);
829 break;
830 }
831 }
832
833 updating = false;
834}
835
836void AnimationPlayerEditor::_update_player() {
837 updating = true;
838
839 animation->clear();
840
841 tool_anim->set_disabled(player == nullptr);
842 pin->set_disabled(player == nullptr);
843
844 if (!player) {
845 AnimationPlayerEditor::get_singleton()->get_track_editor()->update_keying();
846 return;
847 }
848
849 List<StringName> libraries;
850 player->get_animation_library_list(&libraries);
851
852 int active_idx = -1;
853 bool no_anims_found = true;
854 bool foreign_global_anim_lib = false;
855
856 for (const StringName &K : libraries) {
857 if (K != StringName()) {
858 animation->add_separator(K);
859 }
860
861 // Check if the global library is foreign since we want to disable options for adding/remove/renaming animations if it is.
862 Ref<AnimationLibrary> anim_library = player->get_animation_library(K);
863 if (K == "") {
864 foreign_global_anim_lib = EditorNode::get_singleton()->is_resource_read_only(anim_library);
865 }
866
867 List<StringName> animlist;
868 anim_library->get_animation_list(&animlist);
869
870 for (const StringName &E : animlist) {
871 String path = K;
872 if (path != "") {
873 path += "/";
874 }
875 path += E;
876 animation->add_item(path);
877 if (player->get_assigned_animation() == path) {
878 active_idx = animation->get_selectable_item(true);
879 }
880 no_anims_found = false;
881 }
882 }
883#define ITEM_CHECK_DISABLED(m_item) tool_anim->get_popup()->set_item_disabled(tool_anim->get_popup()->get_item_index(m_item), foreign_global_anim_lib)
884
885 ITEM_CHECK_DISABLED(TOOL_NEW_ANIM);
886
887#undef ITEM_CHECK_DISABLED
888
889#define ITEM_CHECK_DISABLED(m_item) tool_anim->get_popup()->set_item_disabled(tool_anim->get_popup()->get_item_index(m_item), no_anims_found || foreign_global_anim_lib)
890
891 ITEM_CHECK_DISABLED(TOOL_DUPLICATE_ANIM);
892 ITEM_CHECK_DISABLED(TOOL_RENAME_ANIM);
893 ITEM_CHECK_DISABLED(TOOL_EDIT_TRANSITIONS);
894 ITEM_CHECK_DISABLED(TOOL_REMOVE_ANIM);
895 ITEM_CHECK_DISABLED(TOOL_EDIT_RESOURCE);
896
897#undef ITEM_CHECK_DISABLED
898
899 stop->set_disabled(no_anims_found);
900 play->set_disabled(no_anims_found);
901 play_bw->set_disabled(no_anims_found);
902 play_bw_from->set_disabled(no_anims_found);
903 play_from->set_disabled(no_anims_found);
904 frame->set_editable(!no_anims_found);
905 animation->set_disabled(no_anims_found);
906 autoplay->set_disabled(no_anims_found);
907 onion_toggle->set_disabled(no_anims_found);
908 onion_skinning->set_disabled(no_anims_found);
909
910 if (hack_disable_onion_skinning) {
911 onion_toggle->set_disabled(true);
912 onion_skinning->set_disabled(true);
913 }
914
915 _update_animation_list_icons();
916
917 updating = false;
918 if (active_idx != -1) {
919 animation->select(active_idx);
920 autoplay->set_pressed(animation->get_item_text(active_idx) == player->get_autoplay());
921 _animation_selected(active_idx);
922 } else if (animation->has_selectable_items()) {
923 int item = animation->get_selectable_item();
924 animation->select(item);
925 autoplay->set_pressed(animation->get_item_text(item) == player->get_autoplay());
926 _animation_selected(item);
927 } else {
928 _animation_selected(0);
929 }
930
931 if (!no_anims_found) {
932 String current = animation->get_item_text(animation->get_selected());
933 Ref<Animation> anim = player->get_animation(current);
934
935 bool animation_library_is_foreign = EditorNode::get_singleton()->is_resource_read_only(anim);
936
937 track_editor->set_animation(anim, animation_library_is_foreign);
938 Node *root = player->get_node(player->get_root());
939 if (root) {
940 track_editor->set_root(root);
941 }
942 }
943
944 _update_animation();
945}
946
947void AnimationPlayerEditor::_update_animation_list_icons() {
948 for (int i = 0; i < animation->get_item_count(); i++) {
949 String anim_name = animation->get_item_text(i);
950 if (animation->is_item_disabled(i) || animation->is_item_separator(i)) {
951 continue;
952 }
953
954 Ref<Texture2D> icon;
955 if (anim_name == player->get_autoplay()) {
956 if (anim_name == SceneStringNames::get_singleton()->RESET) {
957 icon = autoplay_reset_icon;
958 } else {
959 icon = autoplay_icon;
960 }
961 } else if (anim_name == SceneStringNames::get_singleton()->RESET) {
962 icon = reset_icon;
963 }
964
965 animation->set_item_icon(i, icon);
966 }
967}
968
969void AnimationPlayerEditor::_update_name_dialog_library_dropdown() {
970 StringName current_library_name;
971 if (animation->has_selectable_items()) {
972 String current_animation_name = animation->get_item_text(animation->get_selected());
973 Ref<Animation> current_animation = player->get_animation(current_animation_name);
974 if (current_animation.is_valid()) {
975 current_library_name = player->find_animation_library(current_animation);
976 }
977 }
978
979 List<StringName> libraries;
980 player->get_animation_library_list(&libraries);
981 library->clear();
982
983 // When [Global] isn't present, but other libraries are, add option of creating [Global].
984 int index_offset = 0;
985 if (!player->has_animation_library(StringName())) {
986 library->add_item(String(TTR("[Global] (create)")));
987 library->set_item_metadata(0, "");
988 index_offset = 1;
989 }
990
991 int current_lib_id = index_offset; // Don't default to [Global] if it doesn't exist yet.
992 for (int i = 0; i < libraries.size(); i++) {
993 StringName library_name = libraries[i];
994 library->add_item((library_name == StringName()) ? String(TTR("[Global]")) : String(library_name));
995 library->set_item_metadata(i + index_offset, String(library_name));
996 // Default to duplicating into same library.
997 if (library_name == current_library_name) {
998 current_lib_id = i + index_offset;
999 }
1000 }
1001
1002 if (library->get_item_count() > 1) {
1003 library->select(current_lib_id);
1004 library->show();
1005 } else {
1006 library->hide();
1007 }
1008}
1009
1010void AnimationPlayerEditor::edit(AnimationPlayer *p_player) {
1011 if (player && pin->is_pressed()) {
1012 return; // Ignore, pinned.
1013 }
1014
1015 if (player) {
1016 if (player->is_connected("animation_libraries_updated", callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated))) {
1017 player->disconnect("animation_libraries_updated", callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated));
1018 }
1019 }
1020 player = p_player;
1021
1022 if (player) {
1023 if (!player->is_connected("animation_libraries_updated", callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated))) {
1024 player->connect("animation_libraries_updated", callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated));
1025 }
1026 _update_player();
1027
1028 if (onion.enabled) {
1029 if (animation->has_selectable_items()) {
1030 _start_onion_skinning();
1031 } else {
1032 _stop_onion_skinning();
1033 }
1034 }
1035
1036 track_editor->show_select_node_warning(false);
1037 } else {
1038 if (onion.enabled) {
1039 _stop_onion_skinning();
1040 }
1041
1042 track_editor->show_select_node_warning(true);
1043 }
1044
1045 library_editor->set_animation_player(player);
1046}
1047
1048void AnimationPlayerEditor::forward_force_draw_over_viewport(Control *p_overlay) {
1049 if (!onion.can_overlay) {
1050 return;
1051 }
1052
1053 // Can happen on viewport resize, at least.
1054 if (!_are_onion_layers_valid()) {
1055 return;
1056 }
1057
1058 RID ci = p_overlay->get_canvas_item();
1059 Rect2 src_rect = p_overlay->get_global_rect();
1060 // Re-flip since captures are already flipped.
1061 src_rect.position.y = onion.capture_size.y - (src_rect.position.y + src_rect.size.y);
1062 src_rect.size.y *= -1;
1063
1064 Rect2 dst_rect = Rect2(Point2(), p_overlay->get_size());
1065
1066 float alpha_step = 1.0 / (onion.steps + 1);
1067
1068 int cidx = 0;
1069 if (onion.past) {
1070 float alpha = 0;
1071 do {
1072 alpha += alpha_step;
1073
1074 if (onion.captures_valid[cidx]) {
1075 RS::get_singleton()->canvas_item_add_texture_rect_region(
1076 ci, dst_rect, RS::get_singleton()->viewport_get_texture(onion.captures[cidx]), src_rect, Color(1, 1, 1, alpha));
1077 }
1078
1079 cidx++;
1080 } while (cidx < onion.steps);
1081 }
1082 if (onion.future) {
1083 float alpha = 1;
1084 int base_cidx = cidx;
1085 do {
1086 alpha -= alpha_step;
1087
1088 if (onion.captures_valid[cidx]) {
1089 RS::get_singleton()->canvas_item_add_texture_rect_region(
1090 ci, dst_rect, RS::get_singleton()->viewport_get_texture(onion.captures[cidx]), src_rect, Color(1, 1, 1, alpha));
1091 }
1092
1093 cidx++;
1094 } while (cidx < base_cidx + onion.steps); // In case there's the present capture at the end, skip it.
1095 }
1096}
1097
1098void AnimationPlayerEditor::_animation_duplicate() {
1099 if (!animation->has_selectable_items()) {
1100 return;
1101 }
1102
1103 String current = animation->get_item_text(animation->get_selected());
1104 Ref<Animation> anim = player->get_animation(current);
1105 if (!anim.is_valid()) {
1106 return;
1107 }
1108
1109 int count = 2;
1110 String new_name = current;
1111 PackedStringArray split = new_name.split("_");
1112 int last_index = split.size() - 1;
1113 if (last_index > 0 && split[last_index].is_valid_int() && split[last_index].to_int() >= 0) {
1114 count = split[last_index].to_int();
1115 split.remove_at(last_index);
1116 new_name = String("_").join(split);
1117 }
1118 while (true) {
1119 String attempt = new_name;
1120 attempt += vformat("_%d", count);
1121 if (player->has_animation(attempt)) {
1122 count++;
1123 continue;
1124 }
1125 new_name = attempt;
1126 break;
1127 }
1128
1129 if (new_name.contains("/")) {
1130 // Discard library prefix.
1131 new_name = new_name.get_slice("/", 1);
1132 }
1133
1134 _update_name_dialog_library_dropdown();
1135
1136 name_dialog_op = TOOL_DUPLICATE_ANIM;
1137 name_dialog->set_title(TTR("Duplicate Animation"));
1138 // TRANSLATORS: This is a label for the new name field in the "Duplicate Animation" dialog.
1139 name_title->set_text(TTR("Duplicated Animation Name:"));
1140 name->set_text(new_name);
1141 name_dialog->popup_centered(Size2(300, 90));
1142 name->select_all();
1143 name->grab_focus();
1144}
1145
1146Ref<Animation> AnimationPlayerEditor::_animation_clone(Ref<Animation> p_anim) {
1147 Ref<Animation> new_anim = memnew(Animation);
1148 List<PropertyInfo> plist;
1149 p_anim->get_property_list(&plist);
1150
1151 for (const PropertyInfo &E : plist) {
1152 if (E.usage & PROPERTY_USAGE_STORAGE) {
1153 new_anim->set(E.name, p_anim->get(E.name));
1154 }
1155 }
1156 new_anim->set_path("");
1157
1158 return new_anim;
1159}
1160
1161void AnimationPlayerEditor::_seek_value_changed(float p_value, bool p_set, bool p_timeline_only) {
1162 if (updating || !player || player->is_playing()) {
1163 return;
1164 };
1165
1166 updating = true;
1167 String current = player->get_assigned_animation();
1168 if (current.is_empty() || !player->has_animation(current)) {
1169 updating = false;
1170 current = "";
1171 return;
1172 };
1173
1174 Ref<Animation> anim;
1175 anim = player->get_animation(current);
1176
1177 float pos = CLAMP((double)anim->get_length() * (p_value / frame->get_max()), 0, (double)anim->get_length());
1178 if (track_editor->is_snap_enabled()) {
1179 pos = Math::snapped(pos, _get_editor_step());
1180 }
1181
1182 if (!p_timeline_only) {
1183 if (player->is_valid() && !p_set) {
1184 float cpos = player->get_current_animation_position();
1185
1186 player->seek_delta(pos, pos - cpos);
1187 } else {
1188 player->stop();
1189 player->seek(pos, true);
1190 }
1191 }
1192
1193 track_editor->set_anim_pos(pos);
1194};
1195
1196void AnimationPlayerEditor::_animation_player_changed(Object *p_pl) {
1197 if (player == p_pl && is_visible_in_tree()) {
1198 _update_player();
1199 if (blend_editor.dialog->is_visible()) {
1200 _animation_blend(); // Update.
1201 }
1202 if (library_editor->is_visible()) {
1203 library_editor->update_tree();
1204 }
1205 }
1206}
1207
1208void AnimationPlayerEditor::_animation_libraries_updated() {
1209 _animation_player_changed(player);
1210}
1211
1212void AnimationPlayerEditor::_list_changed() {
1213 if (is_visible_in_tree()) {
1214 _update_player();
1215 }
1216}
1217
1218void AnimationPlayerEditor::_animation_key_editor_anim_len_changed(float p_len) {
1219 frame->set_max(p_len);
1220}
1221
1222void AnimationPlayerEditor::_animation_key_editor_seek(float p_pos, bool p_drag, bool p_timeline_only) {
1223 timeline_position = p_pos;
1224
1225 if (!is_visible_in_tree()) {
1226 return;
1227 }
1228
1229 if (!player) {
1230 return;
1231 }
1232
1233 if (player->is_playing()) {
1234 return;
1235 }
1236
1237 if (!player->has_animation(player->get_assigned_animation())) {
1238 return;
1239 }
1240
1241 updating = true;
1242 frame->set_value(Math::snapped(p_pos, _get_editor_step()));
1243 updating = false;
1244 _seek_value_changed(p_pos, !p_drag, p_timeline_only);
1245}
1246
1247void AnimationPlayerEditor::_animation_tool_menu(int p_option) {
1248 String current = _get_current();
1249
1250 Ref<Animation> anim;
1251 if (!current.is_empty()) {
1252 anim = player->get_animation(current);
1253 }
1254
1255 switch (p_option) {
1256 case TOOL_NEW_ANIM: {
1257 _animation_new();
1258 } break;
1259 case TOOL_ANIM_LIBRARY: {
1260 library_editor->set_animation_player(player);
1261 library_editor->show_dialog();
1262 } break;
1263 case TOOL_DUPLICATE_ANIM: {
1264 _animation_duplicate();
1265 } break;
1266 case TOOL_RENAME_ANIM: {
1267 _animation_rename();
1268 } break;
1269 case TOOL_EDIT_TRANSITIONS: {
1270 _animation_blend();
1271 } break;
1272 case TOOL_REMOVE_ANIM: {
1273 _animation_remove();
1274 } break;
1275 case TOOL_EDIT_RESOURCE: {
1276 if (anim.is_valid()) {
1277 EditorNode::get_singleton()->edit_resource(anim);
1278 }
1279 } break;
1280 }
1281}
1282
1283void AnimationPlayerEditor::_onion_skinning_menu(int p_option) {
1284 PopupMenu *menu = onion_skinning->get_popup();
1285 int idx = menu->get_item_index(p_option);
1286
1287 switch (p_option) {
1288 case ONION_SKINNING_ENABLE: {
1289 onion.enabled = !onion.enabled;
1290
1291 if (onion.enabled) {
1292 _start_onion_skinning();
1293 } else {
1294 _stop_onion_skinning();
1295 }
1296
1297 } break;
1298 case ONION_SKINNING_PAST: {
1299 // Ensure at least one of past/future is checked.
1300 onion.past = onion.future ? !onion.past : true;
1301 menu->set_item_checked(idx, onion.past);
1302 } break;
1303 case ONION_SKINNING_FUTURE: {
1304 // Ensure at least one of past/future is checked.
1305 onion.future = onion.past ? !onion.future : true;
1306 menu->set_item_checked(idx, onion.future);
1307 } break;
1308 case ONION_SKINNING_1_STEP: // Fall-through.
1309 case ONION_SKINNING_2_STEPS:
1310 case ONION_SKINNING_3_STEPS: {
1311 onion.steps = (p_option - ONION_SKINNING_1_STEP) + 1;
1312 int one_frame_idx = menu->get_item_index(ONION_SKINNING_1_STEP);
1313 for (int i = 0; i <= ONION_SKINNING_LAST_STEPS_OPTION - ONION_SKINNING_1_STEP; i++) {
1314 menu->set_item_checked(one_frame_idx + i, onion.steps == i + 1);
1315 }
1316 } break;
1317 case ONION_SKINNING_DIFFERENCES_ONLY: {
1318 onion.differences_only = !onion.differences_only;
1319 menu->set_item_checked(idx, onion.differences_only);
1320 } break;
1321 case ONION_SKINNING_FORCE_WHITE_MODULATE: {
1322 onion.force_white_modulate = !onion.force_white_modulate;
1323 menu->set_item_checked(idx, onion.force_white_modulate);
1324 } break;
1325 case ONION_SKINNING_INCLUDE_GIZMOS: {
1326 onion.include_gizmos = !onion.include_gizmos;
1327 menu->set_item_checked(idx, onion.include_gizmos);
1328 } break;
1329 }
1330}
1331
1332void AnimationPlayerEditor::shortcut_input(const Ref<InputEvent> &p_ev) {
1333 ERR_FAIL_COND(p_ev.is_null());
1334
1335 Ref<InputEventKey> k = p_ev;
1336 if (is_visible_in_tree() && k.is_valid() && k->is_pressed() && !k->is_echo() && !k->is_alt_pressed() && !k->is_ctrl_pressed() && !k->is_meta_pressed()) {
1337 switch (k->get_keycode()) {
1338 case Key::A: {
1339 if (!k->is_shift_pressed()) {
1340 _play_bw_from_pressed();
1341 } else {
1342 _play_bw_pressed();
1343 }
1344 accept_event();
1345 } break;
1346 case Key::S: {
1347 _stop_pressed();
1348 accept_event();
1349 } break;
1350 case Key::D: {
1351 if (!k->is_shift_pressed()) {
1352 _play_from_pressed();
1353 } else {
1354 _play_pressed();
1355 }
1356 accept_event();
1357 } break;
1358 default:
1359 break;
1360 }
1361 }
1362}
1363
1364void AnimationPlayerEditor::_editor_visibility_changed() {
1365 if (is_visible() && animation->has_selectable_items()) {
1366 _start_onion_skinning();
1367 }
1368}
1369
1370bool AnimationPlayerEditor::_are_onion_layers_valid() {
1371 ERR_FAIL_COND_V(!onion.past && !onion.future, false);
1372
1373 Point2 capture_size = get_tree()->get_root()->get_size();
1374 return onion.captures.size() == onion.get_needed_capture_count() && onion.capture_size == capture_size;
1375}
1376
1377void AnimationPlayerEditor::_allocate_onion_layers() {
1378 _free_onion_layers();
1379
1380 int captures = onion.get_needed_capture_count();
1381 Point2 capture_size = get_tree()->get_root()->get_size();
1382
1383 onion.captures.resize(captures);
1384 onion.captures_valid.resize(captures);
1385
1386 for (int i = 0; i < captures; i++) {
1387 bool is_present = onion.differences_only && i == captures - 1;
1388
1389 // Each capture is a viewport with a canvas item attached that renders a full-size rect with the contents of the main viewport.
1390 onion.captures.write[i] = RS::get_singleton()->viewport_create();
1391
1392 RS::get_singleton()->viewport_set_size(onion.captures[i], capture_size.width, capture_size.height);
1393 RS::get_singleton()->viewport_set_update_mode(onion.captures[i], RS::VIEWPORT_UPDATE_ALWAYS);
1394 RS::get_singleton()->viewport_set_transparent_background(onion.captures[i], !is_present);
1395 RS::get_singleton()->viewport_attach_canvas(onion.captures[i], onion.capture.canvas);
1396 }
1397
1398 // Reset the capture canvas item to the current root viewport texture (defensive).
1399 RS::get_singleton()->canvas_item_clear(onion.capture.canvas_item);
1400 RS::get_singleton()->canvas_item_add_texture_rect(onion.capture.canvas_item, Rect2(Point2(), capture_size), get_tree()->get_root()->get_texture()->get_rid());
1401
1402 onion.capture_size = capture_size;
1403}
1404
1405void AnimationPlayerEditor::_free_onion_layers() {
1406 for (int i = 0; i < onion.captures.size(); i++) {
1407 if (onion.captures[i].is_valid()) {
1408 RS::get_singleton()->free(onion.captures[i]);
1409 }
1410 }
1411 onion.captures.clear();
1412 onion.captures_valid.clear();
1413}
1414
1415void AnimationPlayerEditor::_prepare_onion_layers_1() {
1416 // This would be called per viewport and we want to act once only.
1417 int64_t cur_frame = get_tree()->get_frame();
1418 if (cur_frame == onion.last_frame) {
1419 return;
1420 }
1421
1422 if (!onion.enabled || !is_processing() || !is_visible() || !get_player()) {
1423 _stop_onion_skinning();
1424 return;
1425 }
1426
1427 onion.last_frame = cur_frame;
1428
1429 // Refresh viewports with no onion layers overlaid.
1430 onion.can_overlay = false;
1431 plugin->update_overlays();
1432
1433 if (player->is_playing()) {
1434 return;
1435 }
1436
1437 // And go to next step afterwards.
1438 call_deferred(SNAME("_prepare_onion_layers_2"));
1439}
1440
1441void AnimationPlayerEditor::_prepare_onion_layers_1_deferred() {
1442 call_deferred(SNAME("_prepare_onion_layers_1"));
1443}
1444
1445void AnimationPlayerEditor::_prepare_onion_layers_2() {
1446 Ref<Animation> anim = player->get_animation(player->get_assigned_animation());
1447 if (!anim.is_valid()) {
1448 return;
1449 }
1450
1451 if (!_are_onion_layers_valid()) {
1452 _allocate_onion_layers();
1453 }
1454
1455 // Hide superfluous elements that would make the overlay unnecessary cluttered.
1456 Dictionary canvas_edit_state;
1457 Dictionary spatial_edit_state;
1458 if (Node3DEditor::get_singleton()->is_visible()) {
1459 // 3D
1460 spatial_edit_state = Node3DEditor::get_singleton()->get_state();
1461 Dictionary new_state = spatial_edit_state.duplicate();
1462 new_state["show_grid"] = false;
1463 new_state["show_origin"] = false;
1464 Array orig_vp = spatial_edit_state["viewports"];
1465 Array vp;
1466 vp.resize(4);
1467 for (int i = 0; i < vp.size(); i++) {
1468 Dictionary d = ((Dictionary)orig_vp[i]).duplicate();
1469 d["use_environment"] = false;
1470 d["doppler"] = false;
1471 d["gizmos"] = onion.include_gizmos ? d["gizmos"] : Variant(false);
1472 d["information"] = false;
1473 vp[i] = d;
1474 }
1475 new_state["viewports"] = vp;
1476 // TODO: Save/restore only affected entries.
1477 Node3DEditor::get_singleton()->set_state(new_state);
1478 } else { // CanvasItemEditor
1479 // 2D
1480 canvas_edit_state = CanvasItemEditor::get_singleton()->get_state();
1481 Dictionary new_state = canvas_edit_state.duplicate();
1482 new_state["show_grid"] = false;
1483 new_state["show_rulers"] = false;
1484 new_state["show_guides"] = false;
1485 new_state["show_helpers"] = false;
1486 new_state["show_zoom_control"] = false;
1487 // TODO: Save/restore only affected entries.
1488 CanvasItemEditor::get_singleton()->set_state(new_state);
1489 }
1490
1491 // Tweak the root viewport to ensure it's rendered before our target.
1492 RID root_vp = get_tree()->get_root()->get_viewport_rid();
1493 Rect2 root_vp_screen_rect = Rect2(Vector2(), get_tree()->get_root()->get_size());
1494 RS::get_singleton()->viewport_attach_to_screen(root_vp, Rect2());
1495 RS::get_singleton()->viewport_set_update_mode(root_vp, RS::VIEWPORT_UPDATE_ALWAYS);
1496
1497 RID present_rid;
1498 if (onion.differences_only) {
1499 // Capture present scene as it is.
1500 RS::get_singleton()->canvas_item_set_material(onion.capture.canvas_item, RID());
1501 present_rid = onion.captures[onion.captures.size() - 1];
1502 RS::get_singleton()->viewport_set_active(present_rid, true);
1503 RS::get_singleton()->viewport_set_parent_viewport(root_vp, present_rid);
1504 RS::get_singleton()->draw(false);
1505 RS::get_singleton()->viewport_set_active(present_rid, false);
1506 }
1507
1508 // Backup current animation state.
1509 Ref<AnimatedValuesBackup> values_backup = player->backup_animated_values();
1510 float cpos = player->get_current_animation_position();
1511
1512 // Render every past/future step with the capture shader.
1513
1514 RS::get_singleton()->canvas_item_set_material(onion.capture.canvas_item, onion.capture.material->get_rid());
1515 onion.capture.material->set_shader_parameter("bkg_color", GLOBAL_GET("rendering/environment/defaults/default_clear_color"));
1516 onion.capture.material->set_shader_parameter("differences_only", onion.differences_only);
1517 onion.capture.material->set_shader_parameter("present", onion.differences_only ? RS::get_singleton()->viewport_get_texture(present_rid) : RID());
1518
1519 int step_off_a = onion.past ? -onion.steps : 0;
1520 int step_off_b = onion.future ? onion.steps : 0;
1521 int cidx = 0;
1522 onion.capture.material->set_shader_parameter("dir_color", onion.force_white_modulate ? Color(1, 1, 1) : Color(EDITOR_GET("editors/animation/onion_layers_past_color")));
1523 for (int step_off = step_off_a; step_off <= step_off_b; step_off++) {
1524 if (step_off == 0) {
1525 // Skip present step and switch to the color of future.
1526 if (!onion.force_white_modulate) {
1527 onion.capture.material->set_shader_parameter("dir_color", EDITOR_GET("editors/animation/onion_layers_future_color"));
1528 }
1529 continue;
1530 }
1531
1532 float pos = cpos + step_off * anim->get_step();
1533
1534 bool valid = anim->get_loop_mode() != Animation::LOOP_NONE || (pos >= 0 && pos <= anim->get_length());
1535 onion.captures_valid.write[cidx] = valid;
1536 if (valid) {
1537 player->seek(pos, true);
1538 get_tree()->flush_transform_notifications(); // Needed for transforms of Node3Ds.
1539 values_backup->update_skeletons(); // Needed for Skeletons (2D & 3D).
1540
1541 RS::get_singleton()->viewport_set_active(onion.captures[cidx], true);
1542 RS::get_singleton()->viewport_set_parent_viewport(root_vp, onion.captures[cidx]);
1543 RS::get_singleton()->draw(false);
1544 RS::get_singleton()->viewport_set_active(onion.captures[cidx], false);
1545 }
1546
1547 cidx++;
1548 }
1549
1550 // Restore root viewport.
1551 RS::get_singleton()->viewport_set_parent_viewport(root_vp, RID());
1552 RS::get_singleton()->viewport_attach_to_screen(root_vp, root_vp_screen_rect);
1553 RS::get_singleton()->viewport_set_update_mode(root_vp, RS::VIEWPORT_UPDATE_WHEN_VISIBLE);
1554
1555 // Restore animation state
1556 // (Seeking with update=true wouldn't do the trick because the current value of the properties
1557 // may not match their value for the current point in the animation).
1558 player->seek(cpos, false);
1559 values_backup->restore();
1560
1561 // Restore state of main editors.
1562 if (Node3DEditor::get_singleton()->is_visible()) {
1563 // 3D
1564 Node3DEditor::get_singleton()->set_state(spatial_edit_state);
1565 } else { // CanvasItemEditor
1566 // 2D
1567 CanvasItemEditor::get_singleton()->set_state(canvas_edit_state);
1568 }
1569
1570 // Update viewports with skin layers overlaid for the actual engine loop render.
1571 onion.can_overlay = true;
1572 plugin->update_overlays();
1573}
1574
1575void AnimationPlayerEditor::_start_onion_skinning() {
1576 // FIXME: Using "process_frame" makes onion layers update one frame behind the current.
1577 if (!get_tree()->is_connected("process_frame", callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred))) {
1578 get_tree()->connect("process_frame", callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred));
1579 }
1580}
1581
1582void AnimationPlayerEditor::_stop_onion_skinning() {
1583 if (get_tree()->is_connected("process_frame", callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred))) {
1584 get_tree()->disconnect("process_frame", callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred));
1585
1586 _free_onion_layers();
1587
1588 // Clean up the overlay.
1589 onion.can_overlay = false;
1590 plugin->update_overlays();
1591 }
1592}
1593
1594void AnimationPlayerEditor::_pin_pressed() {
1595 SceneTreeDock::get_singleton()->get_tree_editor()->update_tree();
1596}
1597
1598bool AnimationPlayerEditor::_validate_tracks(const Ref<Animation> p_anim) {
1599 bool is_valid = true;
1600 if (!p_anim.is_valid()) {
1601 return true; // There is a problem outside of the animation track.
1602 }
1603 int len = p_anim->get_track_count();
1604 for (int i = 0; i < len; i++) {
1605 Animation::TrackType ttype = p_anim->track_get_type(i);
1606 if (ttype == Animation::TYPE_ROTATION_3D) {
1607 int key_len = p_anim->track_get_key_count(i);
1608 for (int j = 0; j < key_len; j++) {
1609 Quaternion q;
1610 p_anim->rotation_track_get_key(i, j, &q);
1611 ERR_BREAK_EDMSG(!q.is_normalized(), "AnimationPlayer: '" + player->get_name() + "', Animation: '" + player->get_current_animation() + "', 3D Rotation Track: '" + p_anim->track_get_path(i) + "' contains unnormalized Quaternion key.");
1612 }
1613 } else if (ttype == Animation::TYPE_VALUE) {
1614 int key_len = p_anim->track_get_key_count(i);
1615 if (key_len == 0) {
1616 continue;
1617 }
1618 switch (p_anim->track_get_key_value(i, 0).get_type()) {
1619 case Variant::QUATERNION: {
1620 for (int j = 0; j < key_len; j++) {
1621 Quaternion q = Quaternion(p_anim->track_get_key_value(i, j));
1622 if (!q.is_normalized()) {
1623 is_valid = false;
1624 ERR_BREAK_EDMSG(true, "AnimationPlayer: '" + player->get_name() + "', Animation: '" + player->get_current_animation() + "', Value Track: '" + p_anim->track_get_path(i) + "' contains unnormalized Quaternion key.");
1625 }
1626 }
1627 } break;
1628 case Variant::TRANSFORM3D: {
1629 for (int j = 0; j < key_len; j++) {
1630 Transform3D t = Transform3D(p_anim->track_get_key_value(i, j));
1631 if (!t.basis.orthonormalized().is_rotation()) {
1632 is_valid = false;
1633 ERR_BREAK_EDMSG(true, "AnimationPlayer: '" + player->get_name() + "', Animation: '" + player->get_current_animation() + "', Value Track: '" + p_anim->track_get_path(i) + "' contains corrupted basis (some axes are too close other axis or scaled by zero) Transform3D key.");
1634 }
1635 }
1636 } break;
1637 default: {
1638 } break;
1639 }
1640 }
1641 }
1642 return is_valid;
1643}
1644
1645void AnimationPlayerEditor::_bind_methods() {
1646 ClassDB::bind_method(D_METHOD("_animation_new"), &AnimationPlayerEditor::_animation_new);
1647 ClassDB::bind_method(D_METHOD("_animation_rename"), &AnimationPlayerEditor::_animation_rename);
1648 ClassDB::bind_method(D_METHOD("_animation_remove"), &AnimationPlayerEditor::_animation_remove);
1649 ClassDB::bind_method(D_METHOD("_animation_blend"), &AnimationPlayerEditor::_animation_blend);
1650 ClassDB::bind_method(D_METHOD("_animation_edit"), &AnimationPlayerEditor::_animation_edit);
1651 ClassDB::bind_method(D_METHOD("_animation_resource_edit"), &AnimationPlayerEditor::_animation_resource_edit);
1652 ClassDB::bind_method(D_METHOD("_animation_player_changed"), &AnimationPlayerEditor::_animation_player_changed);
1653 ClassDB::bind_method(D_METHOD("_animation_libraries_updated"), &AnimationPlayerEditor::_animation_libraries_updated);
1654 ClassDB::bind_method(D_METHOD("_list_changed"), &AnimationPlayerEditor::_list_changed);
1655 ClassDB::bind_method(D_METHOD("_animation_duplicate"), &AnimationPlayerEditor::_animation_duplicate);
1656
1657 ClassDB::bind_method(D_METHOD("_prepare_onion_layers_1"), &AnimationPlayerEditor::_prepare_onion_layers_1);
1658 ClassDB::bind_method(D_METHOD("_prepare_onion_layers_2"), &AnimationPlayerEditor::_prepare_onion_layers_2);
1659 ClassDB::bind_method(D_METHOD("_start_onion_skinning"), &AnimationPlayerEditor::_start_onion_skinning);
1660 ClassDB::bind_method(D_METHOD("_stop_onion_skinning"), &AnimationPlayerEditor::_stop_onion_skinning);
1661
1662 ADD_SIGNAL(MethodInfo("animation_selected", PropertyInfo(Variant::STRING, "name")));
1663}
1664
1665AnimationPlayerEditor *AnimationPlayerEditor::singleton = nullptr;
1666
1667AnimationPlayer *AnimationPlayerEditor::get_player() const {
1668 return player;
1669}
1670
1671AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plugin) {
1672 plugin = p_plugin;
1673 singleton = this;
1674
1675 updating = false;
1676
1677 set_focus_mode(FOCUS_ALL);
1678
1679 player = nullptr;
1680
1681 HBoxContainer *hb = memnew(HBoxContainer);
1682 add_child(hb);
1683
1684 play_bw_from = memnew(Button);
1685 play_bw_from->set_flat(true);
1686 play_bw_from->set_tooltip_text(TTR("Play selected animation backwards from current pos. (A)"));
1687 hb->add_child(play_bw_from);
1688
1689 play_bw = memnew(Button);
1690 play_bw->set_flat(true);
1691 play_bw->set_tooltip_text(TTR("Play selected animation backwards from end. (Shift+A)"));
1692 hb->add_child(play_bw);
1693
1694 stop = memnew(Button);
1695 stop->set_flat(true);
1696 hb->add_child(stop);
1697 stop->set_tooltip_text(TTR("Pause/stop animation playback. (S)"));
1698
1699 play = memnew(Button);
1700 play->set_flat(true);
1701 play->set_tooltip_text(TTR("Play selected animation from start. (Shift+D)"));
1702 hb->add_child(play);
1703
1704 play_from = memnew(Button);
1705 play_from->set_flat(true);
1706 play_from->set_tooltip_text(TTR("Play selected animation from current pos. (D)"));
1707 hb->add_child(play_from);
1708
1709 frame = memnew(SpinBox);
1710 hb->add_child(frame);
1711 frame->set_custom_minimum_size(Size2(80, 0) * EDSCALE);
1712 frame->set_stretch_ratio(2);
1713 frame->set_step(0.0001);
1714 frame->set_tooltip_text(TTR("Animation position (in seconds)."));
1715
1716 hb->add_child(memnew(VSeparator));
1717
1718 scale = memnew(LineEdit);
1719 hb->add_child(scale);
1720 scale->set_h_size_flags(SIZE_EXPAND_FILL);
1721 scale->set_stretch_ratio(1);
1722 scale->set_tooltip_text(TTR("Scale animation playback globally for the node."));
1723 scale->hide();
1724
1725 delete_dialog = memnew(ConfirmationDialog);
1726 add_child(delete_dialog);
1727 delete_dialog->connect("confirmed", callable_mp(this, &AnimationPlayerEditor::_animation_remove_confirmed));
1728
1729 tool_anim = memnew(MenuButton);
1730 tool_anim->set_shortcut_context(this);
1731 tool_anim->set_flat(false);
1732 tool_anim->set_tooltip_text(TTR("Animation Tools"));
1733 tool_anim->set_text(TTR("Animation"));
1734 tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/new_animation", TTR("New")), TOOL_NEW_ANIM);
1735 tool_anim->get_popup()->add_separator();
1736 tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/animation_libraries", TTR("Manage Animations...")), TOOL_ANIM_LIBRARY);
1737 tool_anim->get_popup()->add_separator();
1738 tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/duplicate_animation", TTR("Duplicate...")), TOOL_DUPLICATE_ANIM);
1739 tool_anim->get_popup()->add_separator();
1740 tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/rename_animation", TTR("Rename...")), TOOL_RENAME_ANIM);
1741 tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/edit_transitions", TTR("Edit Transitions...")), TOOL_EDIT_TRANSITIONS);
1742 tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/open_animation_in_inspector", TTR("Open in Inspector")), TOOL_EDIT_RESOURCE);
1743 tool_anim->get_popup()->add_separator();
1744 tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/remove_animation", TTR("Remove")), TOOL_REMOVE_ANIM);
1745 tool_anim->set_disabled(true);
1746 hb->add_child(tool_anim);
1747
1748 animation = memnew(OptionButton);
1749 hb->add_child(animation);
1750 animation->set_h_size_flags(SIZE_EXPAND_FILL);
1751 animation->set_tooltip_text(TTR("Display list of animations in player."));
1752 animation->set_clip_text(true);
1753 animation->set_auto_translate(false);
1754
1755 autoplay = memnew(Button);
1756 autoplay->set_flat(true);
1757 hb->add_child(autoplay);
1758 autoplay->set_tooltip_text(TTR("Autoplay on Load"));
1759
1760 hb->add_child(memnew(VSeparator));
1761
1762 track_editor = memnew(AnimationTrackEditor);
1763
1764 hb->add_child(track_editor->get_edit_menu());
1765
1766 hb->add_child(memnew(VSeparator));
1767
1768 onion_toggle = memnew(Button);
1769 onion_toggle->set_flat(true);
1770 onion_toggle->set_toggle_mode(true);
1771 onion_toggle->set_tooltip_text(TTR("Enable Onion Skinning"));
1772 onion_toggle->connect("pressed", callable_mp(this, &AnimationPlayerEditor::_onion_skinning_menu).bind(ONION_SKINNING_ENABLE));
1773 hb->add_child(onion_toggle);
1774
1775 onion_skinning = memnew(MenuButton);
1776 onion_skinning->set_tooltip_text(TTR("Onion Skinning Options"));
1777 onion_skinning->get_popup()->add_separator(TTR("Directions"));
1778 // TRANSLATORS: Opposite of "Future", refers to a direction in animation onion skinning.
1779 onion_skinning->get_popup()->add_check_item(TTR("Past"), ONION_SKINNING_PAST);
1780 onion_skinning->get_popup()->set_item_checked(-1, true);
1781 // TRANSLATORS: Opposite of "Past", refers to a direction in animation onion skinning.
1782 onion_skinning->get_popup()->add_check_item(TTR("Future"), ONION_SKINNING_FUTURE);
1783 onion_skinning->get_popup()->add_separator(TTR("Depth"));
1784 onion_skinning->get_popup()->add_radio_check_item(TTR("1 step"), ONION_SKINNING_1_STEP);
1785 onion_skinning->get_popup()->set_item_checked(-1, true);
1786 onion_skinning->get_popup()->add_radio_check_item(TTR("2 steps"), ONION_SKINNING_2_STEPS);
1787 onion_skinning->get_popup()->add_radio_check_item(TTR("3 steps"), ONION_SKINNING_3_STEPS);
1788 onion_skinning->get_popup()->add_separator();
1789 onion_skinning->get_popup()->add_check_item(TTR("Differences Only"), ONION_SKINNING_DIFFERENCES_ONLY);
1790 onion_skinning->get_popup()->add_check_item(TTR("Force White Modulate"), ONION_SKINNING_FORCE_WHITE_MODULATE);
1791 onion_skinning->get_popup()->add_check_item(TTR("Include Gizmos (3D)"), ONION_SKINNING_INCLUDE_GIZMOS);
1792 hb->add_child(onion_skinning);
1793
1794 // FIXME: Onion skinning disabled for now as it's broken and triggers fast
1795 // flickering red/blue modulation (GH-53870).
1796 if (hack_disable_onion_skinning) {
1797 onion_toggle->set_disabled(true);
1798 onion_toggle->set_tooltip_text(TTR("Onion Skinning temporarily disabled due to rendering bug."));
1799
1800 onion_skinning->set_disabled(true);
1801 onion_skinning->set_tooltip_text(TTR("Onion Skinning temporarily disabled due to rendering bug."));
1802 }
1803
1804 hb->add_child(memnew(VSeparator));
1805
1806 pin = memnew(Button);
1807 pin->set_flat(true);
1808 pin->set_toggle_mode(true);
1809 pin->set_tooltip_text(TTR("Pin AnimationPlayer"));
1810 hb->add_child(pin);
1811 pin->connect("pressed", callable_mp(this, &AnimationPlayerEditor::_pin_pressed));
1812
1813 file = memnew(EditorFileDialog);
1814 add_child(file);
1815
1816 name_dialog = memnew(ConfirmationDialog);
1817 name_dialog->set_title(TTR("Create New Animation"));
1818 name_dialog->set_hide_on_ok(false);
1819 add_child(name_dialog);
1820 VBoxContainer *vb = memnew(VBoxContainer);
1821 name_dialog->add_child(vb);
1822
1823 name_title = memnew(Label(TTR("Animation Name:")));
1824 vb->add_child(name_title);
1825
1826 HBoxContainer *name_hb = memnew(HBoxContainer);
1827 name = memnew(LineEdit);
1828 name_hb->add_child(name);
1829 name->set_h_size_flags(SIZE_EXPAND_FILL);
1830 library = memnew(OptionButton);
1831 name_hb->add_child(library);
1832 library->hide();
1833 vb->add_child(name_hb);
1834 name_dialog->register_text_enter(name);
1835
1836 error_dialog = memnew(ConfirmationDialog);
1837 error_dialog->set_ok_button_text(TTR("Close"));
1838 error_dialog->set_title(TTR("Error!"));
1839 add_child(error_dialog);
1840
1841 name_dialog->connect("confirmed", callable_mp(this, &AnimationPlayerEditor::_animation_name_edited));
1842
1843 blend_editor.dialog = memnew(AcceptDialog);
1844 add_child(blend_editor.dialog);
1845 blend_editor.dialog->set_ok_button_text(TTR("Close"));
1846 blend_editor.dialog->set_hide_on_ok(true);
1847 VBoxContainer *blend_vb = memnew(VBoxContainer);
1848 blend_editor.dialog->add_child(blend_vb);
1849 blend_editor.tree = memnew(Tree);
1850 blend_editor.tree->set_columns(2);
1851 blend_vb->add_margin_child(TTR("Blend Times:"), blend_editor.tree, true);
1852 blend_editor.next = memnew(OptionButton);
1853 blend_editor.next->set_auto_translate(false);
1854 blend_vb->add_margin_child(TTR("Next (Auto Queue):"), blend_editor.next);
1855 blend_editor.dialog->set_title(TTR("Cross-Animation Blend Times"));
1856 updating_blends = false;
1857
1858 blend_editor.tree->connect("item_edited", callable_mp(this, &AnimationPlayerEditor::_blend_edited));
1859
1860 autoplay->connect("pressed", callable_mp(this, &AnimationPlayerEditor::_autoplay_pressed));
1861 autoplay->set_toggle_mode(true);
1862 play->connect("pressed", callable_mp(this, &AnimationPlayerEditor::_play_pressed));
1863 play_from->connect("pressed", callable_mp(this, &AnimationPlayerEditor::_play_from_pressed));
1864 play_bw->connect("pressed", callable_mp(this, &AnimationPlayerEditor::_play_bw_pressed));
1865 play_bw_from->connect("pressed", callable_mp(this, &AnimationPlayerEditor::_play_bw_from_pressed));
1866 stop->connect("pressed", callable_mp(this, &AnimationPlayerEditor::_stop_pressed));
1867
1868 animation->connect("item_selected", callable_mp(this, &AnimationPlayerEditor::_animation_selected));
1869
1870 frame->connect("value_changed", callable_mp(this, &AnimationPlayerEditor::_seek_value_changed).bind(true, false));
1871 scale->connect("text_submitted", callable_mp(this, &AnimationPlayerEditor::_scale_changed));
1872
1873 last_active = false;
1874 timeline_position = 0;
1875
1876 set_process_shortcut_input(true);
1877
1878 add_child(track_editor);
1879 track_editor->set_v_size_flags(SIZE_EXPAND_FILL);
1880 track_editor->connect("timeline_changed", callable_mp(this, &AnimationPlayerEditor::_animation_key_editor_seek));
1881 track_editor->connect("animation_len_changed", callable_mp(this, &AnimationPlayerEditor::_animation_key_editor_anim_len_changed));
1882
1883 _update_player();
1884
1885 library_editor = memnew(AnimationLibraryEditor);
1886 add_child(library_editor);
1887 library_editor->connect("update_editor", callable_mp(this, &AnimationPlayerEditor::_animation_player_changed));
1888
1889 // Onion skinning.
1890
1891 track_editor->connect("visibility_changed", callable_mp(this, &AnimationPlayerEditor::_editor_visibility_changed));
1892
1893 onion.enabled = false;
1894 onion.past = true;
1895 onion.future = false;
1896 onion.steps = 1;
1897 onion.differences_only = false;
1898 onion.force_white_modulate = false;
1899 onion.include_gizmos = false;
1900
1901 onion.last_frame = 0;
1902 onion.can_overlay = false;
1903 onion.capture_size = Size2();
1904 onion.capture.canvas = RS::get_singleton()->canvas_create();
1905 onion.capture.canvas_item = RS::get_singleton()->canvas_item_create();
1906 RS::get_singleton()->canvas_item_set_parent(onion.capture.canvas_item, onion.capture.canvas);
1907
1908 onion.capture.material = Ref<ShaderMaterial>(memnew(ShaderMaterial));
1909
1910 onion.capture.shader = Ref<Shader>(memnew(Shader));
1911 onion.capture.shader->set_code(R"(
1912// Animation editor onion skinning shader.
1913
1914shader_type canvas_item;
1915
1916uniform vec4 bkg_color;
1917uniform vec4 dir_color;
1918uniform bool differences_only;
1919uniform sampler2D present;
1920
1921float zero_if_equal(vec4 a, vec4 b) {
1922 return smoothstep(0.0, 0.005, length(a.rgb - b.rgb) / sqrt(3.0));
1923}
1924
1925void fragment() {
1926 vec4 capture_samp = texture(TEXTURE, UV);
1927 vec4 present_samp = texture(present, UV);
1928 float bkg_mask = zero_if_equal(capture_samp, bkg_color);
1929 float diff_mask = 1.0 - zero_if_equal(present_samp, bkg_color);
1930 diff_mask = min(1.0, diff_mask + float(!differences_only));
1931 COLOR = vec4(capture_samp.rgb * dir_color.rgb, bkg_mask * diff_mask);
1932}
1933)");
1934 RS::get_singleton()->material_set_shader(onion.capture.material->get_rid(), onion.capture.shader->get_rid());
1935}
1936
1937AnimationPlayerEditor::~AnimationPlayerEditor() {
1938 _free_onion_layers();
1939 RS::get_singleton()->free(onion.capture.canvas);
1940 RS::get_singleton()->free(onion.capture.canvas_item);
1941}
1942
1943void AnimationPlayerEditorPlugin::_notification(int p_what) {
1944 switch (p_what) {
1945 case NOTIFICATION_ENTER_TREE: {
1946 Node3DEditor::get_singleton()->connect("transform_key_request", callable_mp(this, &AnimationPlayerEditorPlugin::_transform_key_request));
1947 InspectorDock::get_inspector_singleton()->connect("property_keyed", callable_mp(this, &AnimationPlayerEditorPlugin::_property_keyed));
1948 anim_editor->get_track_editor()->connect("keying_changed", callable_mp(this, &AnimationPlayerEditorPlugin::_update_keying));
1949 InspectorDock::get_inspector_singleton()->connect("edited_object_changed", callable_mp(anim_editor->get_track_editor(), &AnimationTrackEditor::update_keying));
1950 set_force_draw_over_forwarding_enabled();
1951 } break;
1952 }
1953}
1954
1955void AnimationPlayerEditorPlugin::_property_keyed(const String &p_keyed, const Variant &p_value, bool p_advance) {
1956 AnimationTrackEditor *te = anim_editor->get_track_editor();
1957 if (!te || !te->has_keying()) {
1958 return;
1959 }
1960 te->_clear_selection();
1961 te->insert_value_key(p_keyed, p_value, p_advance);
1962}
1963
1964void AnimationPlayerEditorPlugin::_transform_key_request(Object *sp, const String &p_sub, const Transform3D &p_key) {
1965 if (!anim_editor->get_track_editor()->has_keying()) {
1966 return;
1967 }
1968 Node3D *s = Object::cast_to<Node3D>(sp);
1969 if (!s) {
1970 return;
1971 }
1972 anim_editor->get_track_editor()->insert_transform_key(s, p_sub, Animation::TYPE_POSITION_3D, p_key.origin);
1973 anim_editor->get_track_editor()->insert_transform_key(s, p_sub, Animation::TYPE_ROTATION_3D, p_key.basis.get_rotation_quaternion());
1974 anim_editor->get_track_editor()->insert_transform_key(s, p_sub, Animation::TYPE_SCALE_3D, p_key.basis.get_scale());
1975}
1976
1977void AnimationPlayerEditorPlugin::_update_keying() {
1978 InspectorDock::get_inspector_singleton()->set_keying(anim_editor->get_track_editor()->has_keying());
1979}
1980
1981void AnimationPlayerEditorPlugin::edit(Object *p_object) {
1982 if (!p_object) {
1983 return;
1984 }
1985 anim_editor->edit(Object::cast_to<AnimationPlayer>(p_object));
1986}
1987
1988bool AnimationPlayerEditorPlugin::handles(Object *p_object) const {
1989 return p_object->is_class("AnimationPlayer");
1990}
1991
1992void AnimationPlayerEditorPlugin::make_visible(bool p_visible) {
1993 if (p_visible) {
1994 EditorNode::get_singleton()->make_bottom_panel_item_visible(anim_editor);
1995 anim_editor->set_process(true);
1996 anim_editor->ensure_visibility();
1997 }
1998}
1999
2000AnimationPlayerEditorPlugin::AnimationPlayerEditorPlugin() {
2001 anim_editor = memnew(AnimationPlayerEditor(this));
2002 EditorNode::get_singleton()->add_bottom_panel_item(TTR("Animation"), anim_editor);
2003}
2004
2005AnimationPlayerEditorPlugin::~AnimationPlayerEditorPlugin() {
2006}
2007
2008// AnimationTrackKeyEditEditorPlugin
2009
2010bool EditorInspectorPluginAnimationTrackKeyEdit::can_handle(Object *p_object) {
2011 return Object::cast_to<AnimationTrackKeyEdit>(p_object) != nullptr;
2012}
2013
2014void EditorInspectorPluginAnimationTrackKeyEdit::parse_begin(Object *p_object) {
2015 AnimationTrackKeyEdit *atk = Object::cast_to<AnimationTrackKeyEdit>(p_object);
2016 ERR_FAIL_NULL(atk);
2017
2018 atk_editor = memnew(AnimationTrackKeyEditEditor(atk->animation, atk->track, atk->key_ofs, atk->use_fps));
2019 add_custom_control(atk_editor);
2020}
2021
2022AnimationTrackKeyEditEditorPlugin::AnimationTrackKeyEditEditorPlugin() {
2023 atk_plugin = memnew(EditorInspectorPluginAnimationTrackKeyEdit);
2024 EditorInspector::add_inspector_plugin(atk_plugin);
2025}
2026
2027bool AnimationTrackKeyEditEditorPlugin::handles(Object *p_object) const {
2028 return p_object->is_class("AnimationTrackKeyEdit");
2029}
2030