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