Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/animation/animation_player_editor_plugin.cpp
20931 views
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/os/keyboard.h"
36
#include "editor/animation/animation_tree_editor_plugin.h"
37
#include "editor/docks/editor_dock_manager.h"
38
#include "editor/docks/inspector_dock.h"
39
#include "editor/docks/scene_tree_dock.h"
40
#include "editor/editor_node.h"
41
#include "editor/editor_undo_redo_manager.h"
42
#include "editor/gui/editor_file_dialog.h"
43
#include "editor/gui/editor_validation_panel.h"
44
#include "editor/scene/3d/node_3d_editor_plugin.h" // For onion skinning.
45
#include "editor/scene/canvas_item_editor_plugin.h" // For onion skinning.
46
#include "editor/settings/editor_command_palette.h"
47
#include "editor/settings/editor_settings.h"
48
#include "editor/themes/editor_scale.h"
49
#include "editor/themes/editor_theme_manager.h"
50
#include "scene/animation/animation_tree.h"
51
#include "scene/gui/separator.h"
52
#include "scene/main/window.h"
53
#include "scene/resources/animation.h"
54
#include "scene/resources/image_texture.h"
55
#include "servers/rendering/rendering_server.h"
56
57
///////////////////////////////////
58
59
void AnimationPlayerEditor::_find_player() {
60
if (!is_visible() || player) {
61
return;
62
}
63
64
Node *edited_scene = EditorNode::get_singleton()->get_edited_scene();
65
66
if (!edited_scene) {
67
return;
68
}
69
70
TypedArray<Node> players = edited_scene->find_children("", "AnimationPlayer");
71
72
if (players.size() == 1) {
73
// Replicating EditorNode::_plugin_over_edit to ensure an identical setup as when selecting manually.
74
plugin->edit(players.front());
75
plugin->make_visible(true);
76
}
77
}
78
79
void AnimationPlayerEditor::_node_removed(Node *p_node) {
80
if (player && original_node == p_node) {
81
if (is_dummy) {
82
plugin->_clear_dummy_player();
83
}
84
85
player = nullptr;
86
87
set_process(false);
88
89
track_editor->set_animation(Ref<Animation>(), true);
90
track_editor->set_root(nullptr);
91
track_editor->show_select_node_warning(true);
92
_update_player();
93
94
_ensure_dummy_player();
95
96
pin->set_pressed(false);
97
}
98
}
99
100
void AnimationPlayerEditor::_notification(int p_what) {
101
switch (p_what) {
102
case NOTIFICATION_PROCESS: {
103
finishing = false;
104
if (!player || is_dummy) {
105
track_editor->show_inactive_player_warning(false);
106
} else {
107
track_editor->show_inactive_player_warning(!player->is_active());
108
}
109
110
if (!player) {
111
return;
112
}
113
114
updating = true;
115
116
if (player->is_playing()) {
117
{
118
StringName animname = player->get_assigned_animation();
119
120
if (player->has_animation(animname)) {
121
Ref<Animation> anim = player->get_animation(animname);
122
if (anim.is_valid()) {
123
frame->set_max((double)anim->get_length());
124
}
125
}
126
}
127
frame->set_value(player->get_current_animation_position());
128
track_editor->set_anim_pos(player->get_current_animation_position());
129
} else if (!player->is_valid()) {
130
// Reset timeline when the player has been stopped externally
131
frame->set_value(0);
132
} else if (last_active) {
133
// Need the last frame after it stopped.
134
frame->set_value(player->get_current_animation_position());
135
track_editor->set_anim_pos(player->get_current_animation_position());
136
stop->set_button_icon(stop_icon);
137
}
138
139
last_active = player->is_playing();
140
141
updating = false;
142
} break;
143
144
case NOTIFICATION_ENTER_TREE: {
145
get_tree()->connect(SNAME("node_removed"), callable_mp(this, &AnimationPlayerEditor::_node_removed));
146
} break;
147
148
case NOTIFICATION_EXIT_TREE: {
149
get_tree()->disconnect(SNAME("node_removed"), callable_mp(this, &AnimationPlayerEditor::_node_removed));
150
} break;
151
152
case NOTIFICATION_READY: {
153
EditorNode::get_singleton()->connect("scene_changed", callable_mp(this, &AnimationPlayerEditor::_find_player));
154
155
add_theme_style_override(SceneStringName(panel), EditorNode::get_singleton()->get_editor_theme()->get_stylebox(SceneStringName(panel), SNAME("Panel")));
156
157
_update_playback_tooltips();
158
} break;
159
160
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
161
if (EditorThemeManager::is_generated_theme_outdated()) {
162
add_theme_style_override(SceneStringName(panel), EditorNode::get_singleton()->get_editor_theme()->get_stylebox(SceneStringName(panel), SNAME("Panel")));
163
}
164
165
_update_playback_tooltips();
166
} break;
167
168
case NOTIFICATION_TRANSLATION_CHANGED:
169
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
170
case NOTIFICATION_THEME_CHANGED: {
171
stop_icon = get_editor_theme_icon(SNAME("Stop"));
172
pause_icon = get_editor_theme_icon(SNAME("Pause"));
173
if (player && player->is_playing()) {
174
stop->set_button_icon(pause_icon);
175
} else {
176
stop->set_button_icon(stop_icon);
177
}
178
179
autoplay->set_button_icon(get_editor_theme_icon(SNAME("AutoPlay")));
180
play->set_button_icon(get_editor_theme_icon(SNAME("PlayStart")));
181
play_from->set_button_icon(get_editor_theme_icon(SNAME("Play")));
182
play_bw->set_button_icon(get_editor_theme_icon(SNAME("PlayStartBackwards")));
183
play_bw_from->set_button_icon(get_editor_theme_icon(SNAME("PlayBackwards")));
184
185
autoplay_icon = get_editor_theme_icon(SNAME("AutoPlay"));
186
reset_icon = get_editor_theme_icon(SNAME("Reload"));
187
{
188
Ref<Image> autoplay_img = autoplay_icon->get_image();
189
Ref<Image> reset_img = reset_icon->get_image();
190
Size2 icon_size = autoplay_img->get_size();
191
Ref<Image> autoplay_reset_img = Image::create_empty(icon_size.x * 2, icon_size.y, false, autoplay_img->get_format());
192
autoplay_reset_img->blit_rect(autoplay_img, Rect2i(Point2i(), icon_size), Point2i());
193
autoplay_reset_img->blit_rect(reset_img, Rect2i(Point2i(), icon_size), Point2i(icon_size.x, 0));
194
autoplay_reset_icon = ImageTexture::create_from_image(autoplay_reset_img);
195
}
196
197
onion_toggle->set_button_icon(get_editor_theme_icon(SNAME("Onion")));
198
onion_skinning->set_button_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl")));
199
200
pin->set_button_icon(get_editor_theme_icon(SNAME("Pin")));
201
202
#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)))
203
204
ITEM_ICON(TOOL_NEW_ANIM, "New");
205
ITEM_ICON(TOOL_ANIM_LIBRARY, "AnimationLibrary");
206
ITEM_ICON(TOOL_DUPLICATE_ANIM, "Duplicate");
207
ITEM_ICON(TOOL_RENAME_ANIM, "Rename");
208
ITEM_ICON(TOOL_EDIT_TRANSITIONS, "Blend");
209
ITEM_ICON(TOOL_EDIT_RESOURCE, "Edit");
210
ITEM_ICON(TOOL_REMOVE_ANIM, "Remove");
211
212
_update_animation_list_icons();
213
} break;
214
215
case NOTIFICATION_VISIBILITY_CHANGED: {
216
_find_player();
217
_ensure_dummy_player();
218
} break;
219
}
220
}
221
222
void AnimationPlayerEditor::_autoplay_pressed() {
223
if (updating) {
224
return;
225
}
226
if (animation->has_selectable_items() == 0) {
227
return;
228
}
229
230
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
231
String current = animation->get_item_text(animation->get_selected());
232
if (player->get_autoplay() == current) {
233
//unset
234
undo_redo->create_action(TTR("Toggle Autoplay"));
235
undo_redo->add_do_method(player, "set_autoplay", StringName());
236
undo_redo->add_undo_method(player, "set_autoplay", player->get_autoplay());
237
undo_redo->add_do_method(this, "_animation_player_changed", player);
238
undo_redo->add_undo_method(this, "_animation_player_changed", player);
239
undo_redo->commit_action();
240
241
} else {
242
//set
243
undo_redo->create_action(TTR("Toggle Autoplay"));
244
undo_redo->add_do_method(player, "set_autoplay", StringName(current));
245
undo_redo->add_undo_method(player, "set_autoplay", player->get_autoplay());
246
undo_redo->add_do_method(this, "_animation_player_changed", player);
247
undo_redo->add_undo_method(this, "_animation_player_changed", player);
248
undo_redo->commit_action();
249
}
250
}
251
252
void AnimationPlayerEditor::go_to_nearest_keyframe(bool p_backward) {
253
if (_get_current().is_empty()) {
254
return;
255
}
256
257
Ref<Animation> anim = player->get_animation(player->get_assigned_animation());
258
259
double current_time = player->get_current_animation_position();
260
// Offset the time to avoid finding the same keyframe with Animation::track_find_key().
261
double time_offset = MAX(CMP_EPSILON * 2, current_time * CMP_EPSILON * 2);
262
double current_time_offset = current_time + (p_backward ? -time_offset : time_offset);
263
264
float nearest_key_time = p_backward ? 0 : anim->get_length();
265
int track_count = anim->get_track_count();
266
bool bezier_active = track_editor->is_bezier_editor_active();
267
268
Node *root = get_tree()->get_edited_scene_root();
269
EditorSelection *selection = EditorNode::get_singleton()->get_editor_selection();
270
271
Vector<int> selected_tracks;
272
for (int i = 0; i < track_count; ++i) {
273
if (selection->is_selected(root->get_node_or_null(anim->track_get_path(i)))) {
274
selected_tracks.push_back(i);
275
}
276
}
277
278
// Find the nearest keyframe in selection if the scene has selected nodes
279
// or the nearest keyframe in the entire animation otherwise.
280
if (selected_tracks.size() > 0) {
281
for (int track : selected_tracks) {
282
if (bezier_active && anim->track_get_type(track) != Animation::TYPE_BEZIER) {
283
continue;
284
}
285
int key = anim->track_find_key(track, current_time_offset, Animation::FIND_MODE_NEAREST, false, !p_backward);
286
if (key == -1) {
287
continue;
288
}
289
double key_time = anim->track_get_key_time(track, key);
290
if ((p_backward && key_time > nearest_key_time) || (!p_backward && key_time < nearest_key_time)) {
291
nearest_key_time = key_time;
292
}
293
}
294
} else {
295
for (int track = 0; track < track_count; ++track) {
296
if (bezier_active && anim->track_get_type(track) != Animation::TYPE_BEZIER) {
297
continue;
298
}
299
int key = anim->track_find_key(track, current_time_offset, Animation::FIND_MODE_NEAREST, false, !p_backward);
300
if (key == -1) {
301
continue;
302
}
303
double key_time = anim->track_get_key_time(track, key);
304
if ((p_backward && key_time > nearest_key_time) || (!p_backward && key_time < nearest_key_time)) {
305
nearest_key_time = key_time;
306
}
307
}
308
}
309
310
player->seek_internal(nearest_key_time, true, true, true);
311
frame->set_value(nearest_key_time);
312
track_editor->set_anim_pos(nearest_key_time);
313
}
314
315
void AnimationPlayerEditor::_play_pressed() {
316
String current = _get_current();
317
318
if (!current.is_empty()) {
319
if (current == player->get_assigned_animation()) {
320
player->stop(); //so it won't blend with itself
321
}
322
ERR_FAIL_COND_EDMSG(!_validate_tracks(player->get_animation(current)), "Animation tracks may have any invalid key, abort playing.");
323
PackedStringArray markers = track_editor->get_selected_section();
324
if (markers.size() == 2) {
325
StringName start_marker = markers[0];
326
StringName end_marker = markers[1];
327
player->play_section_with_markers(current, start_marker, end_marker);
328
} else {
329
player->play(current);
330
}
331
}
332
333
//unstop
334
stop->set_button_icon(pause_icon);
335
}
336
337
void AnimationPlayerEditor::_play_from_pressed() {
338
String current = _get_current();
339
340
if (!current.is_empty()) {
341
double time = player->get_current_animation_position();
342
if (current == player->get_assigned_animation() && player->is_playing()) {
343
player->clear_caches(); //so it won't blend with itself
344
}
345
ERR_FAIL_COND_EDMSG(!_validate_tracks(player->get_animation(current)), "Animation tracks may have any invalid key, abort playing.");
346
player->seek_internal(time, true, true, true);
347
PackedStringArray markers = track_editor->get_selected_section();
348
if (markers.size() == 2) {
349
StringName start_marker = markers[0];
350
StringName end_marker = markers[1];
351
player->play_section_with_markers(current, start_marker, end_marker);
352
} else {
353
player->play(current);
354
}
355
}
356
357
//unstop
358
stop->set_button_icon(pause_icon);
359
}
360
361
String AnimationPlayerEditor::_get_current() const {
362
String current;
363
if (animation->get_selected() >= 0 && animation->get_selected() < animation->get_item_count() && !animation->is_item_separator(animation->get_selected())) {
364
current = animation->get_item_text(animation->get_selected());
365
}
366
return current;
367
}
368
void AnimationPlayerEditor::_play_bw_pressed() {
369
String current = _get_current();
370
if (!current.is_empty()) {
371
if (current == player->get_assigned_animation()) {
372
player->stop(); //so it won't blend with itself
373
}
374
ERR_FAIL_COND_EDMSG(!_validate_tracks(player->get_animation(current)), "Animation tracks may have any invalid key, abort playing.");
375
PackedStringArray markers = track_editor->get_selected_section();
376
if (markers.size() == 2) {
377
StringName start_marker = markers[0];
378
StringName end_marker = markers[1];
379
player->play_section_with_markers_backwards(current, start_marker, end_marker);
380
} else {
381
player->play_backwards(current);
382
}
383
}
384
385
//unstop
386
stop->set_button_icon(pause_icon);
387
}
388
389
void AnimationPlayerEditor::_play_bw_from_pressed() {
390
String current = _get_current();
391
392
if (!current.is_empty()) {
393
double time = player->get_current_animation_position();
394
if (current == player->get_assigned_animation() && player->is_playing()) {
395
player->clear_caches(); //so it won't blend with itself
396
}
397
ERR_FAIL_COND_EDMSG(!_validate_tracks(player->get_animation(current)), "Animation tracks may have any invalid key, abort playing.");
398
player->seek_internal(time, true, true, true);
399
PackedStringArray markers = track_editor->get_selected_section();
400
if (markers.size() == 2) {
401
StringName start_marker = markers[0];
402
StringName end_marker = markers[1];
403
player->play_section_with_markers_backwards(current, start_marker, end_marker);
404
} else {
405
player->play_backwards(current);
406
}
407
}
408
409
//unstop
410
stop->set_button_icon(pause_icon);
411
}
412
413
void AnimationPlayerEditor::_stop_pressed() {
414
if (!player) {
415
return;
416
}
417
418
if (player->is_playing()) {
419
player->pause();
420
} else {
421
String current = _get_current();
422
player->stop();
423
player->set_assigned_animation(current);
424
frame->set_value(0);
425
track_editor->set_anim_pos(0);
426
}
427
stop->set_button_icon(stop_icon);
428
}
429
430
void AnimationPlayerEditor::_animation_selected(int p_which) {
431
if (updating) {
432
return;
433
}
434
435
#define ITEM_CHECK_DISABLED(m_item) tool_anim->get_popup()->set_item_disabled(tool_anim->get_popup()->get_item_index(m_item), true)
436
ITEM_CHECK_DISABLED(TOOL_RENAME_ANIM);
437
ITEM_CHECK_DISABLED(TOOL_DUPLICATE_ANIM);
438
ITEM_CHECK_DISABLED(TOOL_REMOVE_ANIM);
439
440
ITEM_CHECK_DISABLED(TOOL_EDIT_TRANSITIONS);
441
ITEM_CHECK_DISABLED(TOOL_EDIT_RESOURCE);
442
#undef ITEM_CHECK_DISABLED
443
444
// when selecting an animation, the idea is that the only interesting behavior
445
// ui-wise is that it should play/blend the next one if currently playing
446
String current = _get_current();
447
448
if (!current.is_empty()) {
449
player->set_assigned_animation(current);
450
451
Ref<Animation> anim = player->get_animation(current);
452
ERR_FAIL_COND(anim.is_null());
453
{
454
bool animation_is_readonly = EditorNode::get_singleton()->is_resource_read_only(anim);
455
456
track_editor->set_animation(anim, animation_is_readonly);
457
Node *root = player->get_node_or_null(player->get_root_node());
458
459
// Player shouldn't access parent if it's the scene root.
460
if (!root || (player == get_tree()->get_edited_scene_root() && player->get_root_node() == SceneStringName(path_pp))) {
461
NodePath cached_root_path = player->get_path_to(get_cached_root_node());
462
if (player->get_node_or_null(cached_root_path) != nullptr) {
463
player->set_root_node(cached_root_path);
464
} else {
465
player->set_root_node(SceneStringName(path_pp)); // No other choice, preventing crash.
466
}
467
} else {
468
cached_root_node_id = root->get_instance_id(); // Caching as `track_editor` can lose track of player's root node.
469
track_editor->set_root(root);
470
}
471
}
472
frame->set_max((double)anim->get_length());
473
autoplay->set_pressed(current == player->get_autoplay());
474
player->stop();
475
} else {
476
track_editor->set_animation(Ref<Animation>(), true);
477
track_editor->set_root(nullptr);
478
autoplay->set_pressed(false);
479
}
480
481
AnimationPlayerEditor::get_singleton()->get_track_editor()->update_keying();
482
_animation_key_editor_seek(timeline_position);
483
484
emit_signal("animation_selected", current);
485
}
486
487
void AnimationPlayerEditor::_animation_new() {
488
int count = 1;
489
String base = "new_animation";
490
String current_library_name = "";
491
if (animation->has_selectable_items()) {
492
String current_animation_name = animation->get_item_text(animation->get_selected());
493
Ref<Animation> current_animation = player->get_animation(current_animation_name);
494
if (current_animation.is_valid()) {
495
current_library_name = player->find_animation_library(current_animation);
496
}
497
}
498
String attempt_prefix = (current_library_name == "") ? "" : current_library_name + "/";
499
while (true) {
500
String attempt = base;
501
if (count > 1) {
502
attempt += vformat("_%d", count);
503
}
504
if (player->has_animation(attempt_prefix + attempt)) {
505
count++;
506
continue;
507
}
508
base = attempt;
509
break;
510
}
511
512
_update_name_dialog_library_dropdown();
513
514
name_dialog_op = TOOL_NEW_ANIM;
515
name_dialog->set_title(TTR("Create New Animation"));
516
name_dialog->popup_centered(Size2(300, 90));
517
name_title->set_text(TTR("New Animation Name:"));
518
name->set_text(base);
519
name->select_all();
520
name->grab_focus();
521
}
522
523
void AnimationPlayerEditor::_animation_rename() {
524
if (!animation->has_selectable_items()) {
525
return;
526
}
527
int selected = animation->get_selected();
528
String selected_name = animation->get_item_text(selected);
529
530
// Remove library prefix if present.
531
if (selected_name.contains_char('/')) {
532
selected_name = selected_name.get_slicec('/', 1);
533
}
534
535
name_dialog->set_title(TTR("Rename Animation"));
536
name_title->set_text(TTR("Change Animation Name:"));
537
name->set_text(selected_name);
538
name_dialog_op = TOOL_RENAME_ANIM;
539
name_dialog->popup_centered(Size2(300, 90));
540
name->select_all();
541
name->grab_focus();
542
library->hide();
543
}
544
545
void AnimationPlayerEditor::_animation_remove() {
546
if (!animation->has_selectable_items()) {
547
return;
548
}
549
550
String current = animation->get_item_text(animation->get_selected());
551
552
delete_dialog->set_text(vformat(TTR("Delete Animation '%s'?"), current));
553
delete_dialog->popup_centered();
554
}
555
556
void AnimationPlayerEditor::_animation_remove_confirmed() {
557
String current = animation->get_item_text(animation->get_selected());
558
Ref<Animation> anim = player->get_animation(current);
559
560
Ref<AnimationLibrary> al = player->get_animation_library(player->find_animation_library(anim));
561
ERR_FAIL_COND(al.is_null());
562
563
// For names of form lib_name/anim_name, remove library name prefix.
564
if (current.contains_char('/')) {
565
current = current.get_slicec('/', 1);
566
}
567
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
568
undo_redo->create_action(TTR("Remove Animation"));
569
if (player->get_autoplay() == current) {
570
undo_redo->add_do_method(player, "set_autoplay", StringName());
571
undo_redo->add_undo_method(player, "set_autoplay", StringName(current));
572
// Avoid having the autoplay icon linger around if there is only one animation in the player.
573
undo_redo->add_do_method(this, "_animation_player_changed", player);
574
}
575
undo_redo->add_do_method(al.ptr(), "remove_animation", current);
576
undo_redo->add_undo_method(al.ptr(), "add_animation", current, anim);
577
undo_redo->add_do_method(this, "_animation_player_changed", player);
578
undo_redo->add_undo_method(this, "_animation_player_changed", player);
579
if (animation->has_selectable_items() && animation->get_selectable_item(false) == animation->get_selectable_item(true)) { // Last item remaining.
580
undo_redo->add_do_method(this, "_stop_onion_skinning");
581
undo_redo->add_undo_method(this, "_start_onion_skinning");
582
}
583
undo_redo->commit_action();
584
}
585
586
void AnimationPlayerEditor::_select_anim_by_name(const String &p_anim) {
587
int idx = -1;
588
for (int i = 0; i < animation->get_item_count(); i++) {
589
if (animation->get_item_text(i) == p_anim) {
590
idx = i;
591
break;
592
}
593
}
594
595
ERR_FAIL_COND(idx == -1);
596
597
animation->select(idx);
598
599
_animation_selected(idx);
600
}
601
602
float AnimationPlayerEditor::_get_editor_step() const {
603
const StringName current = player->get_assigned_animation();
604
const Ref<Animation> anim = player->get_animation(current);
605
ERR_FAIL_COND_V(anim.is_null(), 0.0);
606
607
float step = track_editor->get_snap_unit();
608
609
// Use more precise snapping when holding Shift
610
return Input::get_singleton()->is_key_pressed(Key::SHIFT) ? step * 0.25 : step;
611
}
612
613
void AnimationPlayerEditor::_animation_name_edited() {
614
if (player->is_playing()) {
615
player->stop();
616
}
617
618
String new_name = name->get_text();
619
if (!AnimationLibrary::is_valid_animation_name(new_name)) {
620
error_dialog->set_text(TTR("Invalid animation name!"));
621
error_dialog->popup_centered();
622
return;
623
}
624
625
if (name_dialog_op == TOOL_RENAME_ANIM && animation->has_selectable_items() && animation->get_item_text(animation->get_selected()) == new_name) {
626
name_dialog->hide();
627
return;
628
}
629
630
String test_name_prefix = "";
631
if (library->is_visible() && library->get_selected_id() != -1) {
632
test_name_prefix = library->get_item_metadata(library->get_selected_id());
633
test_name_prefix += (test_name_prefix != "") ? "/" : "";
634
}
635
636
if (player->has_animation(test_name_prefix + new_name)) {
637
error_dialog->set_text(vformat(TTR("Animation '%s' already exists!"), test_name_prefix + new_name));
638
error_dialog->popup_centered();
639
return;
640
}
641
642
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
643
switch (name_dialog_op) {
644
case TOOL_RENAME_ANIM: {
645
String current = animation->get_item_text(animation->get_selected());
646
Ref<Animation> anim = player->get_animation(current);
647
648
Ref<AnimationLibrary> al = player->get_animation_library(player->find_animation_library(anim));
649
ERR_FAIL_COND(al.is_null());
650
651
// Extract library prefix if present.
652
String new_library_prefix = "";
653
if (current.contains_char('/')) {
654
new_library_prefix = current.get_slicec('/', 0) + "/";
655
current = current.get_slicec('/', 1);
656
}
657
658
undo_redo->create_action(TTR("Rename Animation"));
659
undo_redo->add_do_method(al.ptr(), "rename_animation", current, new_name);
660
undo_redo->add_do_method(anim.ptr(), "set_name", new_name);
661
undo_redo->add_undo_method(al.ptr(), "rename_animation", new_name, current);
662
undo_redo->add_undo_method(anim.ptr(), "set_name", current);
663
undo_redo->add_do_method(this, "_animation_player_changed", player);
664
undo_redo->add_undo_method(this, "_animation_player_changed", player);
665
undo_redo->commit_action();
666
667
if (is_dummy) {
668
plugin->_update_dummy_player(original_node);
669
}
670
_select_anim_by_name(new_library_prefix + new_name);
671
} break;
672
673
case TOOL_NEW_ANIM: {
674
Ref<Animation> new_anim = Ref<Animation>(memnew(Animation));
675
new_anim->set_name(new_name);
676
677
if (animation->get_item_count() > 0) {
678
String current = animation->get_item_text(animation->get_selected());
679
Ref<Animation> current_anim = player->get_animation(current);
680
681
if (current_anim.is_valid()) {
682
new_anim->set_step(current_anim->get_step());
683
}
684
} else {
685
new_anim->set_step(EDITOR_GET("editors/animation/default_animation_step"));
686
}
687
688
String library_name;
689
Ref<AnimationLibrary> al;
690
library_name = library->get_item_metadata(library->get_selected());
691
// It's possible that [Global] was selected, but doesn't exist yet.
692
if (player->has_animation_library(library_name)) {
693
al = player->get_animation_library(library_name);
694
}
695
696
undo_redo->create_action(TTR("Add Animation"));
697
698
bool lib_added = false;
699
if (al.is_null()) {
700
al.instantiate();
701
lib_added = true;
702
undo_redo->add_do_method(fetch_mixer_for_library(), "add_animation_library", "", al);
703
library_name = "";
704
}
705
706
undo_redo->add_do_method(al.ptr(), "add_animation", new_name, new_anim);
707
undo_redo->add_undo_method(al.ptr(), "remove_animation", new_name);
708
undo_redo->add_do_method(this, "_animation_player_changed", player);
709
undo_redo->add_undo_method(this, "_animation_player_changed", player);
710
if (!animation->has_selectable_items()) {
711
undo_redo->add_do_method(this, "_start_onion_skinning");
712
undo_redo->add_undo_method(this, "_stop_onion_skinning");
713
}
714
if (lib_added) {
715
undo_redo->add_undo_method(fetch_mixer_for_library(), "remove_animation_library", "");
716
}
717
undo_redo->commit_action();
718
719
if (library_name != "") {
720
library_name = library_name + "/";
721
}
722
723
if (is_dummy) {
724
plugin->_update_dummy_player(original_node);
725
}
726
_select_anim_by_name(library_name + new_name);
727
728
} break;
729
730
case TOOL_DUPLICATE_ANIM: {
731
String current = animation->get_item_text(animation->get_selected());
732
Ref<Animation> anim = player->get_animation(current);
733
734
Ref<Animation> new_anim = _animation_clone(anim);
735
new_anim->set_name(new_name);
736
737
String library_name;
738
Ref<AnimationLibrary> al;
739
if (library->is_visible()) {
740
library_name = library->get_item_metadata(library->get_selected());
741
// It's possible that [Global] was selected, but doesn't exist yet.
742
if (player->has_animation_library(library_name)) {
743
al = player->get_animation_library(library_name);
744
}
745
} else {
746
if (player->has_animation_library("")) {
747
al = player->get_animation_library("");
748
library_name = "";
749
}
750
}
751
752
undo_redo->create_action(TTR("Duplicate Animation"));
753
754
bool lib_added = false;
755
if (al.is_null()) {
756
al.instantiate();
757
lib_added = true;
758
undo_redo->add_do_method(player, "add_animation_library", "", al);
759
library_name = "";
760
}
761
762
undo_redo->add_do_method(al.ptr(), "add_animation", new_name, new_anim);
763
undo_redo->add_undo_method(al.ptr(), "remove_animation", new_name);
764
undo_redo->add_do_method(this, "_animation_player_changed", player);
765
undo_redo->add_undo_method(this, "_animation_player_changed", player);
766
if (lib_added) {
767
undo_redo->add_undo_method(player, "remove_animation_library", "");
768
}
769
undo_redo->commit_action();
770
771
if (library_name != "") {
772
library_name = library_name + "/";
773
}
774
775
if (is_dummy) {
776
plugin->_update_dummy_player(original_node);
777
}
778
_select_anim_by_name(library_name + new_name);
779
} break;
780
}
781
782
name_dialog->hide();
783
}
784
785
void AnimationPlayerEditor::_blend_editor_next_changed(const int p_idx) {
786
if (!animation->has_selectable_items()) {
787
return;
788
}
789
790
String current = animation->get_item_text(animation->get_selected());
791
792
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
793
undo_redo->create_action(TTR("Blend Next Changed"));
794
undo_redo->add_do_method(player, "animation_set_next", current, blend_editor.next->get_item_text(p_idx));
795
undo_redo->add_undo_method(player, "animation_set_next", current, player->animation_get_next(current));
796
undo_redo->add_do_method(this, "_animation_player_changed", player);
797
undo_redo->add_undo_method(this, "_animation_player_changed", player);
798
undo_redo->commit_action();
799
}
800
801
void AnimationPlayerEditor::_edit_animation_blend() {
802
if (updating_blends || !animation->has_selectable_items()) {
803
return;
804
}
805
806
blend_editor.dialog->popup_centered(Size2(400, 400) * EDSCALE);
807
_update_animation_blend();
808
}
809
810
void AnimationPlayerEditor::_update_animation_blend() {
811
if (updating_blends || !animation->has_selectable_items()) {
812
return;
813
}
814
815
blend_editor.tree->clear();
816
817
String current = animation->get_item_text(animation->get_selected());
818
819
List<StringName> anims;
820
player->get_animation_list(&anims);
821
TreeItem *root = blend_editor.tree->create_item();
822
updating_blends = true;
823
824
int i = 0;
825
bool anim_found = false;
826
blend_editor.next->clear();
827
blend_editor.next->add_item("", i);
828
829
for (const StringName &to : anims) {
830
TreeItem *blend = blend_editor.tree->create_item(root);
831
blend->set_editable(0, false);
832
blend->set_editable(1, true);
833
blend->set_text(0, to);
834
blend->set_cell_mode(1, TreeItem::CELL_MODE_RANGE);
835
blend->set_range_config(1, 0, 3600, 0.001);
836
blend->set_range(1, player->get_blend_time(current, to));
837
838
i++;
839
blend_editor.next->add_item(to, i);
840
if (to == player->animation_get_next(current)) {
841
blend_editor.next->select(i);
842
anim_found = true;
843
}
844
}
845
846
// make sure we reset it else it becomes out of sync and could contain a deleted animation
847
if (!anim_found) {
848
blend_editor.next->select(0);
849
player->animation_set_next(current, blend_editor.next->get_item_text(0));
850
}
851
852
updating_blends = false;
853
}
854
855
void AnimationPlayerEditor::_blend_edited() {
856
if (updating_blends || !animation->has_selectable_items()) {
857
return;
858
}
859
860
TreeItem *selected = blend_editor.tree->get_edited();
861
if (!selected) {
862
return;
863
}
864
865
String current = animation->get_item_text(animation->get_selected());
866
867
updating_blends = true;
868
String to = selected->get_text(0);
869
float blend_time = selected->get_range(1);
870
float prev_blend_time = player->get_blend_time(current, to);
871
872
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
873
undo_redo->create_action(TTR("Change Blend Time"));
874
undo_redo->add_do_method(player, "set_blend_time", current, to, blend_time);
875
undo_redo->add_undo_method(player, "set_blend_time", current, to, prev_blend_time);
876
undo_redo->add_do_method(this, "_animation_player_changed", player);
877
undo_redo->add_undo_method(this, "_animation_player_changed", player);
878
undo_redo->commit_action();
879
updating_blends = false;
880
}
881
882
void AnimationPlayerEditor::ensure_visibility() {
883
if (player) {
884
return; // another player is pinned, don't reset
885
}
886
887
_animation_edit();
888
}
889
890
Dictionary AnimationPlayerEditor::get_state() const {
891
Dictionary d;
892
893
if (!is_dummy) {
894
d["visible"] = is_visible_in_tree();
895
if (EditorNode::get_singleton()->get_edited_scene() && is_visible_in_tree() && player) {
896
d["player"] = EditorNode::get_singleton()->get_edited_scene()->get_path_to(player);
897
d["animation"] = player->get_assigned_animation();
898
d["track_editor_state"] = track_editor->get_state();
899
}
900
}
901
902
return d;
903
}
904
905
void AnimationPlayerEditor::set_state(const Dictionary &p_state) {
906
if (!p_state.has("visible") || !p_state["visible"]) {
907
return;
908
}
909
if (!EditorNode::get_singleton()->get_edited_scene()) {
910
return;
911
}
912
913
if (p_state.has("player")) {
914
Node *n = EditorNode::get_singleton()->get_edited_scene()->get_node(p_state["player"]);
915
if (Object::cast_to<AnimationPlayer>(n) && EditorNode::get_singleton()->get_editor_selection()->is_selected(n)) {
916
if (player) {
917
if (player->is_connected(SNAME("animation_list_changed"), callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated))) {
918
player->disconnect(SNAME("animation_list_changed"), callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated));
919
}
920
if (player->is_connected(SNAME("current_animation_changed"), callable_mp(this, &AnimationPlayerEditor::_current_animation_changed))) {
921
player->disconnect(SNAME("current_animation_changed"), callable_mp(this, &AnimationPlayerEditor::_current_animation_changed));
922
}
923
}
924
player = Object::cast_to<AnimationPlayer>(n);
925
if (player) {
926
if (!player->is_connected(SNAME("animation_list_changed"), callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated))) {
927
player->connect(SNAME("animation_list_changed"), callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated), CONNECT_DEFERRED);
928
}
929
if (!player->is_connected(SNAME("current_animation_changed"), callable_mp(this, &AnimationPlayerEditor::_current_animation_changed))) {
930
player->connect(SNAME("current_animation_changed"), callable_mp(this, &AnimationPlayerEditor::_current_animation_changed));
931
}
932
}
933
934
_update_player();
935
make_visible();
936
set_process(true);
937
ensure_visibility();
938
939
if (p_state.has("animation")) {
940
String anim = p_state["animation"];
941
if (!anim.is_empty() && player->has_animation(anim)) {
942
_select_anim_by_name(anim);
943
_animation_edit();
944
}
945
}
946
}
947
}
948
949
if (p_state.has("track_editor_state")) {
950
track_editor->set_state(p_state["track_editor_state"]);
951
}
952
}
953
954
void AnimationPlayerEditor::clear() {
955
track_editor->clear();
956
}
957
958
void AnimationPlayerEditor::_animation_resource_edit() {
959
String current = _get_current();
960
if (current != String()) {
961
Ref<Animation> anim = player->get_animation(current);
962
EditorNode::get_singleton()->edit_resource(anim);
963
}
964
}
965
966
void AnimationPlayerEditor::_animation_edit() {
967
String current = _get_current();
968
if (current != String()) {
969
Ref<Animation> anim = player->get_animation(current);
970
971
track_editor->set_animation(anim, EditorNode::get_singleton()->is_resource_read_only(anim));
972
973
Node *root = player->get_node_or_null(player->get_root_node());
974
if (root) {
975
track_editor->set_root(root);
976
}
977
} else {
978
track_editor->set_animation(Ref<Animation>(), true);
979
track_editor->set_root(nullptr);
980
}
981
}
982
983
void AnimationPlayerEditor::_scale_changed(const String &p_scale) {
984
player->set_speed_scale(p_scale.to_float());
985
}
986
987
void AnimationPlayerEditor::_update_animation() {
988
// the purpose of _update_animation is to reflect the current state
989
// of the animation player in the current editor..
990
991
updating = true;
992
993
if (player->is_playing()) {
994
stop->set_button_icon(pause_icon);
995
} else {
996
stop->set_button_icon(stop_icon);
997
}
998
999
scale->set_text(String::num(player->get_speed_scale(), 2));
1000
String current = player->get_assigned_animation();
1001
1002
for (int i = 0; i < animation->get_item_count(); i++) {
1003
if (animation->get_item_text(i) == current) {
1004
animation->select(i);
1005
break;
1006
}
1007
}
1008
1009
updating = false;
1010
}
1011
1012
void AnimationPlayerEditor::_update_player() {
1013
updating = true;
1014
1015
animation->clear();
1016
1017
tool_anim->set_disabled(player == nullptr);
1018
pin->set_disabled(player == nullptr);
1019
_set_controls_disabled(player == nullptr);
1020
1021
if (!player) {
1022
AnimationPlayerEditor::get_singleton()->get_track_editor()->update_keying();
1023
return;
1024
}
1025
1026
List<StringName> libraries;
1027
player->get_animation_library_list(&libraries);
1028
1029
int active_idx = -1;
1030
bool no_anims_found = true;
1031
bool global_animation_library_is_readonly = false;
1032
bool all_animation_libraries_are_readonly = libraries.size() > 0;
1033
1034
for (const StringName &K : libraries) {
1035
if (K != StringName()) {
1036
animation->add_separator(K);
1037
}
1038
1039
// Check if the global library is read-only since we want to disable options for adding/remove/renaming animations if it is.
1040
Ref<AnimationLibrary> anim_library = player->get_animation_library(K);
1041
bool is_animation_library_read_only = EditorNode::get_singleton()->is_resource_read_only(anim_library);
1042
if (!is_animation_library_read_only) {
1043
all_animation_libraries_are_readonly = false;
1044
} else {
1045
if (K == "") {
1046
global_animation_library_is_readonly = true;
1047
}
1048
}
1049
1050
List<StringName> animlist;
1051
anim_library->get_animation_list(&animlist);
1052
1053
for (const StringName &E : animlist) {
1054
String path = K;
1055
if (path != "") {
1056
path += "/";
1057
}
1058
path += E;
1059
animation->add_item(path);
1060
if (player->get_assigned_animation() == path) {
1061
active_idx = animation->get_selectable_item(true);
1062
}
1063
no_anims_found = false;
1064
}
1065
}
1066
#define ITEM_CHECK_DISABLED(m_item) tool_anim->get_popup()->set_item_disabled(tool_anim->get_popup()->get_item_index(m_item), all_animation_libraries_are_readonly || (no_anims_found && global_animation_library_is_readonly))
1067
ITEM_CHECK_DISABLED(TOOL_NEW_ANIM);
1068
#undef ITEM_CHECK_DISABLED
1069
1070
_update_animation_list_icons();
1071
1072
updating = false;
1073
if (active_idx != -1) {
1074
animation->select(active_idx);
1075
autoplay->set_pressed(animation->get_item_text(active_idx) == player->get_autoplay());
1076
_animation_selected(active_idx);
1077
} else if (animation->has_selectable_items()) {
1078
int item = animation->get_selectable_item();
1079
animation->select(item);
1080
autoplay->set_pressed(animation->get_item_text(item) == player->get_autoplay());
1081
_animation_selected(item);
1082
} else {
1083
_animation_selected(0);
1084
}
1085
1086
if (no_anims_found) {
1087
_set_controls_disabled(true);
1088
} else {
1089
String current = animation->get_item_text(animation->get_selected());
1090
Ref<Animation> anim = player->get_animation(current);
1091
1092
bool animation_library_is_readonly = EditorNode::get_singleton()->is_resource_read_only(anim);
1093
1094
track_editor->set_animation(anim, animation_library_is_readonly);
1095
Node *root = player->get_node_or_null(player->get_root_node());
1096
if (root) {
1097
track_editor->set_root(root);
1098
}
1099
}
1100
1101
_update_animation();
1102
}
1103
1104
void AnimationPlayerEditor::_set_controls_disabled(bool p_disabled) {
1105
frame->set_editable(!p_disabled);
1106
1107
stop->set_disabled(p_disabled);
1108
play->set_disabled(p_disabled);
1109
play_bw->set_disabled(p_disabled);
1110
play_bw_from->set_disabled(p_disabled);
1111
play_from->set_disabled(p_disabled);
1112
animation->set_disabled(p_disabled);
1113
autoplay->set_disabled(p_disabled);
1114
onion_toggle->set_disabled(p_disabled);
1115
onion_skinning->set_disabled(p_disabled);
1116
}
1117
1118
void AnimationPlayerEditor::_update_animation_list_icons() {
1119
for (int i = 0; i < animation->get_item_count(); i++) {
1120
String anim_name = animation->get_item_text(i);
1121
if (animation->is_item_disabled(i) || animation->is_item_separator(i)) {
1122
continue;
1123
}
1124
1125
Ref<Texture2D> icon;
1126
if (anim_name == player->get_autoplay()) {
1127
if (anim_name == SceneStringName(RESET)) {
1128
icon = autoplay_reset_icon;
1129
} else {
1130
icon = autoplay_icon;
1131
}
1132
} else if (anim_name == SceneStringName(RESET)) {
1133
icon = reset_icon;
1134
}
1135
1136
animation->set_item_icon(i, icon);
1137
}
1138
}
1139
1140
void AnimationPlayerEditor::_update_name_dialog_library_dropdown() {
1141
StringName current_library_name;
1142
if (animation->has_selectable_items()) {
1143
String current_animation_name = animation->get_item_text(animation->get_selected());
1144
Ref<Animation> current_animation = player->get_animation(current_animation_name);
1145
if (current_animation.is_valid()) {
1146
current_library_name = player->find_animation_library(current_animation);
1147
}
1148
}
1149
1150
List<StringName> libraries;
1151
player->get_animation_library_list(&libraries);
1152
library->clear();
1153
1154
int valid_library_count = 0;
1155
1156
// When [Global] isn't present, but other libraries are, add option of creating [Global].
1157
int index_offset = 0;
1158
if (!player->has_animation_library(StringName())) {
1159
library->add_item(String(TTR("[Global] (create)")));
1160
library->set_item_metadata(0, "");
1161
if (!libraries.is_empty()) {
1162
index_offset = 1;
1163
}
1164
valid_library_count++;
1165
}
1166
1167
int current_lib_id = index_offset; // Don't default to [Global] if it doesn't exist yet.
1168
for (const StringName &library_name : libraries) {
1169
if (!EditorNode::get_singleton()->is_resource_read_only(player->get_animation_library(library_name))) {
1170
library->add_item((library_name == StringName()) ? String(TTR("[Global]")) : String(library_name));
1171
library->set_item_metadata(valid_library_count, String(library_name));
1172
// Default to duplicating into same library.
1173
if (library_name == current_library_name) {
1174
current_library_name = library_name;
1175
current_lib_id = valid_library_count;
1176
}
1177
valid_library_count++;
1178
}
1179
}
1180
1181
// If our library name is empty, but we have valid libraries, we can check here to auto assign the first
1182
// one which isn't a read-only library.
1183
bool auto_assigning_non_global_library = false;
1184
if (current_library_name == StringName() && valid_library_count > 0) {
1185
for (const StringName &library_name : libraries) {
1186
if (!EditorNode::get_singleton()->is_resource_read_only(player->get_animation_library(library_name))) {
1187
current_library_name = library_name;
1188
current_lib_id = 0;
1189
if (library_name != StringName()) {
1190
auto_assigning_non_global_library = true;
1191
}
1192
break;
1193
}
1194
}
1195
}
1196
1197
if (library->get_item_count() > 0) {
1198
library->select(current_lib_id);
1199
if (library->get_item_count() > 1 || auto_assigning_non_global_library) {
1200
library->show();
1201
library->set_disabled(auto_assigning_non_global_library && library->get_item_count() == 1);
1202
} else {
1203
library->hide();
1204
}
1205
}
1206
}
1207
1208
void AnimationPlayerEditor::_update_playback_tooltips() {
1209
stop->set_tooltip_text(TTR("Pause/Stop Animation") + " (" + ED_GET_SHORTCUT("animation_editor/stop_animation")->get_as_text() + ")");
1210
play->set_tooltip_text(TTR("Play Animation from Start") + " (" + ED_GET_SHORTCUT("animation_editor/play_animation_from_start")->get_as_text() + ")");
1211
play_from->set_tooltip_text(TTR("Play Animation") + " (" + ED_GET_SHORTCUT("animation_editor/play_animation")->get_as_text() + ")");
1212
play_bw_from->set_tooltip_text(TTR("Play Animation Backwards") + " (" + ED_GET_SHORTCUT("animation_editor/play_animation_backwards")->get_as_text() + ")");
1213
play_bw->set_tooltip_text(TTR("Play Animation Backwards from End") + " (" + ED_GET_SHORTCUT("animation_editor/play_animation_from_end")->get_as_text() + ")");
1214
}
1215
1216
void AnimationPlayerEditor::_ensure_dummy_player() {
1217
bool dummy_exists = is_dummy && player && original_node;
1218
if (dummy_exists) {
1219
if (is_visible()) {
1220
player->set_active(true);
1221
original_node->set_editing(true);
1222
} else {
1223
player->set_active(false);
1224
original_node->set_editing(false);
1225
}
1226
}
1227
1228
int selected = animation->get_selected();
1229
autoplay->set_disabled(selected != -1 ? (animation->get_item_text(selected).is_empty() ? true : dummy_exists) : true);
1230
1231
// Show warning.
1232
if (track_editor) {
1233
track_editor->show_dummy_player_warning(dummy_exists);
1234
}
1235
}
1236
1237
void AnimationPlayerEditor::edit(AnimationMixer *p_node, AnimationPlayer *p_player, bool p_is_dummy) {
1238
if (player && pin->is_pressed()) {
1239
return; // Ignore, pinned.
1240
}
1241
1242
if (player) {
1243
if (player->is_connected(SNAME("animation_list_changed"), callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated))) {
1244
player->disconnect(SNAME("animation_list_changed"), callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated));
1245
}
1246
if (player->is_connected(SceneStringName(animation_finished), callable_mp(this, &AnimationPlayerEditor::_animation_finished))) {
1247
player->disconnect(SceneStringName(animation_finished), callable_mp(this, &AnimationPlayerEditor::_animation_finished));
1248
}
1249
if (player->is_connected(SNAME("current_animation_changed"), callable_mp(this, &AnimationPlayerEditor::_current_animation_changed))) {
1250
player->disconnect(SNAME("current_animation_changed"), callable_mp(this, &AnimationPlayerEditor::_current_animation_changed));
1251
}
1252
}
1253
1254
AnimationTree *tree = Object::cast_to<AnimationTree>(p_node);
1255
1256
if (tree) {
1257
if (tree->is_connected(SNAME("animation_player_changed"), callable_mp(this, &AnimationPlayerEditor::unpin))) {
1258
tree->disconnect(SNAME("animation_player_changed"), callable_mp(this, &AnimationPlayerEditor::unpin));
1259
}
1260
}
1261
1262
original_node = p_node;
1263
player = p_player;
1264
is_dummy = p_is_dummy;
1265
1266
if (tree) {
1267
if (!tree->is_connected(SNAME("animation_player_changed"), callable_mp(this, &AnimationPlayerEditor::unpin))) {
1268
tree->connect(SNAME("animation_player_changed"), callable_mp(this, &AnimationPlayerEditor::unpin));
1269
}
1270
}
1271
1272
if (player) {
1273
if (!player->is_connected(SNAME("animation_list_changed"), callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated))) {
1274
player->connect(SNAME("animation_list_changed"), callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated), CONNECT_DEFERRED);
1275
}
1276
if (!player->is_connected(SceneStringName(animation_finished), callable_mp(this, &AnimationPlayerEditor::_animation_finished))) {
1277
player->connect(SceneStringName(animation_finished), callable_mp(this, &AnimationPlayerEditor::_animation_finished));
1278
}
1279
if (!player->is_connected(SNAME("current_animation_changed"), callable_mp(this, &AnimationPlayerEditor::_current_animation_changed))) {
1280
player->connect(SNAME("current_animation_changed"), callable_mp(this, &AnimationPlayerEditor::_current_animation_changed));
1281
}
1282
_update_player();
1283
1284
if (onion.enabled) {
1285
if (animation->has_selectable_items()) {
1286
_start_onion_skinning();
1287
} else {
1288
_stop_onion_skinning();
1289
}
1290
}
1291
1292
track_editor->show_select_node_warning(false);
1293
} else {
1294
if (onion.enabled) {
1295
_stop_onion_skinning();
1296
}
1297
1298
track_editor->show_select_node_warning(true);
1299
}
1300
1301
library_editor->set_animation_mixer(fetch_mixer_for_library());
1302
1303
_ensure_dummy_player();
1304
}
1305
1306
void AnimationPlayerEditor::forward_force_draw_over_viewport(Control *p_overlay) {
1307
if (!onion.can_overlay) {
1308
return;
1309
}
1310
1311
// Can happen on viewport resize, at least.
1312
if (!_are_onion_layers_valid()) {
1313
return;
1314
}
1315
1316
RID ci = p_overlay->get_canvas_item();
1317
Rect2 src_rect = p_overlay->get_global_rect();
1318
// Re-flip since captures are already flipped.
1319
src_rect.position.y = onion.capture_size.y - (src_rect.position.y + src_rect.size.y);
1320
src_rect.size.y *= -1;
1321
1322
Rect2 dst_rect = Rect2(Point2(), p_overlay->get_size());
1323
1324
float alpha_step = 1.0 / (onion.steps + 1);
1325
1326
uint32_t capture_idx = 0;
1327
if (onion.past) {
1328
float alpha = 0.0f;
1329
do {
1330
alpha += alpha_step;
1331
1332
if (onion.captures_valid[capture_idx]) {
1333
RS::get_singleton()->canvas_item_add_texture_rect_region(
1334
ci, dst_rect, RS::get_singleton()->viewport_get_texture(onion.captures[capture_idx]), src_rect, Color(1, 1, 1, alpha));
1335
}
1336
1337
capture_idx++;
1338
} while (capture_idx < onion.steps);
1339
}
1340
if (onion.future) {
1341
float alpha = 1.0f;
1342
uint32_t base_cidx = capture_idx;
1343
do {
1344
alpha -= alpha_step;
1345
1346
if (onion.captures_valid[capture_idx]) {
1347
RS::get_singleton()->canvas_item_add_texture_rect_region(
1348
ci, dst_rect, RS::get_singleton()->viewport_get_texture(onion.captures[capture_idx]), src_rect, Color(1, 1, 1, alpha));
1349
}
1350
1351
capture_idx++;
1352
} while (capture_idx < base_cidx + onion.steps); // In case there's the present capture at the end, skip it.
1353
}
1354
}
1355
1356
void AnimationPlayerEditor::_animation_duplicate() {
1357
if (!animation->has_selectable_items()) {
1358
return;
1359
}
1360
1361
String current = animation->get_item_text(animation->get_selected());
1362
Ref<Animation> anim = player->get_animation(current);
1363
if (anim.is_null()) {
1364
return;
1365
}
1366
1367
int count = 2;
1368
String new_name = current;
1369
PackedStringArray split = new_name.split("_");
1370
int last_index = split.size() - 1;
1371
if (last_index > 0 && split[last_index].is_valid_int() && split[last_index].to_int() >= 0) {
1372
count = split[last_index].to_int();
1373
split.remove_at(last_index);
1374
new_name = String("_").join(split);
1375
}
1376
while (true) {
1377
String attempt = new_name;
1378
attempt += vformat("_%d", count);
1379
if (player->has_animation(attempt)) {
1380
count++;
1381
continue;
1382
}
1383
new_name = attempt;
1384
break;
1385
}
1386
1387
if (new_name.contains_char('/')) {
1388
// Discard library prefix.
1389
new_name = new_name.get_slicec('/', 1);
1390
}
1391
1392
_update_name_dialog_library_dropdown();
1393
1394
name_dialog_op = TOOL_DUPLICATE_ANIM;
1395
name_dialog->set_title(TTR("Duplicate Animation"));
1396
// TRANSLATORS: This is a label for the new name field in the "Duplicate Animation" dialog.
1397
name_title->set_text(TTR("Duplicated Animation Name:"));
1398
name->set_text(new_name);
1399
name_dialog->popup_centered(Size2(300, 90));
1400
name->select_all();
1401
name->grab_focus();
1402
}
1403
1404
Ref<Animation> AnimationPlayerEditor::_animation_clone(Ref<Animation> p_anim) {
1405
Ref<Animation> new_anim = memnew(Animation);
1406
List<PropertyInfo> plist;
1407
p_anim->get_property_list(&plist);
1408
1409
for (const PropertyInfo &E : plist) {
1410
if (E.usage & PROPERTY_USAGE_STORAGE) {
1411
new_anim->set(E.name, p_anim->get(E.name));
1412
}
1413
}
1414
new_anim->set_path("");
1415
1416
return new_anim;
1417
}
1418
1419
void AnimationPlayerEditor::_seek_value_changed(float p_value, bool p_timeline_only) {
1420
if (updating || !player || player->is_playing()) {
1421
return;
1422
};
1423
1424
updating = true;
1425
StringName current = player->get_assigned_animation();
1426
if (current.is_empty() || !player->has_animation(current)) {
1427
updating = false;
1428
current = "";
1429
return;
1430
};
1431
1432
Ref<Animation> anim;
1433
anim = player->get_animation(current);
1434
1435
double pos = CLAMP((double)anim->get_length() * (p_value / frame->get_max()), 0, (double)anim->get_length());
1436
if (track_editor->is_snap_timeline_enabled()) {
1437
pos = Math::snapped(pos, _get_editor_step());
1438
}
1439
pos = CLAMP(pos, 0, (double)anim->get_length() - CMP_EPSILON2); // Hack: Avoid fposmod with LOOP_LINEAR.
1440
1441
if (!p_timeline_only && anim.is_valid() && (!player->is_valid() || !Math::is_equal_approx(pos, player->get_current_animation_position()))) {
1442
player->seek_internal(pos, true, true, false);
1443
}
1444
1445
track_editor->set_anim_pos(pos);
1446
}
1447
1448
void AnimationPlayerEditor::_animation_player_changed(Object *p_pl) {
1449
_update_player();
1450
1451
if (blend_editor.dialog->is_visible()) {
1452
_update_animation_blend(); // Update.
1453
}
1454
1455
if (library_editor->is_visible()) {
1456
library_editor->update_tree();
1457
}
1458
}
1459
1460
void AnimationPlayerEditor::_animation_libraries_updated() {
1461
_animation_player_changed(player);
1462
}
1463
1464
void AnimationPlayerEditor::_list_changed() {
1465
if (is_visible_in_tree()) {
1466
_update_player();
1467
}
1468
}
1469
1470
void AnimationPlayerEditor::_animation_finished(const String &p_name) {
1471
finishing = true;
1472
}
1473
1474
void AnimationPlayerEditor::_current_animation_changed(const StringName &p_name) {
1475
if (is_visible_in_tree()) {
1476
if (finishing) {
1477
finishing = false; // Maybe redundant since it will be false in the AnimationPlayerEditor::_process(), but for safety.
1478
return;
1479
} else if (p_name.is_empty()) {
1480
// Means [stop].
1481
frame->set_value(0);
1482
track_editor->set_anim_pos(0);
1483
_update_animation();
1484
return;
1485
}
1486
Ref<Animation> anim = player->get_animation(p_name);
1487
if (anim.is_null()) {
1488
return;
1489
}
1490
1491
// Determine the read-only status of the animation's library and the libraries as a whole.
1492
List<StringName> libraries;
1493
player->get_animation_library_list(&libraries);
1494
1495
bool current_animation_library_is_readonly = false;
1496
bool all_animation_libraries_are_readonly = true;
1497
for (const StringName &K : libraries) {
1498
Ref<AnimationLibrary> anim_library = player->get_animation_library(K);
1499
bool animation_library_is_readonly = EditorNode::get_singleton()->is_resource_read_only(anim_library);
1500
if (!animation_library_is_readonly) {
1501
all_animation_libraries_are_readonly = false;
1502
}
1503
1504
List<StringName> animlist;
1505
anim_library->get_animation_list(&animlist);
1506
bool animation_found = false;
1507
for (const StringName &E : animlist) {
1508
String path = K;
1509
if (path != "") {
1510
path += "/";
1511
}
1512
path += E;
1513
if (p_name == path) {
1514
current_animation_library_is_readonly = animation_library_is_readonly;
1515
break;
1516
}
1517
}
1518
if (animation_found) {
1519
break;
1520
}
1521
}
1522
1523
StringName library_name = player->find_animation_library(anim);
1524
1525
bool animation_is_readonly = EditorNode::get_singleton()->is_resource_read_only(anim);
1526
1527
track_editor->set_animation(anim, animation_is_readonly);
1528
_update_animation();
1529
1530
#define ITEM_CHECK_DISABLED(m_item) tool_anim->get_popup()->set_item_disabled(tool_anim->get_popup()->get_item_index(m_item), false)
1531
ITEM_CHECK_DISABLED(TOOL_EDIT_TRANSITIONS);
1532
ITEM_CHECK_DISABLED(TOOL_EDIT_RESOURCE);
1533
#undef ITEM_CHECK_DISABLED
1534
1535
#define ITEM_CHECK_DISABLED(m_item) tool_anim->get_popup()->set_item_disabled(tool_anim->get_popup()->get_item_index(m_item), current_animation_library_is_readonly)
1536
ITEM_CHECK_DISABLED(TOOL_RENAME_ANIM);
1537
ITEM_CHECK_DISABLED(TOOL_REMOVE_ANIM);
1538
#undef ITEM_CHECK_DISABLED
1539
1540
#define ITEM_CHECK_DISABLED(m_item) tool_anim->get_popup()->set_item_disabled(tool_anim->get_popup()->get_item_index(m_item), all_animation_libraries_are_readonly)
1541
ITEM_CHECK_DISABLED(TOOL_DUPLICATE_ANIM);
1542
#undef ITEM_CHECK_DISABLED
1543
}
1544
}
1545
1546
void AnimationPlayerEditor::_animation_key_editor_anim_len_changed(float p_len) {
1547
frame->set_max(p_len);
1548
}
1549
void AnimationPlayerEditor::_animation_key_editor_seek(float p_pos, bool p_timeline_only, bool p_update_position_only) {
1550
timeline_position = p_pos;
1551
1552
if (!is_visible_in_tree() ||
1553
p_update_position_only ||
1554
!player ||
1555
player->is_playing() ||
1556
!player->has_animation(player->get_assigned_animation())) {
1557
return;
1558
}
1559
1560
updating = true;
1561
frame->set_value(track_editor->is_snap_timeline_enabled() ? Math::snapped(p_pos, _get_editor_step()) : p_pos);
1562
updating = false;
1563
_seek_value_changed(p_pos, p_timeline_only);
1564
}
1565
1566
void AnimationPlayerEditor::_animation_update_key_frame() {
1567
if (player) {
1568
player->advance(0);
1569
}
1570
}
1571
1572
void AnimationPlayerEditor::_animation_tool_menu(int p_option) {
1573
String current = _get_current();
1574
1575
Ref<Animation> anim;
1576
if (!current.is_empty()) {
1577
anim = player->get_animation(current);
1578
}
1579
1580
switch (p_option) {
1581
case TOOL_NEW_ANIM: {
1582
_animation_new();
1583
} break;
1584
case TOOL_ANIM_LIBRARY: {
1585
library_editor->set_animation_mixer(fetch_mixer_for_library());
1586
library_editor->show_dialog();
1587
} break;
1588
case TOOL_DUPLICATE_ANIM: {
1589
_animation_duplicate();
1590
} break;
1591
case TOOL_RENAME_ANIM: {
1592
_animation_rename();
1593
} break;
1594
case TOOL_EDIT_TRANSITIONS: {
1595
_edit_animation_blend();
1596
} break;
1597
case TOOL_REMOVE_ANIM: {
1598
_animation_remove();
1599
} break;
1600
case TOOL_EDIT_RESOURCE: {
1601
if (anim.is_valid()) {
1602
EditorNode::get_singleton()->edit_resource(anim);
1603
}
1604
} break;
1605
}
1606
}
1607
1608
void AnimationPlayerEditor::_onion_skinning_menu(int p_option) {
1609
PopupMenu *menu = onion_skinning->get_popup();
1610
int idx = menu->get_item_index(p_option);
1611
1612
switch (p_option) {
1613
case ONION_SKINNING_ENABLE: {
1614
onion.enabled = !onion.enabled;
1615
1616
if (onion.enabled) {
1617
if (get_player() && !get_player()->has_animation(SceneStringName(RESET))) {
1618
EditorNode::get_singleton()->show_warning(TTR("Onion skinning requires a RESET animation."));
1619
}
1620
_start_onion_skinning(); // It will check for RESET animation anyway.
1621
} else {
1622
_stop_onion_skinning();
1623
}
1624
1625
} break;
1626
case ONION_SKINNING_PAST: {
1627
// Ensure at least one of past/future is checked.
1628
onion.past = onion.future ? !onion.past : true;
1629
menu->set_item_checked(idx, onion.past);
1630
} break;
1631
case ONION_SKINNING_FUTURE: {
1632
// Ensure at least one of past/future is checked.
1633
onion.future = onion.past ? !onion.future : true;
1634
menu->set_item_checked(idx, onion.future);
1635
} break;
1636
case ONION_SKINNING_1_STEP: // Fall-through.
1637
case ONION_SKINNING_2_STEPS:
1638
case ONION_SKINNING_3_STEPS: {
1639
onion.steps = (p_option - ONION_SKINNING_1_STEP) + 1;
1640
int one_frame_idx = menu->get_item_index(ONION_SKINNING_1_STEP);
1641
for (int i = 0; i <= ONION_SKINNING_LAST_STEPS_OPTION - ONION_SKINNING_1_STEP; i++) {
1642
menu->set_item_checked(one_frame_idx + i, (int)onion.steps == i + 1);
1643
}
1644
} break;
1645
case ONION_SKINNING_DIFFERENCES_ONLY: {
1646
onion.differences_only = !onion.differences_only;
1647
menu->set_item_checked(idx, onion.differences_only);
1648
} break;
1649
case ONION_SKINNING_FORCE_WHITE_MODULATE: {
1650
onion.force_white_modulate = !onion.force_white_modulate;
1651
menu->set_item_checked(idx, onion.force_white_modulate);
1652
} break;
1653
case ONION_SKINNING_INCLUDE_GIZMOS: {
1654
onion.include_gizmos = !onion.include_gizmos;
1655
menu->set_item_checked(idx, onion.include_gizmos);
1656
} break;
1657
}
1658
}
1659
1660
void AnimationPlayerEditor::shortcut_input(const Ref<InputEvent> &p_ev) {
1661
ERR_FAIL_COND(p_ev.is_null());
1662
1663
Ref<InputEventKey> k = p_ev;
1664
if (is_visible_in_tree() && k.is_valid() && k->is_pressed() && !k->is_echo()) {
1665
if (ED_IS_SHORTCUT("animation_editor/stop_animation", p_ev)) {
1666
_stop_pressed();
1667
accept_event();
1668
} else if (ED_IS_SHORTCUT("animation_editor/play_animation", p_ev)) {
1669
_play_from_pressed();
1670
accept_event();
1671
} else if (ED_IS_SHORTCUT("animation_editor/play_animation_backwards", p_ev)) {
1672
_play_bw_from_pressed();
1673
accept_event();
1674
} else if (ED_IS_SHORTCUT("animation_editor/play_animation_from_start", p_ev)) {
1675
_play_pressed();
1676
accept_event();
1677
} else if (ED_IS_SHORTCUT("animation_editor/play_animation_from_end", p_ev)) {
1678
_play_bw_pressed();
1679
accept_event();
1680
} else if (ED_IS_SHORTCUT("animation_editor/go_to_next_keyframe", p_ev)) {
1681
go_to_nearest_keyframe(false);
1682
accept_event();
1683
} else if (ED_IS_SHORTCUT("animation_editor/go_to_previous_keyframe", p_ev)) {
1684
go_to_nearest_keyframe(true);
1685
accept_event();
1686
}
1687
}
1688
}
1689
1690
void AnimationPlayerEditor::_editor_visibility_changed() {
1691
if (is_visible() && animation->has_selectable_items()) {
1692
_start_onion_skinning();
1693
}
1694
}
1695
1696
bool AnimationPlayerEditor::_are_onion_layers_valid() {
1697
ERR_FAIL_COND_V(!onion.past && !onion.future, false);
1698
1699
Size2 capture_size = DisplayServer::get_singleton()->window_get_size(DisplayServer::MAIN_WINDOW_ID);
1700
return onion.captures.size() == onion.get_capture_count() && onion.capture_size == capture_size;
1701
}
1702
1703
void AnimationPlayerEditor::_allocate_onion_layers() {
1704
_free_onion_layers();
1705
1706
int captures = onion.get_capture_count();
1707
Size2 capture_size = DisplayServer::get_singleton()->window_get_size(DisplayServer::MAIN_WINDOW_ID);
1708
1709
onion.captures.resize(captures);
1710
onion.captures_valid.resize(captures);
1711
1712
for (int i = 0; i < captures; i++) {
1713
bool is_present = onion.differences_only && i == captures - 1;
1714
1715
// Each capture is a viewport with a canvas item attached that renders a full-size rect with the contents of the main viewport.
1716
onion.captures[i] = RS::get_singleton()->viewport_create();
1717
1718
RS::get_singleton()->viewport_set_size(onion.captures[i], capture_size.width, capture_size.height);
1719
RS::get_singleton()->viewport_set_update_mode(onion.captures[i], RS::VIEWPORT_UPDATE_ALWAYS);
1720
RS::get_singleton()->viewport_set_transparent_background(onion.captures[i], !is_present);
1721
RS::get_singleton()->viewport_attach_canvas(onion.captures[i], onion.capture.canvas);
1722
}
1723
1724
// Reset the capture canvas item to the current root viewport texture (defensive).
1725
RS::get_singleton()->canvas_item_clear(onion.capture.canvas_item);
1726
RS::get_singleton()->canvas_item_add_texture_rect(onion.capture.canvas_item, Rect2(Point2(), Point2(capture_size.x, -capture_size.y)), get_tree()->get_root()->get_texture()->get_rid());
1727
1728
onion.capture_size = capture_size;
1729
}
1730
1731
void AnimationPlayerEditor::_free_onion_layers() {
1732
for (uint32_t i = 0; i < onion.captures.size(); i++) {
1733
if (onion.captures[i].is_valid()) {
1734
RS::get_singleton()->free_rid(onion.captures[i]);
1735
}
1736
}
1737
onion.captures.clear();
1738
onion.captures_valid.clear();
1739
}
1740
1741
void AnimationPlayerEditor::_prepare_onion_layers_1() {
1742
// This would be called per viewport and we want to act once only.
1743
int64_t cur_frame = get_tree()->get_frame();
1744
if (cur_frame == onion.last_frame) {
1745
return;
1746
}
1747
1748
if (!onion.enabled || !is_visible() || !get_player() || !get_player()->has_animation(SceneStringName(RESET))) {
1749
_stop_onion_skinning();
1750
return;
1751
}
1752
1753
onion.last_frame = cur_frame;
1754
1755
// Refresh viewports with no onion layers overlaid.
1756
onion.can_overlay = false;
1757
plugin->update_overlays();
1758
1759
if (player->is_playing()) {
1760
return;
1761
}
1762
1763
// And go to next step afterwards.
1764
callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_2_prolog).call_deferred();
1765
}
1766
1767
void AnimationPlayerEditor::_prepare_onion_layers_2_prolog() {
1768
Ref<Animation> anim = player->get_animation(player->get_assigned_animation());
1769
if (anim.is_null()) {
1770
return;
1771
}
1772
1773
if (!_are_onion_layers_valid()) {
1774
_allocate_onion_layers();
1775
}
1776
1777
// Hide superfluous elements that would make the overlay unnecessary cluttered.
1778
if (Node3DEditor::get_singleton()->is_visible()) {
1779
// 3D
1780
onion.temp.spatial_edit_state = Node3DEditor::get_singleton()->get_state();
1781
Dictionary new_state = onion.temp.spatial_edit_state.duplicate();
1782
new_state["show_grid"] = false;
1783
new_state["show_origin"] = false;
1784
Array orig_vp = onion.temp.spatial_edit_state["viewports"];
1785
Array vp;
1786
vp.resize(4);
1787
for (int i = 0; i < vp.size(); i++) {
1788
Dictionary d = ((Dictionary)orig_vp[i]).duplicate();
1789
d["use_environment"] = false;
1790
d["doppler"] = false;
1791
d["listener"] = false;
1792
d["gizmos"] = onion.include_gizmos ? d["gizmos"] : Variant(false);
1793
d["information"] = false;
1794
vp[i] = d;
1795
}
1796
new_state["viewports"] = vp;
1797
// TODO: Save/restore only affected entries.
1798
Node3DEditor::get_singleton()->set_state(new_state);
1799
} else {
1800
// CanvasItemEditor.
1801
onion.temp.canvas_edit_state = CanvasItemEditor::get_singleton()->get_state();
1802
Dictionary new_state = onion.temp.canvas_edit_state.duplicate();
1803
new_state["show_origin"] = false;
1804
new_state["show_grid"] = false;
1805
new_state["show_rulers"] = false;
1806
new_state["show_guides"] = false;
1807
new_state["show_helpers"] = false;
1808
new_state["show_zoom_control"] = false;
1809
new_state["show_edit_locks"] = false;
1810
new_state["grid_visibility"] = 2; // TODO: Expose CanvasItemEditor::GRID_VISIBILITY_HIDE somehow and use it.
1811
new_state["show_transformation_gizmos"] = onion.include_gizmos ? new_state["gizmos"] : Variant(false);
1812
// TODO: Save/restore only affected entries.
1813
CanvasItemEditor::get_singleton()->set_state(new_state);
1814
}
1815
1816
// Tweak the root viewport to ensure it's rendered before our target.
1817
RID root_vp = get_tree()->get_root()->get_viewport_rid();
1818
onion.temp.screen_rect = Rect2(Vector2(), DisplayServer::get_singleton()->window_get_size(DisplayServer::MAIN_WINDOW_ID));
1819
RS::get_singleton()->viewport_attach_to_screen(root_vp, Rect2(), DisplayServer::INVALID_WINDOW_ID);
1820
RS::get_singleton()->viewport_set_update_mode(root_vp, RS::VIEWPORT_UPDATE_ALWAYS);
1821
1822
RID present_rid;
1823
if (onion.differences_only) {
1824
// Capture present scene as it is.
1825
RS::get_singleton()->canvas_item_set_material(onion.capture.canvas_item, RID());
1826
present_rid = onion.captures[onion.captures.size() - 1];
1827
RS::get_singleton()->viewport_set_active(present_rid, true);
1828
RS::get_singleton()->viewport_set_parent_viewport(root_vp, present_rid);
1829
RS::get_singleton()->draw(false);
1830
RS::get_singleton()->viewport_set_active(present_rid, false);
1831
}
1832
1833
// Backup current animation state.
1834
onion.temp.anim_values_backup = player->make_backup();
1835
onion.temp.anim_player_position = player->get_current_animation_position();
1836
1837
// Render every past/future step with the capture shader.
1838
1839
RS::get_singleton()->canvas_item_set_material(onion.capture.canvas_item, onion.capture.material->get_rid());
1840
onion.capture.material->set_shader_parameter("bkg_color", GLOBAL_GET("rendering/environment/defaults/default_clear_color"));
1841
onion.capture.material->set_shader_parameter("differences_only", onion.differences_only);
1842
onion.capture.material->set_shader_parameter("present", onion.differences_only ? RS::get_singleton()->viewport_get_texture(present_rid) : RID());
1843
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")));
1844
1845
uint32_t p_capture_idx = 0;
1846
int first_step_offset = onion.past ? -(int)onion.steps : 0;
1847
_prepare_onion_layers_2_step_prepare(first_step_offset, p_capture_idx);
1848
}
1849
1850
void AnimationPlayerEditor::_prepare_onion_layers_2_step_prepare(int p_step_offset, uint32_t p_capture_idx) {
1851
uint32_t next_capture_idx = p_capture_idx;
1852
if (p_step_offset == 0) {
1853
// Skip present step and switch to the color of future.
1854
if (!onion.force_white_modulate) {
1855
onion.capture.material->set_shader_parameter("dir_color", EDITOR_GET("editors/animation/onion_layers_future_color"));
1856
}
1857
} else {
1858
Ref<Animation> anim = player->get_animation(player->get_assigned_animation());
1859
double pos = onion.temp.anim_player_position + p_step_offset * anim->get_step();
1860
1861
bool valid = anim->get_loop_mode() != Animation::LOOP_NONE || (pos >= 0 && pos <= anim->get_length());
1862
onion.captures_valid[p_capture_idx] = valid;
1863
if (valid) {
1864
player->seek_internal(pos, true, true, false);
1865
OS::get_singleton()->get_main_loop()->process(0);
1866
// This is the key: process the frame and let all callbacks/updates/notifications happen
1867
// so everything (transforms, skeletons, etc.) is up-to-date visually.
1868
callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_2_step_capture).call_deferred(p_step_offset, p_capture_idx);
1869
return;
1870
} else {
1871
next_capture_idx++;
1872
}
1873
}
1874
1875
int last_step_offset = onion.future ? onion.steps : 0;
1876
if (p_step_offset < last_step_offset) {
1877
_prepare_onion_layers_2_step_prepare(p_step_offset + 1, next_capture_idx);
1878
} else {
1879
_prepare_onion_layers_2_epilog();
1880
}
1881
}
1882
1883
void AnimationPlayerEditor::_prepare_onion_layers_2_step_capture(int p_step_offset, uint32_t p_capture_idx) {
1884
DEV_ASSERT(p_step_offset != 0);
1885
DEV_ASSERT(onion.captures_valid[p_capture_idx]);
1886
1887
RID root_vp = get_tree()->get_root()->get_viewport_rid();
1888
RS::get_singleton()->viewport_set_active(onion.captures[p_capture_idx], true);
1889
RS::get_singleton()->viewport_set_parent_viewport(root_vp, onion.captures[p_capture_idx]);
1890
RS::get_singleton()->draw(false);
1891
RS::get_singleton()->viewport_set_active(onion.captures[p_capture_idx], false);
1892
1893
int last_step_offset = onion.future ? onion.steps : 0;
1894
if (p_step_offset < last_step_offset) {
1895
_prepare_onion_layers_2_step_prepare(p_step_offset + 1, p_capture_idx + 1);
1896
} else {
1897
_prepare_onion_layers_2_epilog();
1898
}
1899
}
1900
1901
void AnimationPlayerEditor::_prepare_onion_layers_2_epilog() {
1902
// Restore root viewport.
1903
RID root_vp = get_tree()->get_root()->get_viewport_rid();
1904
RS::get_singleton()->viewport_set_parent_viewport(root_vp, RID());
1905
RS::get_singleton()->viewport_attach_to_screen(root_vp, onion.temp.screen_rect, DisplayServer::MAIN_WINDOW_ID);
1906
RS::get_singleton()->viewport_set_update_mode(root_vp, RS::VIEWPORT_UPDATE_WHEN_VISIBLE);
1907
1908
// Restore animation state.
1909
// Here we're combine the power of seeking back to the original position and
1910
// restoring the values backup. In most cases they will bring the same value back,
1911
// but there are cases handled by one that the other can't.
1912
// Namely:
1913
// - Seeking won't restore any values that may have been modified by the user
1914
// in the node after the last time the AnimationPlayer updated it.
1915
// - Restoring the backup won't account for values that are not directly involved
1916
// in the animation but a consequence of them (e.g., SkeletonModification2DLookAt).
1917
// FIXME: Since backup of values is based on the reset animation, only values
1918
// backed by a proper reset animation will work correctly with onion
1919
// skinning and the possibility to restore the values mentioned in the
1920
// first point above is gone. Still good enough.
1921
player->seek_internal(onion.temp.anim_player_position, true, true, false);
1922
player->restore(onion.temp.anim_values_backup);
1923
1924
// Restore state of main editors.
1925
if (Node3DEditor::get_singleton()->is_visible()) {
1926
// 3D
1927
Node3DEditor::get_singleton()->set_state(onion.temp.spatial_edit_state);
1928
} else { // CanvasItemEditor
1929
// 2D
1930
CanvasItemEditor::get_singleton()->set_state(onion.temp.canvas_edit_state);
1931
}
1932
1933
// Update viewports with skin layers overlaid for the actual engine loop render.
1934
onion.can_overlay = true;
1935
plugin->update_overlays();
1936
}
1937
1938
void AnimationPlayerEditor::_start_onion_skinning() {
1939
if (get_player() && !get_player()->has_animation(SceneStringName(RESET))) {
1940
onion.enabled = false;
1941
onion_toggle->set_pressed_no_signal(false);
1942
return;
1943
}
1944
if (!get_tree()->is_connected(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1))) {
1945
get_tree()->connect(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1));
1946
}
1947
}
1948
1949
void AnimationPlayerEditor::_stop_onion_skinning() {
1950
if (get_tree()->is_connected(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1))) {
1951
get_tree()->disconnect(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1));
1952
1953
_free_onion_layers();
1954
1955
// Clean up.
1956
onion.can_overlay = false;
1957
plugin->update_overlays();
1958
onion.temp = {};
1959
}
1960
}
1961
1962
void AnimationPlayerEditor::_pin_pressed() {
1963
SceneTreeDock::get_singleton()->get_tree_editor()->update_tree();
1964
}
1965
1966
AnimationMixer *AnimationPlayerEditor::fetch_mixer_for_library() const {
1967
if (!original_node) {
1968
return nullptr;
1969
}
1970
// Does AnimationTree have AnimationPlayer?
1971
if (original_node->is_class("AnimationTree")) {
1972
AnimationTree *src_tree = Object::cast_to<AnimationTree>(original_node);
1973
Node *src_player = src_tree->get_node_or_null(src_tree->get_animation_player());
1974
if (src_player) {
1975
return Object::cast_to<AnimationMixer>(src_player);
1976
}
1977
}
1978
return original_node;
1979
}
1980
1981
Node *AnimationPlayerEditor::get_cached_root_node() const {
1982
return ObjectDB::get_instance<Node>(cached_root_node_id);
1983
}
1984
1985
bool AnimationPlayerEditor::_validate_tracks(const Ref<Animation> p_anim) {
1986
bool is_valid = true;
1987
if (p_anim.is_null()) {
1988
return true; // There is a problem outside of the animation track.
1989
}
1990
int len = p_anim->get_track_count();
1991
for (int i = 0; i < len; i++) {
1992
Animation::TrackType ttype = p_anim->track_get_type(i);
1993
if (ttype == Animation::TYPE_ROTATION_3D) {
1994
int key_len = p_anim->track_get_key_count(i);
1995
for (int j = 0; j < key_len; j++) {
1996
Quaternion q;
1997
p_anim->rotation_track_get_key(i, j, &q);
1998
ERR_BREAK_EDMSG(!q.is_normalized(), "AnimationPlayer: '" + player->get_name() + "', Animation: '" + player->get_current_animation() + "', 3D Rotation Track: '" + String(p_anim->track_get_path(i)) + "' contains unnormalized Quaternion key.");
1999
}
2000
} else if (ttype == Animation::TYPE_VALUE) {
2001
int key_len = p_anim->track_get_key_count(i);
2002
if (key_len == 0) {
2003
continue;
2004
}
2005
switch (p_anim->track_get_key_value(i, 0).get_type()) {
2006
case Variant::QUATERNION: {
2007
for (int j = 0; j < key_len; j++) {
2008
Quaternion q = Quaternion(p_anim->track_get_key_value(i, j));
2009
if (!q.is_normalized()) {
2010
is_valid = false;
2011
ERR_BREAK_EDMSG(true, "AnimationPlayer: '" + player->get_name() + "', Animation: '" + player->get_current_animation() + "', Value Track: '" + String(p_anim->track_get_path(i)) + "' contains unnormalized Quaternion key.");
2012
}
2013
}
2014
} break;
2015
case Variant::TRANSFORM3D: {
2016
for (int j = 0; j < key_len; j++) {
2017
Transform3D t = Transform3D(p_anim->track_get_key_value(i, j));
2018
if (!t.basis.orthonormalized().is_rotation()) {
2019
is_valid = false;
2020
ERR_BREAK_EDMSG(true, "AnimationPlayer: '" + player->get_name() + "', Animation: '" + player->get_current_animation() + "', Value Track: '" + String(p_anim->track_get_path(i)) + "' contains corrupted basis (some axes are too close other axis or scaled by zero) Transform3D key.");
2021
}
2022
}
2023
} break;
2024
default: {
2025
} break;
2026
}
2027
}
2028
}
2029
return is_valid;
2030
}
2031
2032
void AnimationPlayerEditor::_bind_methods() {
2033
// Needed for UndoRedo.
2034
ClassDB::bind_method(D_METHOD("_animation_player_changed"), &AnimationPlayerEditor::_animation_player_changed);
2035
ClassDB::bind_method(D_METHOD("_animation_update_key_frame"), &AnimationPlayerEditor::_animation_update_key_frame);
2036
ClassDB::bind_method(D_METHOD("_start_onion_skinning"), &AnimationPlayerEditor::_start_onion_skinning);
2037
ClassDB::bind_method(D_METHOD("_stop_onion_skinning"), &AnimationPlayerEditor::_stop_onion_skinning);
2038
2039
ADD_SIGNAL(MethodInfo("animation_selected", PropertyInfo(Variant::STRING, "name")));
2040
}
2041
2042
AnimationPlayerEditor *AnimationPlayerEditor::singleton = nullptr;
2043
2044
AnimationPlayer *AnimationPlayerEditor::get_player() const {
2045
return player;
2046
}
2047
2048
AnimationMixer *AnimationPlayerEditor::get_editing_node() const {
2049
return original_node;
2050
}
2051
2052
AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plugin) {
2053
plugin = p_plugin;
2054
singleton = this;
2055
2056
set_name(TTRC("Animation"));
2057
set_icon_name("Animation");
2058
set_dock_shortcut(ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_animation_bottom_panel", TTRC("Toggle Animation Dock"), KeyModifierMask::ALT | Key::N));
2059
set_default_slot(EditorDock::DOCK_SLOT_BOTTOM);
2060
set_available_layouts(EditorDock::DOCK_LAYOUT_HORIZONTAL | EditorDock::DOCK_LAYOUT_FLOATING);
2061
2062
set_focus_mode(FOCUS_ALL);
2063
set_process_shortcut_input(true);
2064
2065
VBoxContainer *main_vbox_container = memnew(VBoxContainer);
2066
add_child(main_vbox_container);
2067
HBoxContainer *hb = memnew(HBoxContainer);
2068
main_vbox_container->add_child(hb);
2069
2070
HBoxContainer *playback_container = memnew(HBoxContainer);
2071
playback_container->set_layout_direction(LAYOUT_DIRECTION_LTR);
2072
hb->add_child(playback_container);
2073
2074
play_bw_from = memnew(Button);
2075
play_bw_from->set_theme_type_variation(SceneStringName(FlatButton));
2076
playback_container->add_child(play_bw_from);
2077
2078
play_bw = memnew(Button);
2079
play_bw->set_theme_type_variation(SceneStringName(FlatButton));
2080
playback_container->add_child(play_bw);
2081
2082
stop = memnew(Button);
2083
stop->set_theme_type_variation(SceneStringName(FlatButton));
2084
playback_container->add_child(stop);
2085
2086
play = memnew(Button);
2087
play->set_theme_type_variation(SceneStringName(FlatButton));
2088
playback_container->add_child(play);
2089
2090
play_from = memnew(Button);
2091
play_from->set_theme_type_variation(SceneStringName(FlatButton));
2092
playback_container->add_child(play_from);
2093
2094
frame = memnew(SpinBox);
2095
hb->add_child(frame);
2096
frame->set_custom_minimum_size(Size2(80, 0) * EDSCALE);
2097
frame->set_stretch_ratio(2);
2098
frame->set_step(0.0001);
2099
frame->set_tooltip_text(TTR("Animation position (in seconds)."));
2100
2101
hb->add_child(memnew(VSeparator));
2102
2103
scale = memnew(LineEdit);
2104
hb->add_child(scale);
2105
scale->set_h_size_flags(SIZE_EXPAND_FILL);
2106
scale->set_stretch_ratio(1);
2107
scale->set_tooltip_text(TTR("Scale animation playback globally for the node."));
2108
scale->hide();
2109
2110
delete_dialog = memnew(ConfirmationDialog);
2111
add_child(delete_dialog);
2112
delete_dialog->connect(SceneStringName(confirmed), callable_mp(this, &AnimationPlayerEditor::_animation_remove_confirmed));
2113
2114
tool_anim = memnew(MenuButton);
2115
tool_anim->set_shortcut_context(this);
2116
tool_anim->set_flat(false);
2117
tool_anim->set_tooltip_text(TTR("Animation Tools"));
2118
tool_anim->set_text(TTR("Animation"));
2119
tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/new_animation", TTRC("New...")), TOOL_NEW_ANIM);
2120
tool_anim->get_popup()->add_separator();
2121
tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/animation_libraries", TTRC("Manage Animations...")), TOOL_ANIM_LIBRARY);
2122
tool_anim->get_popup()->add_separator();
2123
tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/duplicate_animation", TTRC("Duplicate...")), TOOL_DUPLICATE_ANIM);
2124
tool_anim->get_popup()->add_separator();
2125
tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/rename_animation", TTRC("Rename...")), TOOL_RENAME_ANIM);
2126
tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/edit_transitions", TTRC("Edit Transitions...")), TOOL_EDIT_TRANSITIONS);
2127
tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/open_animation_in_inspector", TTRC("Open in Inspector")), TOOL_EDIT_RESOURCE);
2128
tool_anim->get_popup()->add_separator();
2129
tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/remove_animation", TTRC("Remove")), TOOL_REMOVE_ANIM);
2130
tool_anim->set_disabled(true);
2131
tool_anim->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &AnimationPlayerEditor::_animation_tool_menu));
2132
hb->add_child(tool_anim);
2133
2134
animation = memnew(OptionButton);
2135
hb->add_child(animation);
2136
animation->set_accessibility_name(TTRC("Animation"));
2137
animation->set_h_size_flags(SIZE_EXPAND_FILL);
2138
animation->set_tooltip_text(TTR("Display list of animations in player."));
2139
animation->set_clip_text(true);
2140
animation->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
2141
2142
autoplay = memnew(Button);
2143
autoplay->set_theme_type_variation(SceneStringName(FlatButton));
2144
hb->add_child(autoplay);
2145
autoplay->set_tooltip_text(TTR("Autoplay on Load"));
2146
2147
hb->add_child(memnew(VSeparator));
2148
2149
track_editor = memnew(AnimationTrackEditor);
2150
hb->add_child(track_editor->get_edit_menu());
2151
2152
hb->add_child(memnew(VSeparator));
2153
2154
onion_toggle = memnew(Button);
2155
onion_toggle->set_theme_type_variation(SceneStringName(FlatButton));
2156
onion_toggle->set_toggle_mode(true);
2157
onion_toggle->set_tooltip_text(TTR("Enable Onion Skinning"));
2158
onion_toggle->connect(SceneStringName(pressed), callable_mp(this, &AnimationPlayerEditor::_onion_skinning_menu).bind(ONION_SKINNING_ENABLE));
2159
hb->add_child(onion_toggle);
2160
2161
onion_skinning = memnew(MenuButton);
2162
onion_skinning->set_accessibility_name(TTRC("Onion Skinning Options"));
2163
onion_skinning->set_flat(false);
2164
onion_skinning->set_theme_type_variation("FlatMenuButton");
2165
onion_skinning->set_tooltip_text(TTR("Onion Skinning Options"));
2166
onion_skinning->get_popup()->add_separator(TTR("Directions"));
2167
// TRANSLATORS: Opposite of "Future", refers to a direction in animation onion skinning.
2168
onion_skinning->get_popup()->add_check_item(TTR("Past"), ONION_SKINNING_PAST);
2169
onion_skinning->get_popup()->set_item_checked(-1, true);
2170
// TRANSLATORS: Opposite of "Past", refers to a direction in animation onion skinning.
2171
onion_skinning->get_popup()->add_check_item(TTR("Future"), ONION_SKINNING_FUTURE);
2172
onion_skinning->get_popup()->add_separator(TTR("Depth"));
2173
onion_skinning->get_popup()->add_radio_check_item(TTR("1 step"), ONION_SKINNING_1_STEP);
2174
onion_skinning->get_popup()->set_item_checked(-1, true);
2175
onion_skinning->get_popup()->add_radio_check_item(TTR("2 steps"), ONION_SKINNING_2_STEPS);
2176
onion_skinning->get_popup()->add_radio_check_item(TTR("3 steps"), ONION_SKINNING_3_STEPS);
2177
onion_skinning->get_popup()->add_separator();
2178
onion_skinning->get_popup()->add_check_item(TTR("Differences Only"), ONION_SKINNING_DIFFERENCES_ONLY);
2179
onion_skinning->get_popup()->add_check_item(TTR("Force White Modulate"), ONION_SKINNING_FORCE_WHITE_MODULATE);
2180
onion_skinning->get_popup()->add_check_item(TTR("Include Gizmos (3D)"), ONION_SKINNING_INCLUDE_GIZMOS);
2181
onion_skinning->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &AnimationPlayerEditor::_onion_skinning_menu));
2182
hb->add_child(onion_skinning);
2183
2184
hb->add_child(memnew(VSeparator));
2185
2186
pin = memnew(Button);
2187
pin->set_theme_type_variation(SceneStringName(FlatButton));
2188
pin->set_toggle_mode(true);
2189
pin->set_tooltip_text(TTR("Pin AnimationPlayer"));
2190
hb->add_child(pin);
2191
pin->connect(SceneStringName(pressed), callable_mp(this, &AnimationPlayerEditor::_pin_pressed));
2192
2193
file = memnew(EditorFileDialog);
2194
add_child(file);
2195
2196
name_dialog = memnew(ConfirmationDialog);
2197
name_dialog->set_title(TTR("Create New Animation"));
2198
name_dialog->set_hide_on_ok(false);
2199
add_child(name_dialog);
2200
VBoxContainer *vb = memnew(VBoxContainer);
2201
name_dialog->add_child(vb);
2202
2203
name_title = memnew(Label(TTR("Animation Name:")));
2204
vb->add_child(name_title);
2205
2206
HBoxContainer *name_hb = memnew(HBoxContainer);
2207
name = memnew(LineEdit);
2208
name_hb->add_child(name);
2209
name->set_h_size_flags(SIZE_EXPAND_FILL);
2210
library = memnew(OptionButton);
2211
name_hb->add_child(library);
2212
library->hide();
2213
vb->add_child(name_hb);
2214
name_dialog->register_text_enter(name);
2215
2216
error_dialog = memnew(AcceptDialog);
2217
error_dialog->set_ok_button_text(TTR("Close"));
2218
error_dialog->set_title(TTR("Error!"));
2219
name_dialog->add_child(error_dialog);
2220
2221
name_dialog->connect(SceneStringName(confirmed), callable_mp(this, &AnimationPlayerEditor::_animation_name_edited));
2222
2223
blend_editor.dialog = memnew(AcceptDialog);
2224
blend_editor.dialog->set_title(TTR("Cross-Animation Blend Times"));
2225
blend_editor.dialog->set_ok_button_text(TTR("Close"));
2226
blend_editor.dialog->set_hide_on_ok(true);
2227
add_child(blend_editor.dialog);
2228
2229
VBoxContainer *blend_vb = memnew(VBoxContainer);
2230
blend_editor.dialog->add_child(blend_vb);
2231
2232
blend_editor.tree = memnew(Tree);
2233
blend_editor.tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
2234
blend_editor.tree->set_hide_root(true);
2235
blend_editor.tree->set_columns(2);
2236
blend_editor.tree->set_column_expand_ratio(0, 10);
2237
blend_editor.tree->set_column_clip_content(0, true);
2238
blend_editor.tree->set_column_expand_ratio(1, 3);
2239
blend_editor.tree->set_column_clip_content(1, true);
2240
blend_vb->add_margin_child(TTR("Blend Times:"), blend_editor.tree, true);
2241
blend_editor.tree->connect(SNAME("item_edited"), callable_mp(this, &AnimationPlayerEditor::_blend_edited));
2242
2243
blend_editor.next = memnew(OptionButton);
2244
blend_editor.next->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
2245
blend_editor.next->connect(SceneStringName(item_selected), callable_mp(this, &AnimationPlayerEditor::_blend_editor_next_changed));
2246
blend_vb->add_margin_child(TTR("Next (Auto Queue):"), blend_editor.next);
2247
2248
autoplay->connect(SceneStringName(pressed), callable_mp(this, &AnimationPlayerEditor::_autoplay_pressed));
2249
autoplay->set_toggle_mode(true);
2250
play->connect(SceneStringName(pressed), callable_mp(this, &AnimationPlayerEditor::_play_pressed));
2251
play_from->connect(SceneStringName(pressed), callable_mp(this, &AnimationPlayerEditor::_play_from_pressed));
2252
play_bw->connect(SceneStringName(pressed), callable_mp(this, &AnimationPlayerEditor::_play_bw_pressed));
2253
play_bw_from->connect(SceneStringName(pressed), callable_mp(this, &AnimationPlayerEditor::_play_bw_from_pressed));
2254
stop->connect(SceneStringName(pressed), callable_mp(this, &AnimationPlayerEditor::_stop_pressed));
2255
2256
animation->connect(SceneStringName(item_selected), callable_mp(this, &AnimationPlayerEditor::_animation_selected));
2257
2258
frame->connect(SceneStringName(value_changed), callable_mp(this, &AnimationPlayerEditor::_seek_value_changed).bind(false));
2259
scale->connect(SceneStringName(text_submitted), callable_mp(this, &AnimationPlayerEditor::_scale_changed));
2260
2261
main_vbox_container->add_child(track_editor);
2262
track_editor->set_v_size_flags(SIZE_EXPAND_FILL);
2263
track_editor->connect(SNAME("timeline_changed"), callable_mp(this, &AnimationPlayerEditor::_animation_key_editor_seek));
2264
track_editor->connect(SNAME("animation_len_changed"), callable_mp(this, &AnimationPlayerEditor::_animation_key_editor_anim_len_changed));
2265
2266
_update_player();
2267
2268
library_editor = memnew(AnimationLibraryEditor);
2269
add_child(library_editor);
2270
library_editor->connect(SNAME("update_editor"), callable_mp(this, &AnimationPlayerEditor::_animation_player_changed));
2271
2272
// Onion skinning.
2273
2274
track_editor->connect(SceneStringName(visibility_changed), callable_mp(this, &AnimationPlayerEditor::_editor_visibility_changed));
2275
2276
onion.capture.canvas = RS::get_singleton()->canvas_create();
2277
onion.capture.canvas_item = RS::get_singleton()->canvas_item_create();
2278
RS::get_singleton()->canvas_item_set_parent(onion.capture.canvas_item, onion.capture.canvas);
2279
2280
onion.capture.material.instantiate();
2281
2282
onion.capture.shader.instantiate();
2283
onion.capture.shader->set_code(R"(
2284
// Animation editor onion skinning shader.
2285
2286
shader_type canvas_item;
2287
2288
uniform vec4 bkg_color;
2289
uniform vec4 dir_color;
2290
uniform bool differences_only;
2291
uniform sampler2D present;
2292
2293
float zero_if_equal(vec4 a, vec4 b) {
2294
return smoothstep(0.0, 0.005, length(a.rgb - b.rgb) / sqrt(3.0));
2295
}
2296
2297
void fragment() {
2298
vec4 capture_samp = texture(TEXTURE, UV);
2299
float bkg_mask = zero_if_equal(capture_samp, bkg_color);
2300
float diff_mask = 1.0;
2301
if (differences_only) {
2302
// FIXME: If Y-flips across render target, canvas item, etc. was handled correctly,
2303
// this would not be as convoluted in the shader.
2304
vec4 capture_samp2 = texture(TEXTURE, vec2(UV.x, 1.0 - UV.y));
2305
vec4 present_samp = texture(present, vec2(UV.x, 1.0 - UV.y));
2306
diff_mask = 1.0 - zero_if_equal(present_samp, bkg_color);
2307
}
2308
COLOR = vec4(capture_samp.rgb * dir_color.rgb, bkg_mask * diff_mask);
2309
}
2310
)");
2311
RS::get_singleton()->material_set_shader(onion.capture.material->get_rid(), onion.capture.shader->get_rid());
2312
2313
ED_SHORTCUT("animation_editor/stop_animation", TTRC("Pause/Stop Animation"), Key::S, true);
2314
ED_SHORTCUT("animation_editor/play_animation", TTRC("Play Animation"), Key::D, true);
2315
ED_SHORTCUT("animation_editor/play_animation_backwards", TTRC("Play Animation Backwards"), Key::A, true);
2316
ED_SHORTCUT("animation_editor/play_animation_from_start", TTRC("Play Animation from Start"), KeyModifierMask::SHIFT + Key::D, true);
2317
ED_SHORTCUT("animation_editor/play_animation_from_end", TTRC("Play Animation Backwards from End"), KeyModifierMask::SHIFT + Key::A, true);
2318
}
2319
2320
AnimationPlayerEditor::~AnimationPlayerEditor() {
2321
_free_onion_layers();
2322
RS::get_singleton()->free_rid(onion.capture.canvas);
2323
RS::get_singleton()->free_rid(onion.capture.canvas_item);
2324
onion.capture = {};
2325
}
2326
2327
void AnimationPlayerEditorPlugin::_notification(int p_what) {
2328
switch (p_what) {
2329
case NOTIFICATION_READY: {
2330
Node3DEditor::get_singleton()->connect(SNAME("transform_key_request"), callable_mp(this, &AnimationPlayerEditorPlugin::_transform_key_request));
2331
InspectorDock::get_inspector_singleton()->connect(SNAME("property_keyed"), callable_mp(this, &AnimationPlayerEditorPlugin::_property_keyed));
2332
anim_editor->get_track_editor()->connect(SNAME("keying_changed"), callable_mp(this, &AnimationPlayerEditorPlugin::_update_keying));
2333
InspectorDock::get_inspector_singleton()->connect(SNAME("edited_object_changed"), callable_mp(anim_editor->get_track_editor(), &AnimationTrackEditor::update_keying));
2334
set_force_draw_over_forwarding_enabled();
2335
} break;
2336
}
2337
}
2338
2339
void AnimationPlayerEditorPlugin::_property_keyed(const String &p_keyed, const Variant &p_value, bool p_advance) {
2340
AnimationTrackEditor *te = anim_editor->get_track_editor();
2341
if (!te || !te->has_keying()) {
2342
return;
2343
}
2344
te->_clear_selection();
2345
te->insert_value_key(p_keyed, p_advance);
2346
}
2347
2348
void AnimationPlayerEditorPlugin::_transform_key_request(Object *sp, const String &p_sub, const Transform3D &p_key) {
2349
if (!anim_editor->get_track_editor()->has_keying()) {
2350
return;
2351
}
2352
Node3D *s = Object::cast_to<Node3D>(sp);
2353
if (!s) {
2354
return;
2355
}
2356
anim_editor->get_track_editor()->insert_transform_key(s, p_sub, Animation::TYPE_POSITION_3D, p_key.origin);
2357
anim_editor->get_track_editor()->insert_transform_key(s, p_sub, Animation::TYPE_ROTATION_3D, p_key.basis.get_rotation_quaternion());
2358
anim_editor->get_track_editor()->insert_transform_key(s, p_sub, Animation::TYPE_SCALE_3D, p_key.basis.get_scale());
2359
}
2360
2361
void AnimationPlayerEditorPlugin::_update_keying() {
2362
InspectorDock::get_inspector_singleton()->set_keying(anim_editor->get_track_editor()->has_keying());
2363
}
2364
2365
void AnimationPlayerEditorPlugin::edit(Object *p_object) {
2366
if (player && anim_editor && anim_editor->is_pinned()) {
2367
return; // Ignore, pinned.
2368
}
2369
2370
player = nullptr;
2371
if (!p_object) {
2372
return;
2373
}
2374
last_mixer = p_object->get_instance_id();
2375
2376
AnimationMixer *src_node = Object::cast_to<AnimationMixer>(p_object);
2377
bool is_dummy = false;
2378
if (!p_object->is_class("AnimationPlayer")) {
2379
// If it needs dummy AnimationPlayer, assign original AnimationMixer to LibraryEditor.
2380
_update_dummy_player(src_node);
2381
2382
is_dummy = true;
2383
2384
if (!src_node->is_connected(SNAME("mixer_updated"), callable_mp(this, &AnimationPlayerEditorPlugin::_update_dummy_player))) {
2385
src_node->connect(SNAME("mixer_updated"), callable_mp(this, &AnimationPlayerEditorPlugin::_update_dummy_player).bind(src_node), CONNECT_DEFERRED);
2386
}
2387
if (!src_node->is_connected(SNAME("animation_libraries_updated"), callable_mp(this, &AnimationPlayerEditorPlugin::_update_dummy_player))) {
2388
src_node->connect(SNAME("animation_libraries_updated"), callable_mp(this, &AnimationPlayerEditorPlugin::_update_dummy_player).bind(src_node), CONNECT_DEFERRED);
2389
}
2390
} else {
2391
_clear_dummy_player();
2392
player = Object::cast_to<AnimationPlayer>(p_object);
2393
}
2394
player->set_dummy(is_dummy);
2395
2396
anim_editor->edit(src_node, player, is_dummy);
2397
}
2398
2399
void AnimationPlayerEditorPlugin::_clear_dummy_player() {
2400
if (!dummy_player) {
2401
return;
2402
}
2403
Node *parent = dummy_player->get_parent();
2404
if (parent) {
2405
callable_mp(parent, &Node::remove_child).call_deferred(dummy_player);
2406
}
2407
dummy_player->queue_free();
2408
dummy_player = nullptr;
2409
}
2410
2411
void AnimationPlayerEditorPlugin::_update_dummy_player(AnimationMixer *p_mixer) {
2412
// Check current editing object.
2413
if (p_mixer->get_instance_id() != last_mixer && p_mixer->is_connected(SNAME("mixer_updated"), callable_mp(this, &AnimationPlayerEditorPlugin::_update_dummy_player))) {
2414
p_mixer->disconnect(SNAME("mixer_updated"), callable_mp(this, &AnimationPlayerEditorPlugin::_update_dummy_player));
2415
return;
2416
}
2417
2418
// Add dummy player to scene.
2419
if (!dummy_player) {
2420
Node *parent = p_mixer->get_parent();
2421
ERR_FAIL_NULL(parent);
2422
dummy_player = memnew(AnimationPlayer);
2423
dummy_player->set_active(false); // Inactive as default, it will be activated if the AnimationPlayerEditor visibility is changed.
2424
parent->add_child(dummy_player);
2425
}
2426
player = dummy_player;
2427
2428
// Convert AnimationTree (AnimationMixer) to AnimationPlayer.
2429
AnimationMixer *default_node = memnew(AnimationMixer);
2430
List<PropertyInfo> pinfo;
2431
default_node->get_property_list(&pinfo);
2432
for (const PropertyInfo &E : pinfo) {
2433
if (!(E.usage & PROPERTY_USAGE_STORAGE)) {
2434
continue;
2435
}
2436
if (E.name != "script" && E.name != "active" && E.name != "deterministic" && E.name != "root_motion_track") {
2437
dummy_player->set(E.name, p_mixer->get(E.name));
2438
}
2439
}
2440
memdelete(default_node);
2441
2442
// Library list is dynamically added to property list, should be copied explicitly.
2443
List<StringName> libraries;
2444
p_mixer->get_animation_library_list(&libraries);
2445
for (const StringName &K : libraries) {
2446
dummy_player->add_animation_library(K, p_mixer->get_animation_library(K));
2447
}
2448
2449
if (anim_editor) {
2450
anim_editor->_update_player();
2451
}
2452
}
2453
2454
bool AnimationPlayerEditorPlugin::handles(Object *p_object) const {
2455
return p_object->is_class("AnimationPlayer") || p_object->is_class("AnimationTree") || p_object->is_class("AnimationMixer");
2456
}
2457
2458
void AnimationPlayerEditorPlugin::make_visible(bool p_visible) {
2459
if (p_visible) {
2460
// if AnimationTree editor is visible, do not occupy the bottom panel
2461
if (AnimationTreeEditor::get_singleton() && AnimationTreeEditor::get_singleton()->is_visible_in_tree()) {
2462
return;
2463
}
2464
anim_editor->make_visible();
2465
anim_editor->set_process(true);
2466
anim_editor->ensure_visibility();
2467
}
2468
}
2469
2470
AnimationPlayerEditorPlugin::AnimationPlayerEditorPlugin() {
2471
anim_editor = memnew(AnimationPlayerEditor(this));
2472
EditorDockManager::get_singleton()->add_dock(anim_editor);
2473
}
2474
2475
AnimationPlayerEditorPlugin::~AnimationPlayerEditorPlugin() {
2476
if (dummy_player) {
2477
memdelete(dummy_player);
2478
}
2479
}
2480
2481
// AnimationTrackKeyEditEditorPlugin
2482
2483
bool EditorInspectorPluginAnimationTrackKeyEdit::can_handle(Object *p_object) {
2484
return Object::cast_to<AnimationTrackKeyEdit>(p_object) != nullptr;
2485
}
2486
2487
void EditorInspectorPluginAnimationTrackKeyEdit::parse_begin(Object *p_object) {
2488
AnimationTrackKeyEdit *atk = Object::cast_to<AnimationTrackKeyEdit>(p_object);
2489
ERR_FAIL_NULL(atk);
2490
2491
atk_editor = memnew(AnimationTrackKeyEditEditor(atk->animation, atk->track, atk->key_ofs, atk->use_fps));
2492
add_custom_control(atk_editor);
2493
}
2494
2495
AnimationTrackKeyEditEditorPlugin::AnimationTrackKeyEditEditorPlugin() {
2496
atk_plugin = memnew(EditorInspectorPluginAnimationTrackKeyEdit);
2497
EditorInspector::add_inspector_plugin(atk_plugin);
2498
}
2499
2500
bool AnimationTrackKeyEditEditorPlugin::handles(Object *p_object) const {
2501
return p_object->is_class("AnimationTrackKeyEdit");
2502
}
2503
2504
bool EditorInspectorPluginAnimationMarkerKeyEdit::can_handle(Object *p_object) {
2505
return Object::cast_to<AnimationMarkerKeyEdit>(p_object) != nullptr;
2506
}
2507
2508
void EditorInspectorPluginAnimationMarkerKeyEdit::parse_begin(Object *p_object) {
2509
AnimationMarkerKeyEdit *amk = Object::cast_to<AnimationMarkerKeyEdit>(p_object);
2510
ERR_FAIL_NULL(amk);
2511
2512
amk_editor = memnew(AnimationMarkerKeyEditEditor(amk->animation, amk->marker_name, amk->use_fps));
2513
add_custom_control(amk_editor);
2514
}
2515
2516
AnimationMarkerKeyEditEditorPlugin::AnimationMarkerKeyEditEditorPlugin() {
2517
amk_plugin = memnew(EditorInspectorPluginAnimationMarkerKeyEdit);
2518
EditorInspector::add_inspector_plugin(amk_plugin);
2519
}
2520
2521
bool AnimationMarkerKeyEditEditorPlugin::handles(Object *p_object) const {
2522
return p_object->is_class("AnimationMarkerKeyEdit");
2523
}
2524
2525