1/**************************************************************************/
2/* animation_track_editor.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_track_editor.h"
32
33#include "animation_track_editor_plugins.h"
34#include "core/input/input.h"
35#include "editor/animation_bezier_editor.h"
36#include "editor/editor_node.h"
37#include "editor/editor_scale.h"
38#include "editor/editor_settings.h"
39#include "editor/editor_string_names.h"
40#include "editor/editor_undo_redo_manager.h"
41#include "editor/gui/editor_spin_slider.h"
42#include "editor/gui/scene_tree_editor.h"
43#include "editor/inspector_dock.h"
44#include "editor/plugins/animation_player_editor_plugin.h"
45#include "scene/animation/animation_player.h"
46#include "scene/animation/tween.h"
47#include "scene/gui/check_box.h"
48#include "scene/gui/grid_container.h"
49#include "scene/gui/option_button.h"
50#include "scene/gui/panel_container.h"
51#include "scene/gui/separator.h"
52#include "scene/gui/slider.h"
53#include "scene/gui/spin_box.h"
54#include "scene/gui/texture_rect.h"
55#include "scene/gui/view_panner.h"
56#include "scene/main/window.h"
57#include "scene/scene_string_names.h"
58#include "servers/audio/audio_stream.h"
59
60void AnimationTrackKeyEdit::_bind_methods() {
61 ClassDB::bind_method(D_METHOD("_update_obj"), &AnimationTrackKeyEdit::_update_obj);
62 ClassDB::bind_method(D_METHOD("_key_ofs_changed"), &AnimationTrackKeyEdit::_key_ofs_changed);
63 ClassDB::bind_method(D_METHOD("_hide_script_from_inspector"), &AnimationTrackKeyEdit::_hide_script_from_inspector);
64 ClassDB::bind_method(D_METHOD("_hide_metadata_from_inspector"), &AnimationTrackKeyEdit::_hide_metadata_from_inspector);
65 ClassDB::bind_method(D_METHOD("get_root_path"), &AnimationTrackKeyEdit::get_root_path);
66 ClassDB::bind_method(D_METHOD("_dont_undo_redo"), &AnimationTrackKeyEdit::_dont_undo_redo);
67 ClassDB::bind_method(D_METHOD("_is_read_only"), &AnimationTrackKeyEdit::_is_read_only);
68}
69
70void AnimationTrackKeyEdit::_fix_node_path(Variant &value) {
71 NodePath np = value;
72
73 if (np == NodePath()) {
74 return;
75 }
76
77 Node *root = EditorNode::get_singleton()->get_tree()->get_root();
78
79 Node *np_node = root->get_node(np);
80 ERR_FAIL_NULL(np_node);
81
82 Node *edited_node = root->get_node(base);
83 ERR_FAIL_NULL(edited_node);
84
85 value = edited_node->get_path_to(np_node);
86}
87
88void AnimationTrackKeyEdit::_update_obj(const Ref<Animation> &p_anim) {
89 if (setting || animation != p_anim) {
90 return;
91 }
92
93 notify_change();
94}
95
96void AnimationTrackKeyEdit::_key_ofs_changed(const Ref<Animation> &p_anim, float from, float to) {
97 if (animation != p_anim || from != key_ofs) {
98 return;
99 }
100
101 key_ofs = to;
102
103 if (setting) {
104 return;
105 }
106
107 notify_change();
108}
109
110bool AnimationTrackKeyEdit::_set(const StringName &p_name, const Variant &p_value) {
111 int key = animation->track_find_key(track, key_ofs, Animation::FIND_MODE_APPROX);
112 ERR_FAIL_COND_V(key == -1, false);
113
114 String name = p_name;
115 if (name == "easing") {
116 float val = p_value;
117 float prev_val = animation->track_get_key_transition(track, key);
118 setting = true;
119 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
120 undo_redo->create_action(TTR("Animation Change Transition"), UndoRedo::MERGE_ENDS);
121 undo_redo->add_do_method(animation.ptr(), "track_set_key_transition", track, key, val);
122 undo_redo->add_undo_method(animation.ptr(), "track_set_key_transition", track, key, prev_val);
123 undo_redo->add_do_method(this, "_update_obj", animation);
124 undo_redo->add_undo_method(this, "_update_obj", animation);
125 undo_redo->commit_action();
126
127 setting = false;
128 return true;
129 }
130
131 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
132 switch (animation->track_get_type(track)) {
133 case Animation::TYPE_POSITION_3D:
134 case Animation::TYPE_ROTATION_3D:
135 case Animation::TYPE_SCALE_3D: {
136 if (name == "position" || name == "rotation" || name == "scale") {
137 Variant old = animation->track_get_key_value(track, key);
138 setting = true;
139 String action_name;
140 switch (animation->track_get_type(track)) {
141 case Animation::TYPE_POSITION_3D:
142 action_name = TTR("Animation Change Position3D");
143 break;
144 case Animation::TYPE_ROTATION_3D:
145 action_name = TTR("Animation Change Rotation3D");
146 break;
147 case Animation::TYPE_SCALE_3D:
148 action_name = TTR("Animation Change Scale3D");
149 break;
150 default: {
151 }
152 }
153
154 undo_redo->create_action(action_name);
155 undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, p_value);
156 undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, old);
157 undo_redo->add_do_method(this, "_update_obj", animation);
158 undo_redo->add_undo_method(this, "_update_obj", animation);
159 undo_redo->commit_action();
160
161 setting = false;
162 return true;
163 }
164
165 } break;
166 case Animation::TYPE_BLEND_SHAPE:
167 case Animation::TYPE_VALUE: {
168 if (name == "value") {
169 Variant value = p_value;
170
171 if (value.get_type() == Variant::NODE_PATH) {
172 _fix_node_path(value);
173 }
174
175 setting = true;
176 undo_redo->create_action(TTR("Animation Change Keyframe Value"), UndoRedo::MERGE_ENDS);
177 Variant prev = animation->track_get_key_value(track, key);
178 undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, value);
179 undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, prev);
180 undo_redo->add_do_method(this, "_update_obj", animation);
181 undo_redo->add_undo_method(this, "_update_obj", animation);
182 undo_redo->commit_action();
183
184 setting = false;
185 return true;
186 }
187 } break;
188 case Animation::TYPE_METHOD: {
189 Dictionary d_old = animation->track_get_key_value(track, key);
190 Dictionary d_new = d_old.duplicate();
191
192 bool change_notify_deserved = false;
193 bool mergeable = false;
194
195 if (name == "name") {
196 d_new["method"] = p_value;
197 } else if (name == "arg_count") {
198 Vector<Variant> args = d_old["args"];
199 args.resize(p_value);
200 d_new["args"] = args;
201 change_notify_deserved = true;
202 } else if (name.begins_with("args/")) {
203 Vector<Variant> args = d_old["args"];
204 int idx = name.get_slice("/", 1).to_int();
205 ERR_FAIL_INDEX_V(idx, args.size(), false);
206
207 String what = name.get_slice("/", 2);
208 if (what == "type") {
209 Variant::Type t = Variant::Type(int(p_value));
210
211 if (t != args[idx].get_type()) {
212 Callable::CallError err;
213 if (Variant::can_convert_strict(args[idx].get_type(), t)) {
214 Variant old = args[idx];
215 Variant *ptrs[1] = { &old };
216 Variant::construct(t, args.write[idx], (const Variant **)ptrs, 1, err);
217 } else {
218 Variant::construct(t, args.write[idx], nullptr, 0, err);
219 }
220 change_notify_deserved = true;
221 d_new["args"] = args;
222 }
223 } else if (what == "value") {
224 Variant value = p_value;
225 if (value.get_type() == Variant::NODE_PATH) {
226 _fix_node_path(value);
227 }
228
229 args.write[idx] = value;
230 d_new["args"] = args;
231 mergeable = true;
232 }
233 }
234
235 if (mergeable) {
236 undo_redo->create_action(TTR("Animation Change Call"), UndoRedo::MERGE_ENDS);
237 } else {
238 undo_redo->create_action(TTR("Animation Change Call"));
239 }
240
241 setting = true;
242 undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, d_new);
243 undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, d_old);
244 undo_redo->add_do_method(this, "_update_obj", animation);
245 undo_redo->add_undo_method(this, "_update_obj", animation);
246 undo_redo->commit_action();
247
248 setting = false;
249 if (change_notify_deserved) {
250 notify_change();
251 }
252 return true;
253 } break;
254 case Animation::TYPE_BEZIER: {
255 if (name == "value") {
256 const Variant &value = p_value;
257
258 setting = true;
259 undo_redo->create_action(TTR("Animation Change Keyframe Value"), UndoRedo::MERGE_ENDS);
260 float prev = animation->bezier_track_get_key_value(track, key);
261 undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_value", track, key, value);
262 undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_value", track, key, prev);
263 undo_redo->add_do_method(this, "_update_obj", animation);
264 undo_redo->add_undo_method(this, "_update_obj", animation);
265 undo_redo->commit_action();
266
267 setting = false;
268 return true;
269 }
270
271 if (name == "in_handle") {
272 const Variant &value = p_value;
273
274 setting = true;
275 undo_redo->create_action(TTR("Animation Change Keyframe Value"), UndoRedo::MERGE_ENDS);
276 Vector2 prev = animation->bezier_track_get_key_in_handle(track, key);
277 undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_in_handle", track, key, value);
278 undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", track, key, prev);
279 undo_redo->add_do_method(this, "_update_obj", animation);
280 undo_redo->add_undo_method(this, "_update_obj", animation);
281 undo_redo->commit_action();
282
283 setting = false;
284 return true;
285 }
286
287 if (name == "out_handle") {
288 const Variant &value = p_value;
289
290 setting = true;
291 undo_redo->create_action(TTR("Animation Change Keyframe Value"), UndoRedo::MERGE_ENDS);
292 Vector2 prev = animation->bezier_track_get_key_out_handle(track, key);
293 undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_out_handle", track, key, value);
294 undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", track, key, prev);
295 undo_redo->add_do_method(this, "_update_obj", animation);
296 undo_redo->add_undo_method(this, "_update_obj", animation);
297 undo_redo->commit_action();
298
299 setting = false;
300 return true;
301 }
302
303 if (name == "handle_mode") {
304 const Variant &value = p_value;
305
306 setting = true;
307 undo_redo->create_action(TTR("Animation Change Keyframe Value"), UndoRedo::MERGE_ENDS);
308 int prev = animation->bezier_track_get_key_handle_mode(track, key);
309 undo_redo->add_do_method(this, "_bezier_track_set_key_handle_mode", animation.ptr(), track, key, value);
310 undo_redo->add_undo_method(this, "_bezier_track_set_key_handle_mode", animation.ptr(), track, key, prev);
311 undo_redo->add_do_method(this, "_update_obj", animation);
312 undo_redo->add_undo_method(this, "_update_obj", animation);
313 undo_redo->commit_action();
314
315 setting = false;
316 return true;
317 }
318 } break;
319 case Animation::TYPE_AUDIO: {
320 if (name == "stream") {
321 Ref<AudioStream> stream = p_value;
322
323 setting = true;
324 undo_redo->create_action(TTR("Animation Change Keyframe Value"), UndoRedo::MERGE_ENDS);
325 Ref<Resource> prev = animation->audio_track_get_key_stream(track, key);
326 undo_redo->add_do_method(animation.ptr(), "audio_track_set_key_stream", track, key, stream);
327 undo_redo->add_undo_method(animation.ptr(), "audio_track_set_key_stream", track, key, prev);
328 undo_redo->add_do_method(this, "_update_obj", animation);
329 undo_redo->add_undo_method(this, "_update_obj", animation);
330 undo_redo->commit_action();
331
332 setting = false;
333 return true;
334 }
335
336 if (name == "start_offset") {
337 float value = p_value;
338
339 setting = true;
340 undo_redo->create_action(TTR("Animation Change Keyframe Value"), UndoRedo::MERGE_ENDS);
341 float prev = animation->audio_track_get_key_start_offset(track, key);
342 undo_redo->add_do_method(animation.ptr(), "audio_track_set_key_start_offset", track, key, value);
343 undo_redo->add_undo_method(animation.ptr(), "audio_track_set_key_start_offset", track, key, prev);
344 undo_redo->add_do_method(this, "_update_obj", animation);
345 undo_redo->add_undo_method(this, "_update_obj", animation);
346 undo_redo->commit_action();
347
348 setting = false;
349 return true;
350 }
351
352 if (name == "end_offset") {
353 float value = p_value;
354
355 setting = true;
356 undo_redo->create_action(TTR("Animation Change Keyframe Value"), UndoRedo::MERGE_ENDS);
357 float prev = animation->audio_track_get_key_end_offset(track, key);
358 undo_redo->add_do_method(animation.ptr(), "audio_track_set_key_end_offset", track, key, value);
359 undo_redo->add_undo_method(animation.ptr(), "audio_track_set_key_end_offset", track, key, prev);
360 undo_redo->add_do_method(this, "_update_obj", animation);
361 undo_redo->add_undo_method(this, "_update_obj", animation);
362 undo_redo->commit_action();
363
364 setting = false;
365 return true;
366 }
367 } break;
368 case Animation::TYPE_ANIMATION: {
369 if (name == "animation") {
370 StringName anim_name = p_value;
371
372 setting = true;
373 undo_redo->create_action(TTR("Animation Change Keyframe Value"), UndoRedo::MERGE_ENDS);
374 StringName prev = animation->animation_track_get_key_animation(track, key);
375 undo_redo->add_do_method(animation.ptr(), "animation_track_set_key_animation", track, key, anim_name);
376 undo_redo->add_undo_method(animation.ptr(), "animation_track_set_key_animation", track, key, prev);
377 undo_redo->add_do_method(this, "_update_obj", animation);
378 undo_redo->add_undo_method(this, "_update_obj", animation);
379 undo_redo->commit_action();
380
381 setting = false;
382 return true;
383 }
384 } break;
385 }
386
387 return false;
388}
389
390bool AnimationTrackKeyEdit::_get(const StringName &p_name, Variant &r_ret) const {
391 int key = animation->track_find_key(track, key_ofs, Animation::FIND_MODE_APPROX);
392 ERR_FAIL_COND_V(key == -1, false);
393
394 String name = p_name;
395 if (name == "easing") {
396 r_ret = animation->track_get_key_transition(track, key);
397 return true;
398 }
399
400 switch (animation->track_get_type(track)) {
401 case Animation::TYPE_POSITION_3D:
402 case Animation::TYPE_ROTATION_3D:
403 case Animation::TYPE_SCALE_3D: {
404 if (name == "position" || name == "rotation" || name == "scale") {
405 r_ret = animation->track_get_key_value(track, key);
406 return true;
407 }
408 } break;
409 case Animation::TYPE_BLEND_SHAPE:
410 case Animation::TYPE_VALUE: {
411 if (name == "value") {
412 r_ret = animation->track_get_key_value(track, key);
413 return true;
414 }
415
416 } break;
417 case Animation::TYPE_METHOD: {
418 Dictionary d = animation->track_get_key_value(track, key);
419
420 if (name == "name") {
421 ERR_FAIL_COND_V(!d.has("method"), false);
422 r_ret = d["method"];
423 return true;
424 }
425
426 ERR_FAIL_COND_V(!d.has("args"), false);
427
428 Vector<Variant> args = d["args"];
429
430 if (name == "arg_count") {
431 r_ret = args.size();
432 return true;
433 }
434
435 if (name.begins_with("args/")) {
436 int idx = name.get_slice("/", 1).to_int();
437 ERR_FAIL_INDEX_V(idx, args.size(), false);
438
439 String what = name.get_slice("/", 2);
440 if (what == "type") {
441 r_ret = args[idx].get_type();
442 return true;
443 }
444
445 if (what == "value") {
446 r_ret = args[idx];
447 return true;
448 }
449 }
450
451 } break;
452 case Animation::TYPE_BEZIER: {
453 if (name == "value") {
454 r_ret = animation->bezier_track_get_key_value(track, key);
455 return true;
456 }
457
458 if (name == "in_handle") {
459 r_ret = animation->bezier_track_get_key_in_handle(track, key);
460 return true;
461 }
462
463 if (name == "out_handle") {
464 r_ret = animation->bezier_track_get_key_out_handle(track, key);
465 return true;
466 }
467
468 if (name == "handle_mode") {
469 r_ret = animation->bezier_track_get_key_handle_mode(track, key);
470 return true;
471 }
472
473 } break;
474 case Animation::TYPE_AUDIO: {
475 if (name == "stream") {
476 r_ret = animation->audio_track_get_key_stream(track, key);
477 return true;
478 }
479
480 if (name == "start_offset") {
481 r_ret = animation->audio_track_get_key_start_offset(track, key);
482 return true;
483 }
484
485 if (name == "end_offset") {
486 r_ret = animation->audio_track_get_key_end_offset(track, key);
487 return true;
488 }
489
490 } break;
491 case Animation::TYPE_ANIMATION: {
492 if (name == "animation") {
493 r_ret = animation->animation_track_get_key_animation(track, key);
494 return true;
495 }
496
497 } break;
498 }
499
500 return false;
501}
502
503void AnimationTrackKeyEdit::_get_property_list(List<PropertyInfo> *p_list) const {
504 if (animation.is_null()) {
505 return;
506 }
507
508 ERR_FAIL_INDEX(track, animation->get_track_count());
509 int key = animation->track_find_key(track, key_ofs, Animation::FIND_MODE_APPROX);
510 ERR_FAIL_COND(key == -1);
511
512 switch (animation->track_get_type(track)) {
513 case Animation::TYPE_POSITION_3D: {
514 p_list->push_back(PropertyInfo(Variant::VECTOR3, PNAME("position")));
515 } break;
516 case Animation::TYPE_ROTATION_3D: {
517 p_list->push_back(PropertyInfo(Variant::QUATERNION, PNAME("rotation")));
518 } break;
519 case Animation::TYPE_SCALE_3D: {
520 p_list->push_back(PropertyInfo(Variant::VECTOR3, PNAME("scale")));
521 } break;
522 case Animation::TYPE_BLEND_SHAPE: {
523 p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("value")));
524 } break;
525 case Animation::TYPE_VALUE: {
526 Variant v = animation->track_get_key_value(track, key);
527
528 if (hint.type != Variant::NIL) {
529 PropertyInfo pi = hint;
530 pi.name = PNAME("value");
531 p_list->push_back(pi);
532 } else {
533 PropertyHint val_hint = PROPERTY_HINT_NONE;
534 String val_hint_string;
535
536 if (v.get_type() == Variant::OBJECT) {
537 // Could actually check the object property if exists..? Yes I will!
538 Ref<Resource> res = v;
539 if (res.is_valid()) {
540 val_hint = PROPERTY_HINT_RESOURCE_TYPE;
541 val_hint_string = res->get_class();
542 }
543 }
544
545 if (v.get_type() != Variant::NIL) {
546 p_list->push_back(PropertyInfo(v.get_type(), PNAME("value"), val_hint, val_hint_string));
547 }
548 }
549
550 } break;
551 case Animation::TYPE_METHOD: {
552 p_list->push_back(PropertyInfo(Variant::STRING_NAME, PNAME("name")));
553 p_list->push_back(PropertyInfo(Variant::INT, PNAME("arg_count"), PROPERTY_HINT_RANGE, "0,32,1,or_greater"));
554
555 Dictionary d = animation->track_get_key_value(track, key);
556 ERR_FAIL_COND(!d.has("args"));
557 Vector<Variant> args = d["args"];
558 String vtypes;
559 for (int i = 0; i < Variant::VARIANT_MAX; i++) {
560 if (i > 0) {
561 vtypes += ",";
562 }
563 vtypes += Variant::get_type_name(Variant::Type(i));
564 }
565
566 for (int i = 0; i < args.size(); i++) {
567 p_list->push_back(PropertyInfo(Variant::INT, vformat("%s/%d/%s", PNAME("args"), i, PNAME("type")), PROPERTY_HINT_ENUM, vtypes));
568 if (args[i].get_type() != Variant::NIL) {
569 p_list->push_back(PropertyInfo(args[i].get_type(), vformat("%s/%d/%s", PNAME("args"), i, PNAME("value"))));
570 }
571 }
572
573 } break;
574 case Animation::TYPE_BEZIER: {
575 Animation::HandleMode hm = animation->bezier_track_get_key_handle_mode(track, key);
576 p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("value")));
577 if (hm == Animation::HANDLE_MODE_LINEAR) {
578 p_list->push_back(PropertyInfo(Variant::VECTOR2, PNAME("in_handle"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY));
579 p_list->push_back(PropertyInfo(Variant::VECTOR2, PNAME("out_handle"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY));
580 } else {
581 p_list->push_back(PropertyInfo(Variant::VECTOR2, PNAME("in_handle")));
582 p_list->push_back(PropertyInfo(Variant::VECTOR2, PNAME("out_handle")));
583 }
584 p_list->push_back(PropertyInfo(Variant::INT, PNAME("handle_mode"), PROPERTY_HINT_ENUM, "Free,Linear,Balanced,Mirrored"));
585
586 } break;
587 case Animation::TYPE_AUDIO: {
588 p_list->push_back(PropertyInfo(Variant::OBJECT, PNAME("stream"), PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"));
589 p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("start_offset"), PROPERTY_HINT_RANGE, "0,3600,0.0001,or_greater"));
590 p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("end_offset"), PROPERTY_HINT_RANGE, "0,3600,0.0001,or_greater"));
591
592 } break;
593 case Animation::TYPE_ANIMATION: {
594 String animations;
595
596 if (root_path && root_path->has_node(animation->track_get_path(track))) {
597 AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(root_path->get_node(animation->track_get_path(track)));
598 if (ap) {
599 List<StringName> anims;
600 ap->get_animation_list(&anims);
601 for (const StringName &E : anims) {
602 if (!animations.is_empty()) {
603 animations += ",";
604 }
605
606 animations += String(E);
607 }
608 }
609 }
610
611 if (!animations.is_empty()) {
612 animations += ",";
613 }
614 animations += "[stop]";
615
616 p_list->push_back(PropertyInfo(Variant::STRING_NAME, PNAME("animation"), PROPERTY_HINT_ENUM, animations));
617
618 } break;
619 }
620
621 if (animation->track_get_type(track) == Animation::TYPE_VALUE) {
622 p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("easing"), PROPERTY_HINT_EXP_EASING));
623 }
624}
625
626void AnimationTrackKeyEdit::notify_change() {
627 notify_property_list_changed();
628}
629
630Node *AnimationTrackKeyEdit::get_root_path() {
631 return root_path;
632}
633
634void AnimationTrackKeyEdit::set_use_fps(bool p_enable) {
635 use_fps = p_enable;
636 notify_property_list_changed();
637}
638
639void AnimationMultiTrackKeyEdit::_bind_methods() {
640 ClassDB::bind_method(D_METHOD("_update_obj"), &AnimationMultiTrackKeyEdit::_update_obj);
641 ClassDB::bind_method(D_METHOD("_key_ofs_changed"), &AnimationMultiTrackKeyEdit::_key_ofs_changed);
642 ClassDB::bind_method(D_METHOD("_hide_script_from_inspector"), &AnimationMultiTrackKeyEdit::_hide_script_from_inspector);
643 ClassDB::bind_method(D_METHOD("_hide_metadata_from_inspector"), &AnimationMultiTrackKeyEdit::_hide_metadata_from_inspector);
644 ClassDB::bind_method(D_METHOD("get_root_path"), &AnimationMultiTrackKeyEdit::get_root_path);
645 ClassDB::bind_method(D_METHOD("_dont_undo_redo"), &AnimationMultiTrackKeyEdit::_dont_undo_redo);
646 ClassDB::bind_method(D_METHOD("_is_read_only"), &AnimationMultiTrackKeyEdit::_is_read_only);
647}
648
649void AnimationMultiTrackKeyEdit::_fix_node_path(Variant &value, NodePath &base) {
650 NodePath np = value;
651
652 if (np == NodePath()) {
653 return;
654 }
655
656 Node *root = EditorNode::get_singleton()->get_tree()->get_root();
657
658 Node *np_node = root->get_node(np);
659 ERR_FAIL_NULL(np_node);
660
661 Node *edited_node = root->get_node(base);
662 ERR_FAIL_NULL(edited_node);
663
664 value = edited_node->get_path_to(np_node);
665}
666
667void AnimationMultiTrackKeyEdit::_update_obj(const Ref<Animation> &p_anim) {
668 if (setting || animation != p_anim) {
669 return;
670 }
671
672 notify_change();
673}
674
675void AnimationMultiTrackKeyEdit::_key_ofs_changed(const Ref<Animation> &p_anim, float from, float to) {
676 if (animation != p_anim) {
677 return;
678 }
679
680 for (const KeyValue<int, List<float>> &E : key_ofs_map) {
681 int key = 0;
682 for (const float &key_ofs : E.value) {
683 if (from != key_ofs) {
684 key++;
685 continue;
686 }
687
688 int track = E.key;
689 key_ofs_map[track][key] = to;
690
691 if (setting) {
692 return;
693 }
694
695 notify_change();
696
697 return;
698 }
699 }
700}
701
702bool AnimationMultiTrackKeyEdit::_set(const StringName &p_name, const Variant &p_value) {
703 bool update_obj = false;
704 bool change_notify_deserved = false;
705 for (const KeyValue<int, List<float>> &E : key_ofs_map) {
706 int track = E.key;
707 for (const float &key_ofs : E.value) {
708 int key = animation->track_find_key(track, key_ofs, Animation::FIND_MODE_APPROX);
709 ERR_FAIL_COND_V(key == -1, false);
710
711 String name = p_name;
712 if (name == "easing") {
713 float val = p_value;
714 float prev_val = animation->track_get_key_transition(track, key);
715
716 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
717 if (!setting) {
718 setting = true;
719 undo_redo->create_action(TTR("Animation Multi Change Transition"), UndoRedo::MERGE_ENDS);
720 }
721 undo_redo->add_do_method(animation.ptr(), "track_set_key_transition", track, key, val);
722 undo_redo->add_undo_method(animation.ptr(), "track_set_key_transition", track, key, prev_val);
723 update_obj = true;
724 }
725
726 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
727 switch (animation->track_get_type(track)) {
728 case Animation::TYPE_POSITION_3D:
729 case Animation::TYPE_ROTATION_3D:
730 case Animation::TYPE_SCALE_3D: {
731 Variant old = animation->track_get_key_value(track, key);
732 if (!setting) {
733 String action_name;
734 switch (animation->track_get_type(track)) {
735 case Animation::TYPE_POSITION_3D:
736 action_name = TTR("Animation Multi Change Position3D");
737 break;
738 case Animation::TYPE_ROTATION_3D:
739 action_name = TTR("Animation Multi Change Rotation3D");
740 break;
741 case Animation::TYPE_SCALE_3D:
742 action_name = TTR("Animation Multi Change Scale3D");
743 break;
744 default: {
745 }
746 }
747
748 setting = true;
749 undo_redo->create_action(action_name);
750 }
751 undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, p_value);
752 undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, old);
753 update_obj = true;
754 } break;
755 case Animation::TYPE_BLEND_SHAPE:
756 case Animation::TYPE_VALUE: {
757 if (name == "value") {
758 Variant value = p_value;
759
760 if (value.get_type() == Variant::NODE_PATH) {
761 _fix_node_path(value, base_map[track]);
762 }
763
764 if (!setting) {
765 setting = true;
766 undo_redo->create_action(TTR("Animation Multi Change Keyframe Value"), UndoRedo::MERGE_ENDS);
767 }
768 Variant prev = animation->track_get_key_value(track, key);
769 undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, value);
770 undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, prev);
771 update_obj = true;
772 }
773 } break;
774 case Animation::TYPE_METHOD: {
775 Dictionary d_old = animation->track_get_key_value(track, key);
776 Dictionary d_new = d_old.duplicate();
777
778 bool mergeable = false;
779
780 if (name == "name") {
781 d_new["method"] = p_value;
782 } else if (name == "arg_count") {
783 Vector<Variant> args = d_old["args"];
784 args.resize(p_value);
785 d_new["args"] = args;
786 change_notify_deserved = true;
787 } else if (name.begins_with("args/")) {
788 Vector<Variant> args = d_old["args"];
789 int idx = name.get_slice("/", 1).to_int();
790 ERR_FAIL_INDEX_V(idx, args.size(), false);
791
792 String what = name.get_slice("/", 2);
793 if (what == "type") {
794 Variant::Type t = Variant::Type(int(p_value));
795
796 if (t != args[idx].get_type()) {
797 Callable::CallError err;
798 if (Variant::can_convert_strict(args[idx].get_type(), t)) {
799 Variant old = args[idx];
800 Variant *ptrs[1] = { &old };
801 Variant::construct(t, args.write[idx], (const Variant **)ptrs, 1, err);
802 } else {
803 Variant::construct(t, args.write[idx], nullptr, 0, err);
804 }
805 change_notify_deserved = true;
806 d_new["args"] = args;
807 }
808 } else if (what == "value") {
809 Variant value = p_value;
810 if (value.get_type() == Variant::NODE_PATH) {
811 _fix_node_path(value, base_map[track]);
812 }
813
814 args.write[idx] = value;
815 d_new["args"] = args;
816 mergeable = true;
817 }
818 }
819
820 Variant prev = animation->track_get_key_value(track, key);
821
822 if (!setting) {
823 if (mergeable) {
824 undo_redo->create_action(TTR("Animation Multi Change Call"), UndoRedo::MERGE_ENDS);
825 } else {
826 undo_redo->create_action(TTR("Animation Multi Change Call"));
827 }
828
829 setting = true;
830 }
831
832 undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, d_new);
833 undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, d_old);
834 update_obj = true;
835 } break;
836 case Animation::TYPE_BEZIER: {
837 if (name == "value") {
838 const Variant &value = p_value;
839
840 if (!setting) {
841 setting = true;
842 undo_redo->create_action(TTR("Animation Multi Change Keyframe Value"), UndoRedo::MERGE_ENDS);
843 }
844 float prev = animation->bezier_track_get_key_value(track, key);
845 undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_value", track, key, value);
846 undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_value", track, key, prev);
847 update_obj = true;
848 } else if (name == "in_handle") {
849 const Variant &value = p_value;
850
851 if (!setting) {
852 setting = true;
853 undo_redo->create_action(TTR("Animation Multi Change Keyframe Value"), UndoRedo::MERGE_ENDS);
854 }
855 Vector2 prev = animation->bezier_track_get_key_in_handle(track, key);
856 undo_redo->add_do_method(this, "_bezier_track_set_key_in_handle", track, key, value);
857 undo_redo->add_undo_method(this, "_bezier_track_set_key_in_handle", track, key, prev);
858 update_obj = true;
859 } else if (name == "out_handle") {
860 const Variant &value = p_value;
861
862 if (!setting) {
863 setting = true;
864 undo_redo->create_action(TTR("Animation Multi Change Keyframe Value"), UndoRedo::MERGE_ENDS);
865 }
866 Vector2 prev = animation->bezier_track_get_key_out_handle(track, key);
867 undo_redo->add_do_method(this, "_bezier_track_set_key_out_handle", track, key, value);
868 undo_redo->add_undo_method(this, "_bezier_track_set_key_out_handle", track, key, prev);
869 update_obj = true;
870 } else if (name == "handle_mode") {
871 const Variant &value = p_value;
872
873 if (!setting) {
874 setting = true;
875 undo_redo->create_action(TTR("Animation Multi Change Keyframe Value"), UndoRedo::MERGE_ENDS);
876 }
877 int prev = animation->bezier_track_get_key_handle_mode(track, key);
878 undo_redo->add_do_method(this, "_bezier_track_set_key_handle_mode", animation.ptr(), track, key, value);
879 undo_redo->add_undo_method(this, "_bezier_track_set_key_handle_mode", animation.ptr(), track, key, prev);
880 update_obj = true;
881 }
882 } break;
883 case Animation::TYPE_AUDIO: {
884 if (name == "stream") {
885 Ref<AudioStream> stream = p_value;
886
887 if (!setting) {
888 setting = true;
889 undo_redo->create_action(TTR("Animation Multi Change Keyframe Value"), UndoRedo::MERGE_ENDS);
890 }
891 Ref<Resource> prev = animation->audio_track_get_key_stream(track, key);
892 undo_redo->add_do_method(animation.ptr(), "audio_track_set_key_stream", track, key, stream);
893 undo_redo->add_undo_method(animation.ptr(), "audio_track_set_key_stream", track, key, prev);
894 update_obj = true;
895 } else if (name == "start_offset") {
896 float value = p_value;
897
898 if (!setting) {
899 setting = true;
900 undo_redo->create_action(TTR("Animation Multi Change Keyframe Value"), UndoRedo::MERGE_ENDS);
901 }
902 float prev = animation->audio_track_get_key_start_offset(track, key);
903 undo_redo->add_do_method(animation.ptr(), "audio_track_set_key_start_offset", track, key, value);
904 undo_redo->add_undo_method(animation.ptr(), "audio_track_set_key_start_offset", track, key, prev);
905 update_obj = true;
906 } else if (name == "end_offset") {
907 float value = p_value;
908
909 if (!setting) {
910 setting = true;
911 undo_redo->create_action(TTR("Animation Multi Change Keyframe Value"), UndoRedo::MERGE_ENDS);
912 }
913 float prev = animation->audio_track_get_key_end_offset(track, key);
914 undo_redo->add_do_method(animation.ptr(), "audio_track_set_key_end_offset", track, key, value);
915 undo_redo->add_undo_method(animation.ptr(), "audio_track_set_key_end_offset", track, key, prev);
916 update_obj = true;
917 }
918 } break;
919 case Animation::TYPE_ANIMATION: {
920 if (name == "animation") {
921 StringName anim_name = p_value;
922
923 if (!setting) {
924 setting = true;
925 undo_redo->create_action(TTR("Animation Multi Change Keyframe Value"), UndoRedo::MERGE_ENDS);
926 }
927 StringName prev = animation->animation_track_get_key_animation(track, key);
928 undo_redo->add_do_method(animation.ptr(), "animation_track_set_key_animation", track, key, anim_name);
929 undo_redo->add_undo_method(animation.ptr(), "animation_track_set_key_animation", track, key, prev);
930 update_obj = true;
931 }
932 } break;
933 }
934 }
935 }
936
937 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
938 if (setting) {
939 if (update_obj) {
940 undo_redo->add_do_method(this, "_update_obj", animation);
941 undo_redo->add_undo_method(this, "_update_obj", animation);
942 }
943
944 undo_redo->commit_action();
945 setting = false;
946
947 if (change_notify_deserved) {
948 notify_change();
949 }
950
951 return true;
952 }
953
954 return false;
955}
956
957bool AnimationMultiTrackKeyEdit::_get(const StringName &p_name, Variant &r_ret) const {
958 for (const KeyValue<int, List<float>> &E : key_ofs_map) {
959 int track = E.key;
960 for (const float &key_ofs : E.value) {
961 int key = animation->track_find_key(track, key_ofs, Animation::FIND_MODE_APPROX);
962 ERR_CONTINUE(key == -1);
963
964 String name = p_name;
965 if (name == "easing") {
966 r_ret = animation->track_get_key_transition(track, key);
967 return true;
968 }
969
970 switch (animation->track_get_type(track)) {
971 case Animation::TYPE_POSITION_3D:
972 case Animation::TYPE_ROTATION_3D:
973 case Animation::TYPE_SCALE_3D: {
974 if (name == "position" || name == "rotation" || name == "scale") {
975 r_ret = animation->track_get_key_value(track, key);
976 return true;
977 }
978
979 } break;
980 case Animation::TYPE_BLEND_SHAPE:
981 case Animation::TYPE_VALUE: {
982 if (name == "value") {
983 r_ret = animation->track_get_key_value(track, key);
984 return true;
985 }
986
987 } break;
988 case Animation::TYPE_METHOD: {
989 Dictionary d = animation->track_get_key_value(track, key);
990
991 if (name == "name") {
992 ERR_FAIL_COND_V(!d.has("method"), false);
993 r_ret = d["method"];
994 return true;
995 }
996
997 ERR_FAIL_COND_V(!d.has("args"), false);
998
999 Vector<Variant> args = d["args"];
1000
1001 if (name == "arg_count") {
1002 r_ret = args.size();
1003 return true;
1004 }
1005
1006 if (name.begins_with("args/")) {
1007 int idx = name.get_slice("/", 1).to_int();
1008 ERR_FAIL_INDEX_V(idx, args.size(), false);
1009
1010 String what = name.get_slice("/", 2);
1011 if (what == "type") {
1012 r_ret = args[idx].get_type();
1013 return true;
1014 }
1015
1016 if (what == "value") {
1017 r_ret = args[idx];
1018 return true;
1019 }
1020 }
1021
1022 } break;
1023 case Animation::TYPE_BEZIER: {
1024 if (name == "value") {
1025 r_ret = animation->bezier_track_get_key_value(track, key);
1026 return true;
1027 }
1028
1029 if (name == "in_handle") {
1030 r_ret = animation->bezier_track_get_key_in_handle(track, key);
1031 return true;
1032 }
1033
1034 if (name == "out_handle") {
1035 r_ret = animation->bezier_track_get_key_out_handle(track, key);
1036 return true;
1037 }
1038
1039 if (name == "handle_mode") {
1040 r_ret = animation->bezier_track_get_key_handle_mode(track, key);
1041 return true;
1042 }
1043
1044 } break;
1045 case Animation::TYPE_AUDIO: {
1046 if (name == "stream") {
1047 r_ret = animation->audio_track_get_key_stream(track, key);
1048 return true;
1049 }
1050
1051 if (name == "start_offset") {
1052 r_ret = animation->audio_track_get_key_start_offset(track, key);
1053 return true;
1054 }
1055
1056 if (name == "end_offset") {
1057 r_ret = animation->audio_track_get_key_end_offset(track, key);
1058 return true;
1059 }
1060
1061 } break;
1062 case Animation::TYPE_ANIMATION: {
1063 if (name == "animation") {
1064 r_ret = animation->animation_track_get_key_animation(track, key);
1065 return true;
1066 }
1067
1068 } break;
1069 }
1070 }
1071 }
1072
1073 return false;
1074}
1075
1076void AnimationMultiTrackKeyEdit::_get_property_list(List<PropertyInfo> *p_list) const {
1077 if (animation.is_null()) {
1078 return;
1079 }
1080
1081 int first_track = -1;
1082 float first_key = -1.0;
1083
1084 bool same_track_type = true;
1085 bool same_key_type = true;
1086 for (const KeyValue<int, List<float>> &E : key_ofs_map) {
1087 int track = E.key;
1088 ERR_FAIL_INDEX(track, animation->get_track_count());
1089
1090 if (first_track < 0) {
1091 first_track = track;
1092 }
1093
1094 if (same_track_type) {
1095 if (animation->track_get_type(first_track) != animation->track_get_type(track)) {
1096 same_track_type = false;
1097 same_key_type = false;
1098 }
1099
1100 for (const float &F : E.value) {
1101 int key = animation->track_find_key(track, F, Animation::FIND_MODE_APPROX);
1102 ERR_FAIL_COND(key == -1);
1103 if (first_key < 0) {
1104 first_key = key;
1105 }
1106
1107 if (animation->track_get_key_value(first_track, first_key).get_type() != animation->track_get_key_value(track, key).get_type()) {
1108 same_key_type = false;
1109 }
1110 }
1111 }
1112 }
1113
1114 if (same_track_type) {
1115 switch (animation->track_get_type(first_track)) {
1116 case Animation::TYPE_POSITION_3D: {
1117 p_list->push_back(PropertyInfo(Variant::VECTOR3, "position"));
1118 } break;
1119 case Animation::TYPE_ROTATION_3D: {
1120 p_list->push_back(PropertyInfo(Variant::QUATERNION, "rotation"));
1121 } break;
1122 case Animation::TYPE_SCALE_3D: {
1123 p_list->push_back(PropertyInfo(Variant::VECTOR3, "scale"));
1124 } break;
1125 case Animation::TYPE_BLEND_SHAPE: {
1126 p_list->push_back(PropertyInfo(Variant::FLOAT, "value"));
1127 } break;
1128 case Animation::TYPE_VALUE: {
1129 if (same_key_type) {
1130 Variant v = animation->track_get_key_value(first_track, first_key);
1131
1132 if (hint.type != Variant::NIL) {
1133 PropertyInfo pi = hint;
1134 pi.name = "value";
1135 p_list->push_back(pi);
1136 } else {
1137 PropertyHint val_hint = PROPERTY_HINT_NONE;
1138 String val_hint_string;
1139
1140 if (v.get_type() == Variant::OBJECT) {
1141 // Could actually check the object property if exists..? Yes I will!
1142 Ref<Resource> res = v;
1143 if (res.is_valid()) {
1144 val_hint = PROPERTY_HINT_RESOURCE_TYPE;
1145 val_hint_string = res->get_class();
1146 }
1147 }
1148
1149 if (v.get_type() != Variant::NIL) {
1150 p_list->push_back(PropertyInfo(v.get_type(), "value", val_hint, val_hint_string));
1151 }
1152 }
1153 }
1154
1155 p_list->push_back(PropertyInfo(Variant::FLOAT, "easing", PROPERTY_HINT_EXP_EASING));
1156 } break;
1157 case Animation::TYPE_METHOD: {
1158 p_list->push_back(PropertyInfo(Variant::STRING_NAME, "name"));
1159
1160 p_list->push_back(PropertyInfo(Variant::INT, "arg_count", PROPERTY_HINT_RANGE, "0,32,1,or_greater"));
1161
1162 Dictionary d = animation->track_get_key_value(first_track, first_key);
1163 ERR_FAIL_COND(!d.has("args"));
1164 Vector<Variant> args = d["args"];
1165 String vtypes;
1166 for (int i = 0; i < Variant::VARIANT_MAX; i++) {
1167 if (i > 0) {
1168 vtypes += ",";
1169 }
1170 vtypes += Variant::get_type_name(Variant::Type(i));
1171 }
1172
1173 for (int i = 0; i < args.size(); i++) {
1174 p_list->push_back(PropertyInfo(Variant::INT, "args/" + itos(i) + "/type", PROPERTY_HINT_ENUM, vtypes));
1175 if (args[i].get_type() != Variant::NIL) {
1176 p_list->push_back(PropertyInfo(args[i].get_type(), "args/" + itos(i) + "/value"));
1177 }
1178 }
1179 } break;
1180 case Animation::TYPE_BEZIER: {
1181 p_list->push_back(PropertyInfo(Variant::FLOAT, "value"));
1182 p_list->push_back(PropertyInfo(Variant::VECTOR2, "in_handle"));
1183 p_list->push_back(PropertyInfo(Variant::VECTOR2, "out_handle"));
1184 p_list->push_back(PropertyInfo(Variant::INT, "handle_mode", PROPERTY_HINT_ENUM, "Free,Linear,Balanced,Mirrored"));
1185 } break;
1186 case Animation::TYPE_AUDIO: {
1187 p_list->push_back(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"));
1188 p_list->push_back(PropertyInfo(Variant::FLOAT, "start_offset", PROPERTY_HINT_RANGE, "0,3600,0.0001,or_greater"));
1189 p_list->push_back(PropertyInfo(Variant::FLOAT, "end_offset", PROPERTY_HINT_RANGE, "0,3600,0.0001,or_greater"));
1190 } break;
1191 case Animation::TYPE_ANIMATION: {
1192 if (key_ofs_map.size() > 1) {
1193 break;
1194 }
1195
1196 String animations;
1197
1198 if (root_path && root_path->has_node(animation->track_get_path(first_track))) {
1199 AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(root_path->get_node(animation->track_get_path(first_track)));
1200 if (ap) {
1201 List<StringName> anims;
1202 ap->get_animation_list(&anims);
1203 for (List<StringName>::Element *G = anims.front(); G; G = G->next()) {
1204 if (!animations.is_empty()) {
1205 animations += ",";
1206 }
1207
1208 animations += String(G->get());
1209 }
1210 }
1211 }
1212
1213 if (!animations.is_empty()) {
1214 animations += ",";
1215 }
1216 animations += "[stop]";
1217
1218 p_list->push_back(PropertyInfo(Variant::STRING_NAME, "animation", PROPERTY_HINT_ENUM, animations));
1219 } break;
1220 }
1221 }
1222}
1223
1224void AnimationMultiTrackKeyEdit::notify_change() {
1225 notify_property_list_changed();
1226}
1227
1228Node *AnimationMultiTrackKeyEdit::get_root_path() {
1229 return root_path;
1230}
1231
1232void AnimationMultiTrackKeyEdit::set_use_fps(bool p_enable) {
1233 use_fps = p_enable;
1234 notify_property_list_changed();
1235}
1236
1237void AnimationTimelineEdit::_zoom_changed(double) {
1238 queue_redraw();
1239 play_position->queue_redraw();
1240 emit_signal(SNAME("zoom_changed"));
1241}
1242
1243float AnimationTimelineEdit::get_zoom_scale() const {
1244 float zv = zoom->get_max() - zoom->get_value();
1245 if (zv < 1) {
1246 zv = 1.0 - zv;
1247 return Math::pow(1.0f + zv, 8.0f) * 100;
1248 } else {
1249 return 1.0 / Math::pow(zv, 8.0f) * 100;
1250 }
1251}
1252
1253void AnimationTimelineEdit::_anim_length_changed(double p_new_len) {
1254 if (editing) {
1255 return;
1256 }
1257
1258 p_new_len = MAX(0.0001, p_new_len);
1259 if (use_fps && animation->get_step() > 0) {
1260 p_new_len *= animation->get_step();
1261 }
1262
1263 editing = true;
1264 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
1265 undo_redo->create_action(TTR("Change Animation Length"), UndoRedo::MERGE_ENDS);
1266 undo_redo->add_do_method(animation.ptr(), "set_length", p_new_len);
1267 undo_redo->add_undo_method(animation.ptr(), "set_length", animation->get_length());
1268 undo_redo->commit_action();
1269 editing = false;
1270 queue_redraw();
1271
1272 emit_signal(SNAME("length_changed"), p_new_len);
1273}
1274
1275void AnimationTimelineEdit::_anim_loop_pressed() {
1276 if (!read_only) {
1277 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
1278 undo_redo->create_action(TTR("Change Animation Loop"));
1279 switch (animation->get_loop_mode()) {
1280 case Animation::LOOP_NONE: {
1281 undo_redo->add_do_method(animation.ptr(), "set_loop_mode", Animation::LOOP_LINEAR);
1282 } break;
1283 case Animation::LOOP_LINEAR: {
1284 undo_redo->add_do_method(animation.ptr(), "set_loop_mode", Animation::LOOP_PINGPONG);
1285 } break;
1286 case Animation::LOOP_PINGPONG: {
1287 undo_redo->add_do_method(animation.ptr(), "set_loop_mode", Animation::LOOP_NONE);
1288 } break;
1289 default:
1290 break;
1291 }
1292 undo_redo->add_do_method(this, "update_values");
1293 undo_redo->add_undo_method(animation.ptr(), "set_loop_mode", animation->get_loop_mode());
1294 undo_redo->add_undo_method(this, "update_values");
1295 undo_redo->commit_action();
1296 } else {
1297 String base_path = animation->get_path();
1298 if (FileAccess::exists(base_path + ".import")) {
1299 EditorNode::get_singleton()->show_warning(TTR("Can't change loop mode on animation instanced from imported scene."));
1300 } else {
1301 EditorNode::get_singleton()->show_warning(TTR("Can't change loop mode on animation embedded in another scene."));
1302 }
1303 update_values();
1304 }
1305}
1306
1307int AnimationTimelineEdit::get_buttons_width() const {
1308 Ref<Texture2D> interp_mode = get_editor_theme_icon(SNAME("TrackContinuous"));
1309 Ref<Texture2D> interp_type = get_editor_theme_icon(SNAME("InterpRaw"));
1310 Ref<Texture2D> loop_type = get_editor_theme_icon(SNAME("InterpWrapClamp"));
1311 Ref<Texture2D> remove_icon = get_editor_theme_icon(SNAME("Remove"));
1312 Ref<Texture2D> down_icon = get_theme_icon(SNAME("select_arrow"), SNAME("Tree"));
1313
1314 int total_w = interp_mode->get_width() + interp_type->get_width() + loop_type->get_width() + remove_icon->get_width();
1315 total_w += (down_icon->get_width() + 4 * EDSCALE) * 4;
1316
1317 return total_w;
1318}
1319
1320int AnimationTimelineEdit::get_name_limit() const {
1321 Ref<Texture2D> hsize_icon = get_editor_theme_icon(SNAME("Hsize"));
1322
1323 int limit = MAX(name_limit, add_track->get_minimum_size().width + hsize_icon->get_width());
1324
1325 limit = MIN(limit, get_size().width - get_buttons_width() - 1);
1326
1327 return limit;
1328}
1329
1330void AnimationTimelineEdit::_notification(int p_what) {
1331 switch (p_what) {
1332 case NOTIFICATION_ENTER_TREE:
1333 case NOTIFICATION_THEME_CHANGED: {
1334 panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/animation_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EDITOR_GET("editors/panning/simple_panning")));
1335 add_track->set_icon(get_editor_theme_icon(SNAME("Add")));
1336 loop->set_icon(get_editor_theme_icon(SNAME("Loop")));
1337 time_icon->set_texture(get_editor_theme_icon(SNAME("Time")));
1338
1339 add_track->get_popup()->clear();
1340 add_track->get_popup()->add_icon_item(get_editor_theme_icon(SNAME("KeyValue")), TTR("Property Track"));
1341 add_track->get_popup()->add_icon_item(get_editor_theme_icon(SNAME("KeyXPosition")), TTR("3D Position Track"));
1342 add_track->get_popup()->add_icon_item(get_editor_theme_icon(SNAME("KeyXRotation")), TTR("3D Rotation Track"));
1343 add_track->get_popup()->add_icon_item(get_editor_theme_icon(SNAME("KeyXScale")), TTR("3D Scale Track"));
1344 add_track->get_popup()->add_icon_item(get_editor_theme_icon(SNAME("KeyBlendShape")), TTR("Blend Shape Track"));
1345 add_track->get_popup()->add_icon_item(get_editor_theme_icon(SNAME("KeyCall")), TTR("Call Method Track"));
1346 add_track->get_popup()->add_icon_item(get_editor_theme_icon(SNAME("KeyBezier")), TTR("Bezier Curve Track"));
1347 add_track->get_popup()->add_icon_item(get_editor_theme_icon(SNAME("KeyAudio")), TTR("Audio Playback Track"));
1348 add_track->get_popup()->add_icon_item(get_editor_theme_icon(SNAME("KeyAnimation")), TTR("Animation Playback Track"));
1349 } break;
1350
1351 case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
1352 panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/animation_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EDITOR_GET("editors/panning/simple_panning")));
1353 } break;
1354
1355 case NOTIFICATION_RESIZED: {
1356 len_hb->set_position(Vector2(get_size().width - get_buttons_width(), 0));
1357 len_hb->set_size(Size2(get_buttons_width(), get_size().height));
1358 } break;
1359
1360 case NOTIFICATION_DRAW: {
1361 int key_range = get_size().width - get_buttons_width() - get_name_limit();
1362
1363 if (!animation.is_valid()) {
1364 return;
1365 }
1366
1367 Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
1368 int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
1369 Color color = get_theme_color(SNAME("font_color"), SNAME("Label"));
1370
1371 int zoomw = key_range;
1372 float scale = get_zoom_scale();
1373 int h = get_size().height;
1374
1375 float l = animation->get_length();
1376 if (l <= 0) {
1377 l = 0.0001; // Avoid crashor.
1378 }
1379
1380 Ref<Texture2D> hsize_icon = get_editor_theme_icon(SNAME("Hsize"));
1381 hsize_rect = Rect2(get_name_limit() - hsize_icon->get_width() - 2 * EDSCALE, (get_size().height - hsize_icon->get_height()) / 2, hsize_icon->get_width(), hsize_icon->get_height());
1382 draw_texture(hsize_icon, hsize_rect.position);
1383
1384 {
1385 float time_min = 0;
1386 float time_max = animation->get_length();
1387 for (int i = 0; i < animation->get_track_count(); i++) {
1388 if (animation->track_get_key_count(i) > 0) {
1389 float beg = animation->track_get_key_time(i, 0);
1390
1391 if (beg < time_min) {
1392 time_min = beg;
1393 }
1394
1395 float end = animation->track_get_key_time(i, animation->track_get_key_count(i) - 1);
1396
1397 if (end > time_max) {
1398 time_max = end;
1399 }
1400 }
1401 }
1402
1403 float extra = (zoomw / scale) * 0.5;
1404
1405 time_max += extra;
1406 set_min(time_min);
1407 set_max(time_max);
1408
1409 if (zoomw / scale < (time_max - time_min)) {
1410 hscroll->show();
1411
1412 } else {
1413 hscroll->hide();
1414 }
1415 }
1416
1417 set_page(zoomw / scale);
1418
1419 int end_px = (l - get_value()) * scale;
1420 int begin_px = -get_value() * scale;
1421 Color notimecol = get_theme_color(SNAME("dark_color_2"), EditorStringName(Editor));
1422 Color timecolor = color;
1423 timecolor.a = 0.2;
1424 Color linecolor = color;
1425 linecolor.a = 0.2;
1426
1427 {
1428 draw_rect(Rect2(Point2(get_name_limit(), 0), Point2(zoomw - 1, h)), notimecol);
1429
1430 if (begin_px < zoomw && end_px > 0) {
1431 if (begin_px < 0) {
1432 begin_px = 0;
1433 }
1434 if (end_px > zoomw) {
1435 end_px = zoomw;
1436 }
1437
1438 draw_rect(Rect2(Point2(get_name_limit() + begin_px, 0), Point2(end_px - begin_px, h)), timecolor);
1439 }
1440 }
1441
1442 Color color_time_sec = color;
1443 Color color_time_dec = color;
1444 color_time_dec.a *= 0.5;
1445#define SC_ADJ 100
1446 int dec = 1;
1447 int step = 1;
1448 int decimals = 2;
1449 bool step_found = false;
1450
1451 const float period_width = font->get_char_size('.', font_size).width;
1452 float max_digit_width = font->get_char_size('0', font_size).width;
1453 for (int i = 1; i <= 9; i++) {
1454 const float digit_width = font->get_char_size('0' + i, font_size).width;
1455 max_digit_width = MAX(digit_width, max_digit_width);
1456 }
1457 const int max_sc = int(Math::ceil(zoomw / scale));
1458 const int max_sc_width = String::num(max_sc).length() * max_digit_width;
1459
1460 while (!step_found) {
1461 int min = max_sc_width;
1462 if (decimals > 0) {
1463 min += period_width + max_digit_width * decimals;
1464 }
1465
1466 static const int _multp[3] = { 1, 2, 5 };
1467 for (int i = 0; i < 3; i++) {
1468 step = (_multp[i] * dec);
1469 if (step * scale / SC_ADJ > min) {
1470 step_found = true;
1471 break;
1472 }
1473 }
1474 if (step_found) {
1475 break;
1476 }
1477 dec *= 10;
1478 decimals--;
1479 if (decimals < 0) {
1480 decimals = 0;
1481 }
1482 }
1483
1484 if (use_fps) {
1485 float step_size = animation->get_step();
1486 if (step_size > 0) {
1487 int prev_frame_ofs = -10000000;
1488
1489 for (int i = 0; i < zoomw; i++) {
1490 float pos = get_value() + double(i) / scale;
1491 float prev = get_value() + (double(i) - 1.0) / scale;
1492
1493 int frame = pos / step_size;
1494 int prev_frame = prev / step_size;
1495
1496 bool sub = Math::floor(prev) == Math::floor(pos);
1497
1498 if (frame != prev_frame && i >= prev_frame_ofs) {
1499 draw_line(Point2(get_name_limit() + i, 0), Point2(get_name_limit() + i, h), linecolor, Math::round(EDSCALE));
1500
1501 draw_string(font, Point2(get_name_limit() + i + 3 * EDSCALE, (h - font->get_height(font_size)) / 2 + font->get_ascent(font_size)).floor(), itos(frame), HORIZONTAL_ALIGNMENT_LEFT, zoomw - i, font_size, sub ? color_time_dec : color_time_sec);
1502 prev_frame_ofs = i + font->get_string_size(itos(frame), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).x + 5 * EDSCALE;
1503 }
1504 }
1505 }
1506
1507 } else {
1508 for (int i = 0; i < zoomw; i++) {
1509 float pos = get_value() + double(i) / scale;
1510 float prev = get_value() + (double(i) - 1.0) / scale;
1511
1512 int sc = int(Math::floor(pos * SC_ADJ));
1513 int prev_sc = int(Math::floor(prev * SC_ADJ));
1514 bool sub = (sc % SC_ADJ);
1515
1516 if ((sc / step) != (prev_sc / step) || (prev_sc < 0 && sc >= 0)) {
1517 int scd = sc < 0 ? prev_sc : sc;
1518 draw_line(Point2(get_name_limit() + i, 0), Point2(get_name_limit() + i, h), linecolor, Math::round(EDSCALE));
1519 draw_string(font, Point2(get_name_limit() + i + 3, (h - font->get_height(font_size)) / 2 + font->get_ascent(font_size)).floor(), String::num((scd - (scd % step)) / double(SC_ADJ), decimals), HORIZONTAL_ALIGNMENT_LEFT, zoomw - i, font_size, sub ? color_time_dec : color_time_sec);
1520 }
1521 }
1522 }
1523
1524 draw_line(Vector2(0, get_size().height), get_size(), linecolor, Math::round(EDSCALE));
1525 update_values();
1526 } break;
1527 }
1528}
1529
1530void AnimationTimelineEdit::set_animation(const Ref<Animation> &p_animation, bool p_read_only) {
1531 animation = p_animation;
1532 read_only = p_read_only;
1533
1534 length->set_read_only(read_only);
1535
1536 if (animation.is_valid()) {
1537 len_hb->show();
1538 if (read_only) {
1539 add_track->hide();
1540 } else {
1541 add_track->show();
1542 }
1543 play_position->show();
1544 } else {
1545 len_hb->hide();
1546 add_track->hide();
1547 play_position->hide();
1548 }
1549 queue_redraw();
1550}
1551
1552Size2 AnimationTimelineEdit::get_minimum_size() const {
1553 Size2 ms = add_track->get_minimum_size();
1554 Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
1555 int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
1556 ms.height = MAX(ms.height, font->get_height(font_size));
1557 ms.width = get_buttons_width() + add_track->get_minimum_size().width + get_editor_theme_icon(SNAME("Hsize"))->get_width() + 2;
1558 return ms;
1559}
1560
1561void AnimationTimelineEdit::set_zoom(Range *p_zoom) {
1562 zoom = p_zoom;
1563 zoom->connect("value_changed", callable_mp(this, &AnimationTimelineEdit::_zoom_changed));
1564}
1565
1566void AnimationTimelineEdit::set_track_edit(AnimationTrackEdit *p_track_edit) {
1567 track_edit = p_track_edit;
1568}
1569
1570void AnimationTimelineEdit::set_play_position(float p_pos) {
1571 play_position_pos = p_pos;
1572 play_position->queue_redraw();
1573}
1574
1575float AnimationTimelineEdit::get_play_position() const {
1576 return play_position_pos;
1577}
1578
1579void AnimationTimelineEdit::update_play_position() {
1580 play_position->queue_redraw();
1581}
1582
1583void AnimationTimelineEdit::update_values() {
1584 if (!animation.is_valid() || editing) {
1585 return;
1586 }
1587
1588 editing = true;
1589 if (use_fps && animation->get_step() > 0) {
1590 length->set_value(animation->get_length() / animation->get_step());
1591 length->set_step(1);
1592 length->set_tooltip_text(TTR("Animation length (frames)"));
1593 time_icon->set_tooltip_text(TTR("Animation length (frames)"));
1594 if (track_edit) {
1595 track_edit->editor->_update_key_edit();
1596 }
1597 } else {
1598 length->set_value(animation->get_length());
1599 length->set_step(0.0001);
1600 length->set_tooltip_text(TTR("Animation length (seconds)"));
1601 time_icon->set_tooltip_text(TTR("Animation length (seconds)"));
1602 }
1603
1604 switch (animation->get_loop_mode()) {
1605 case Animation::LOOP_NONE: {
1606 loop->set_icon(get_editor_theme_icon(SNAME("Loop")));
1607 loop->set_pressed(false);
1608 } break;
1609 case Animation::LOOP_LINEAR: {
1610 loop->set_icon(get_editor_theme_icon(SNAME("Loop")));
1611 loop->set_pressed(true);
1612 } break;
1613 case Animation::LOOP_PINGPONG: {
1614 loop->set_icon(get_editor_theme_icon(SNAME("PingPongLoop")));
1615 loop->set_pressed(true);
1616 } break;
1617 default:
1618 break;
1619 }
1620
1621 editing = false;
1622}
1623
1624void AnimationTimelineEdit::_play_position_draw() {
1625 if (!animation.is_valid() || play_position_pos < 0) {
1626 return;
1627 }
1628
1629 float scale = get_zoom_scale();
1630 int h = play_position->get_size().height;
1631
1632 int px = (-get_value() + play_position_pos) * scale + get_name_limit();
1633
1634 if (px >= get_name_limit() && px < (play_position->get_size().width - get_buttons_width())) {
1635 Color color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
1636 play_position->draw_line(Point2(px, 0), Point2(px, h), color, Math::round(2 * EDSCALE));
1637 play_position->draw_texture(
1638 get_editor_theme_icon(SNAME("TimelineIndicator")),
1639 Point2(px - get_editor_theme_icon(SNAME("TimelineIndicator"))->get_width() * 0.5, 0),
1640 color);
1641 }
1642}
1643
1644void AnimationTimelineEdit::gui_input(const Ref<InputEvent> &p_event) {
1645 ERR_FAIL_COND(p_event.is_null());
1646
1647 if (panner->gui_input(p_event)) {
1648 accept_event();
1649 return;
1650 }
1651
1652 const Ref<InputEventMouseButton> mb = p_event;
1653
1654 if (mb.is_valid() && mb->is_pressed() && mb->is_alt_pressed() && mb->get_button_index() == MouseButton::WHEEL_UP) {
1655 if (track_edit) {
1656 track_edit->get_editor()->goto_prev_step(true);
1657 }
1658 accept_event();
1659 }
1660
1661 if (mb.is_valid() && mb->is_pressed() && mb->is_alt_pressed() && mb->get_button_index() == MouseButton::WHEEL_DOWN) {
1662 if (track_edit) {
1663 track_edit->get_editor()->goto_next_step(true);
1664 }
1665 accept_event();
1666 }
1667
1668 if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT && hsize_rect.has_point(mb->get_position())) {
1669 dragging_hsize = true;
1670 dragging_hsize_from = mb->get_position().x;
1671 dragging_hsize_at = name_limit;
1672 }
1673
1674 if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT && dragging_hsize) {
1675 dragging_hsize = false;
1676 }
1677 if (mb.is_valid() && mb->get_position().x > get_name_limit() && mb->get_position().x < (get_size().width - get_buttons_width())) {
1678 if (!panner->is_panning() && mb->get_button_index() == MouseButton::LEFT) {
1679 int x = mb->get_position().x - get_name_limit();
1680
1681 float ofs = x / get_zoom_scale() + get_value();
1682 emit_signal(SNAME("timeline_changed"), ofs, false, mb->is_alt_pressed());
1683 dragging_timeline = true;
1684 }
1685 }
1686
1687 if (dragging_timeline && mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && !mb->is_pressed()) {
1688 dragging_timeline = false;
1689 }
1690
1691 Ref<InputEventMouseMotion> mm = p_event;
1692
1693 if (mm.is_valid()) {
1694 if (dragging_hsize) {
1695 int ofs = mm->get_position().x - dragging_hsize_from;
1696 name_limit = dragging_hsize_at + ofs;
1697 queue_redraw();
1698 emit_signal(SNAME("name_limit_changed"));
1699 play_position->queue_redraw();
1700 }
1701 if (dragging_timeline) {
1702 int x = mm->get_position().x - get_name_limit();
1703 float ofs = x / get_zoom_scale() + get_value();
1704 emit_signal(SNAME("timeline_changed"), ofs, false, mm->is_alt_pressed());
1705 }
1706 }
1707}
1708
1709Control::CursorShape AnimationTimelineEdit::get_cursor_shape(const Point2 &p_pos) const {
1710 if (dragging_hsize || hsize_rect.has_point(p_pos)) {
1711 // Indicate that the track name column's width can be adjusted
1712 return Control::CURSOR_HSIZE;
1713 } else {
1714 return get_default_cursor_shape();
1715 }
1716}
1717
1718void AnimationTimelineEdit::_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event) {
1719 set_value(get_value() - p_scroll_vec.x / get_zoom_scale());
1720}
1721
1722void AnimationTimelineEdit::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event) {
1723 double current_zoom_value = get_zoom()->get_value();
1724 get_zoom()->set_value(MAX(0.01, current_zoom_value * p_zoom_factor));
1725}
1726
1727void AnimationTimelineEdit::set_use_fps(bool p_use_fps) {
1728 use_fps = p_use_fps;
1729 queue_redraw();
1730}
1731
1732bool AnimationTimelineEdit::is_using_fps() const {
1733 return use_fps;
1734}
1735
1736void AnimationTimelineEdit::set_hscroll(HScrollBar *p_hscroll) {
1737 hscroll = p_hscroll;
1738}
1739
1740void AnimationTimelineEdit::_track_added(int p_track) {
1741 emit_signal(SNAME("track_added"), p_track);
1742}
1743
1744void AnimationTimelineEdit::_bind_methods() {
1745 ADD_SIGNAL(MethodInfo("zoom_changed"));
1746 ADD_SIGNAL(MethodInfo("name_limit_changed"));
1747 ADD_SIGNAL(MethodInfo("timeline_changed", PropertyInfo(Variant::FLOAT, "position"), PropertyInfo(Variant::BOOL, "drag"), PropertyInfo(Variant::BOOL, "timeline_only")));
1748 ADD_SIGNAL(MethodInfo("track_added", PropertyInfo(Variant::INT, "track")));
1749 ADD_SIGNAL(MethodInfo("length_changed", PropertyInfo(Variant::FLOAT, "size")));
1750
1751 ClassDB::bind_method(D_METHOD("update_values"), &AnimationTimelineEdit::update_values);
1752}
1753
1754AnimationTimelineEdit::AnimationTimelineEdit() {
1755 name_limit = 150 * EDSCALE;
1756
1757 play_position = memnew(Control);
1758 play_position->set_mouse_filter(MOUSE_FILTER_PASS);
1759 add_child(play_position);
1760 play_position->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
1761 play_position->connect("draw", callable_mp(this, &AnimationTimelineEdit::_play_position_draw));
1762
1763 add_track = memnew(MenuButton);
1764 add_track->set_position(Vector2(0, 0));
1765 add_child(add_track);
1766 add_track->set_text(TTR("Add Track"));
1767
1768 len_hb = memnew(HBoxContainer);
1769
1770 Control *expander = memnew(Control);
1771 expander->set_h_size_flags(SIZE_EXPAND_FILL);
1772 len_hb->add_child(expander);
1773 time_icon = memnew(TextureRect);
1774 time_icon->set_v_size_flags(SIZE_SHRINK_CENTER);
1775 time_icon->set_tooltip_text(TTR("Animation length (seconds)"));
1776 len_hb->add_child(time_icon);
1777 length = memnew(EditorSpinSlider);
1778 length->set_min(0.0001);
1779 length->set_max(36000);
1780 length->set_step(0.0001);
1781 length->set_allow_greater(true);
1782 length->set_custom_minimum_size(Vector2(70 * EDSCALE, 0));
1783 length->set_hide_slider(true);
1784 length->set_tooltip_text(TTR("Animation length (seconds)"));
1785 length->connect("value_changed", callable_mp(this, &AnimationTimelineEdit::_anim_length_changed));
1786 len_hb->add_child(length);
1787 loop = memnew(Button);
1788 loop->set_flat(true);
1789 loop->set_tooltip_text(TTR("Animation Looping"));
1790 loop->connect("pressed", callable_mp(this, &AnimationTimelineEdit::_anim_loop_pressed));
1791 loop->set_toggle_mode(true);
1792 len_hb->add_child(loop);
1793 add_child(len_hb);
1794
1795 add_track->hide();
1796 add_track->get_popup()->connect("index_pressed", callable_mp(this, &AnimationTimelineEdit::_track_added));
1797 len_hb->hide();
1798
1799 panner.instantiate();
1800 panner->set_callbacks(callable_mp(this, &AnimationTimelineEdit::_pan_callback), callable_mp(this, &AnimationTimelineEdit::_zoom_callback));
1801 panner->set_pan_axis(ViewPanner::PAN_AXIS_HORIZONTAL);
1802
1803 set_layout_direction(Control::LAYOUT_DIRECTION_LTR);
1804}
1805
1806////////////////////////////////////
1807
1808void AnimationTrackEdit::_notification(int p_what) {
1809 switch (p_what) {
1810 case NOTIFICATION_THEME_CHANGED: {
1811 if (animation.is_null()) {
1812 return;
1813 }
1814 ERR_FAIL_INDEX(track, animation->get_track_count());
1815
1816 type_icon = _get_key_type_icon();
1817 selected_icon = get_editor_theme_icon(SNAME("KeySelected"));
1818 } break;
1819
1820 case NOTIFICATION_DRAW: {
1821 if (animation.is_null()) {
1822 return;
1823 }
1824 ERR_FAIL_INDEX(track, animation->get_track_count());
1825
1826 int limit = timeline->get_name_limit();
1827
1828 if (track % 2 == 1) {
1829 // Draw a background over odd lines to make long lists of tracks easier to read.
1830 draw_rect(Rect2(Point2(1 * EDSCALE, 0), get_size() - Size2(1 * EDSCALE, 0)), Color(0.5, 0.5, 0.5, 0.05));
1831 }
1832
1833 if (hovered) {
1834 // Draw hover feedback.
1835 draw_rect(Rect2(Point2(1 * EDSCALE, 0), get_size() - Size2(1 * EDSCALE, 0)), Color(0.5, 0.5, 0.5, 0.1));
1836 }
1837
1838 if (has_focus()) {
1839 Color accent = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
1840 accent.a *= 0.7;
1841 // Offside so the horizontal sides aren't cutoff.
1842 draw_style_box(get_theme_stylebox(SNAME("Focus"), EditorStringName(EditorStyles)), Rect2(Point2(1 * EDSCALE, 0), get_size() - Size2(1 * EDSCALE, 0)));
1843 }
1844
1845 Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
1846 int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
1847 Color color = get_theme_color(SNAME("font_color"), SNAME("Label"));
1848 int hsep = get_theme_constant(SNAME("h_separation"), SNAME("ItemList"));
1849 Color linecolor = color;
1850 linecolor.a = 0.2;
1851
1852 Color dc = get_theme_color(SNAME("disabled_font_color"), EditorStringName(Editor));
1853
1854 // NAMES AND ICONS //
1855
1856 {
1857 Ref<Texture2D> check = animation->track_is_enabled(track) ? get_theme_icon(SNAME("checked"), SNAME("CheckBox")) : get_theme_icon(SNAME("unchecked"), SNAME("CheckBox"));
1858
1859 int ofs = in_group ? check->get_width() : 0; // Not the best reference for margin but..
1860
1861 check_rect = Rect2(Point2(ofs, int(get_size().height - check->get_height()) / 2), check->get_size());
1862 draw_texture(check, check_rect.position);
1863 ofs += check->get_width() + hsep;
1864
1865 Ref<Texture2D> key_type_icon = _get_key_type_icon();
1866 draw_texture(key_type_icon, Point2(ofs, int(get_size().height - key_type_icon->get_height()) / 2));
1867 ofs += key_type_icon->get_width() + hsep;
1868
1869 NodePath anim_path = animation->track_get_path(track);
1870 Node *node = nullptr;
1871 if (root && root->has_node(anim_path)) {
1872 node = root->get_node(anim_path);
1873 }
1874
1875 String text;
1876 Color text_color = color;
1877 if (node && EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) {
1878 text_color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
1879 }
1880
1881 if (in_group) {
1882 if (animation->track_get_type(track) == Animation::TYPE_METHOD) {
1883 text = TTR("Functions:");
1884 } else if (animation->track_get_type(track) == Animation::TYPE_AUDIO) {
1885 text = TTR("Audio Clips:");
1886 } else if (animation->track_get_type(track) == Animation::TYPE_ANIMATION) {
1887 text = TTR("Animation Clips:");
1888 } else {
1889 text += anim_path.get_concatenated_subnames();
1890 }
1891 text_color.a *= 0.7;
1892 } else if (node) {
1893 Ref<Texture2D> icon = EditorNode::get_singleton()->get_object_icon(node, "Node");
1894
1895 draw_texture(icon, Point2(ofs, int(get_size().height - icon->get_height()) / 2));
1896 icon_cache = icon;
1897
1898 text = String() + node->get_name() + ":" + anim_path.get_concatenated_subnames();
1899 ofs += hsep;
1900 ofs += icon->get_width();
1901
1902 } else {
1903 icon_cache = key_type_icon;
1904
1905 text = anim_path;
1906 }
1907
1908 path_cache = text;
1909
1910 path_rect = Rect2(ofs, 0, limit - ofs - hsep, get_size().height);
1911
1912 Vector2 string_pos = Point2(ofs, (get_size().height - font->get_height(font_size)) / 2 + font->get_ascent(font_size));
1913 string_pos = string_pos.floor();
1914 draw_string(font, string_pos, text, HORIZONTAL_ALIGNMENT_LEFT, limit - ofs - hsep, font_size, text_color);
1915
1916 draw_line(Point2(limit, 0), Point2(limit, get_size().height), linecolor, Math::round(EDSCALE));
1917 }
1918
1919 // KEYFRAMES //
1920
1921 draw_bg(limit, get_size().width - timeline->get_buttons_width());
1922
1923 {
1924 float scale = timeline->get_zoom_scale();
1925 int limit_end = get_size().width - timeline->get_buttons_width();
1926
1927 for (int i = 0; i < animation->track_get_key_count(track); i++) {
1928 float offset = animation->track_get_key_time(track, i) - timeline->get_value();
1929 if (editor->is_key_selected(track, i) && editor->is_moving_selection()) {
1930 offset = editor->snap_time(offset + editor->get_moving_selection_offset(), true);
1931 }
1932 offset = offset * scale + limit;
1933 if (i < animation->track_get_key_count(track) - 1) {
1934 float offset_n = animation->track_get_key_time(track, i + 1) - timeline->get_value();
1935 if (editor->is_key_selected(track, i + 1) && editor->is_moving_selection()) {
1936 offset_n = editor->snap_time(offset_n + editor->get_moving_selection_offset());
1937 }
1938 offset_n = offset_n * scale + limit;
1939
1940 draw_key_link(i, scale, int(offset), int(offset_n), limit, limit_end);
1941 }
1942
1943 draw_key(i, scale, int(offset), editor->is_key_selected(track, i), limit, limit_end);
1944 }
1945 }
1946
1947 draw_fg(limit, get_size().width - timeline->get_buttons_width());
1948
1949 // BUTTONS //
1950
1951 {
1952 Ref<Texture2D> wrap_icon[2] = {
1953 get_editor_theme_icon(SNAME("InterpWrapClamp")),
1954 get_editor_theme_icon(SNAME("InterpWrapLoop")),
1955 };
1956 Ref<Texture2D> interp_icon[5] = {
1957 get_editor_theme_icon(SNAME("InterpRaw")),
1958 get_editor_theme_icon(SNAME("InterpLinear")),
1959 get_editor_theme_icon(SNAME("InterpCubic")),
1960 get_editor_theme_icon(SNAME("InterpLinearAngle")),
1961 get_editor_theme_icon(SNAME("InterpCubicAngle")),
1962 };
1963 Ref<Texture2D> cont_icon[3] = {
1964 get_editor_theme_icon(SNAME("TrackContinuous")),
1965 get_editor_theme_icon(SNAME("TrackDiscrete")),
1966 get_editor_theme_icon(SNAME("TrackCapture"))
1967 };
1968 Ref<Texture2D> blend_icon[2] = {
1969 get_editor_theme_icon(SNAME("UseBlendEnable")),
1970 get_editor_theme_icon(SNAME("UseBlendDisable")),
1971 };
1972
1973 int ofs = get_size().width - timeline->get_buttons_width();
1974
1975 Ref<Texture2D> down_icon = get_theme_icon(SNAME("select_arrow"), SNAME("Tree"));
1976
1977 draw_line(Point2(ofs, 0), Point2(ofs, get_size().height), linecolor, Math::round(EDSCALE));
1978
1979 ofs += hsep;
1980 {
1981 // Callmode.
1982
1983 Animation::UpdateMode update_mode;
1984
1985 if (animation->track_get_type(track) == Animation::TYPE_VALUE) {
1986 update_mode = animation->value_track_get_update_mode(track);
1987 } else {
1988 update_mode = Animation::UPDATE_CONTINUOUS;
1989 }
1990
1991 Ref<Texture2D> update_icon = cont_icon[update_mode];
1992
1993 update_mode_rect.position.x = ofs;
1994 update_mode_rect.position.y = int(get_size().height - update_icon->get_height()) / 2;
1995 update_mode_rect.size = update_icon->get_size();
1996
1997 if (!animation->track_is_compressed(track) && animation->track_get_type(track) == Animation::TYPE_VALUE) {
1998 draw_texture(update_icon, update_mode_rect.position);
1999 }
2000 if (animation->track_get_type(track) == Animation::TYPE_AUDIO) {
2001 Ref<Texture2D> use_blend_icon = blend_icon[animation->audio_track_is_use_blend(track) ? 0 : 1];
2002 Vector2 use_blend_icon_pos = update_mode_rect.position + (update_mode_rect.size - use_blend_icon->get_size()) / 2;
2003 draw_texture(use_blend_icon, use_blend_icon_pos);
2004 }
2005 // Make it easier to click.
2006 update_mode_rect.position.y = 0;
2007 update_mode_rect.size.y = get_size().height;
2008
2009 ofs += update_icon->get_width() + hsep / 2;
2010 update_mode_rect.size.x += hsep / 2;
2011
2012 if (!read_only) {
2013 if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_AUDIO) {
2014 draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2));
2015 update_mode_rect.size.x += down_icon->get_width();
2016 } else if (animation->track_get_type(track) == Animation::TYPE_BEZIER) {
2017 update_mode_rect.size.x += down_icon->get_width();
2018 update_mode_rect = Rect2();
2019 } else {
2020 update_mode_rect = Rect2();
2021 }
2022 } else {
2023 update_mode_rect = Rect2();
2024 }
2025
2026 ofs += down_icon->get_width();
2027 draw_line(Point2(ofs + hsep * 0.5, 0), Point2(ofs + hsep * 0.5, get_size().height), linecolor, Math::round(EDSCALE));
2028 ofs += hsep;
2029 }
2030
2031 {
2032 // Interp.
2033
2034 Animation::InterpolationType interp_mode = animation->track_get_interpolation_type(track);
2035
2036 Ref<Texture2D> icon = interp_icon[interp_mode];
2037
2038 interp_mode_rect.position.x = ofs;
2039 interp_mode_rect.position.y = int(get_size().height - icon->get_height()) / 2;
2040 interp_mode_rect.size = icon->get_size();
2041
2042 if (!animation->track_is_compressed(track) && (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_BLEND_SHAPE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D)) {
2043 draw_texture(icon, interp_mode_rect.position);
2044 }
2045 // Make it easier to click.
2046 interp_mode_rect.position.y = 0;
2047 interp_mode_rect.size.y = get_size().height;
2048
2049 ofs += icon->get_width() + hsep / 2;
2050 interp_mode_rect.size.x += hsep / 2;
2051
2052 if (!read_only && !animation->track_is_compressed(track) && (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_BLEND_SHAPE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D)) {
2053 draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2));
2054 interp_mode_rect.size.x += down_icon->get_width();
2055 } else {
2056 interp_mode_rect = Rect2();
2057 }
2058
2059 ofs += down_icon->get_width();
2060 draw_line(Point2(ofs + hsep * 0.5, 0), Point2(ofs + hsep * 0.5, get_size().height), linecolor, Math::round(EDSCALE));
2061 ofs += hsep;
2062 }
2063
2064 {
2065 // Loop.
2066
2067 bool loop_wrap = animation->track_get_interpolation_loop_wrap(track);
2068
2069 Ref<Texture2D> icon = wrap_icon[loop_wrap ? 1 : 0];
2070
2071 loop_wrap_rect.position.x = ofs;
2072 loop_wrap_rect.position.y = int(get_size().height - icon->get_height()) / 2;
2073 loop_wrap_rect.size = icon->get_size();
2074
2075 if (!animation->track_is_compressed(track) && (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_BLEND_SHAPE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D)) {
2076 draw_texture(icon, loop_wrap_rect.position);
2077 }
2078
2079 loop_wrap_rect.position.y = 0;
2080 loop_wrap_rect.size.y = get_size().height;
2081
2082 ofs += icon->get_width() + hsep / 2;
2083 loop_wrap_rect.size.x += hsep / 2;
2084
2085 if (!read_only && !animation->track_is_compressed(track) && (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_BLEND_SHAPE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D)) {
2086 draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2));
2087 loop_wrap_rect.size.x += down_icon->get_width();
2088 } else {
2089 loop_wrap_rect = Rect2();
2090 }
2091
2092 ofs += down_icon->get_width();
2093 draw_line(Point2(ofs + hsep * 0.5, 0), Point2(ofs + hsep * 0.5, get_size().height), linecolor, Math::round(EDSCALE));
2094 ofs += hsep;
2095 }
2096
2097 {
2098 // Erase.
2099
2100 Ref<Texture2D> icon = get_editor_theme_icon(animation->track_is_compressed(track) ? SNAME("Lock") : SNAME("Remove"));
2101
2102 remove_rect.position.x = ofs + ((get_size().width - ofs) - icon->get_width());
2103 remove_rect.position.y = int(get_size().height - icon->get_height()) / 2;
2104 remove_rect.size = icon->get_size();
2105
2106 if (read_only) {
2107 draw_texture(icon, remove_rect.position, dc);
2108 } else {
2109 draw_texture(icon, remove_rect.position);
2110 }
2111 }
2112 }
2113
2114 if (in_group) {
2115 draw_line(Vector2(timeline->get_name_limit(), get_size().height), get_size(), linecolor, Math::round(EDSCALE));
2116 } else {
2117 draw_line(Vector2(0, get_size().height), get_size(), linecolor, Math::round(EDSCALE));
2118 }
2119
2120 if (dropping_at != 0) {
2121 Color drop_color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
2122 if (dropping_at < 0) {
2123 draw_line(Vector2(0, 0), Vector2(get_size().width, 0), drop_color, Math::round(EDSCALE));
2124 } else {
2125 draw_line(Vector2(0, get_size().height), get_size(), drop_color, Math::round(EDSCALE));
2126 }
2127 }
2128 } break;
2129
2130 case NOTIFICATION_MOUSE_ENTER:
2131 hovered = true;
2132 queue_redraw();
2133 break;
2134 case NOTIFICATION_MOUSE_EXIT:
2135 hovered = false;
2136 // When the mouse cursor exits the track, we're no longer hovering any keyframe.
2137 hovering_key_idx = -1;
2138 queue_redraw();
2139 [[fallthrough]];
2140 case NOTIFICATION_DRAG_END: {
2141 cancel_drop();
2142 } break;
2143 }
2144}
2145
2146int AnimationTrackEdit::get_key_height() const {
2147 if (!animation.is_valid()) {
2148 return 0;
2149 }
2150
2151 return type_icon->get_height();
2152}
2153
2154Rect2 AnimationTrackEdit::get_key_rect(int p_index, float p_pixels_sec) {
2155 if (!animation.is_valid()) {
2156 return Rect2();
2157 }
2158 Rect2 rect = Rect2(-type_icon->get_width() / 2, 0, type_icon->get_width(), get_size().height);
2159
2160 // Make it a big easier to click.
2161 rect.position.x -= rect.size.x * 0.5;
2162 rect.size.x *= 2;
2163 return rect;
2164}
2165
2166bool AnimationTrackEdit::is_key_selectable_by_distance() const {
2167 return true;
2168}
2169
2170void AnimationTrackEdit::draw_key_link(int p_index, float p_pixels_sec, int p_x, int p_next_x, int p_clip_left, int p_clip_right) {
2171 if (p_next_x < p_clip_left) {
2172 return;
2173 }
2174 if (p_x > p_clip_right) {
2175 return;
2176 }
2177
2178 Variant current = animation->track_get_key_value(get_track(), p_index);
2179 Variant next = animation->track_get_key_value(get_track(), p_index + 1);
2180 if (current != next || animation->track_get_type(get_track()) == Animation::TrackType::TYPE_METHOD) {
2181 return;
2182 }
2183
2184 Color color = get_theme_color(SNAME("font_color"), SNAME("Label"));
2185 color.a = 0.5;
2186
2187 int from_x = MAX(p_x, p_clip_left);
2188 int to_x = MIN(p_next_x, p_clip_right);
2189
2190 draw_line(Point2(from_x + 1, get_size().height / 2), Point2(to_x, get_size().height / 2), color, Math::round(2 * EDSCALE));
2191}
2192
2193void AnimationTrackEdit::draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) {
2194 if (!animation.is_valid()) {
2195 return;
2196 }
2197
2198 if (p_x < p_clip_left || p_x > p_clip_right) {
2199 return;
2200 }
2201
2202 Ref<Texture2D> icon_to_draw = p_selected ? selected_icon : type_icon;
2203
2204 if (animation->track_get_type(track) == Animation::TYPE_VALUE && !Math::is_equal_approx(animation->track_get_key_transition(track, p_index), real_t(1.0))) {
2205 // Use a different icon for keys with non-linear easing.
2206 icon_to_draw = get_editor_theme_icon(p_selected ? SNAME("KeyEasedSelected") : SNAME("KeyValueEased"));
2207 }
2208
2209 // Override type icon for invalid value keys, unless selected.
2210 if (!p_selected && animation->track_get_type(track) == Animation::TYPE_VALUE) {
2211 const Variant &v = animation->track_get_key_value(track, p_index);
2212 Variant::Type valid_type = Variant::NIL;
2213 if (!_is_value_key_valid(v, valid_type)) {
2214 icon_to_draw = get_editor_theme_icon(SNAME("KeyInvalid"));
2215 }
2216 }
2217
2218 Vector2 ofs(p_x - icon_to_draw->get_width() / 2, int(get_size().height - icon_to_draw->get_height()) / 2);
2219
2220 if (animation->track_get_type(track) == Animation::TYPE_METHOD) {
2221 Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
2222 int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
2223 Color color = get_theme_color(SNAME("font_color"), SNAME("Label"));
2224 color.a = 0.5;
2225
2226 Dictionary d = animation->track_get_key_value(track, p_index);
2227 String text;
2228
2229 if (d.has("method")) {
2230 text += String(d["method"]);
2231 }
2232 text += "(";
2233 Vector<Variant> args;
2234 if (d.has("args")) {
2235 args = d["args"];
2236 }
2237 for (int i = 0; i < args.size(); i++) {
2238 if (i > 0) {
2239 text += ", ";
2240 }
2241 text += args[i].get_construct_string();
2242 }
2243 text += ")";
2244
2245 int limit = MAX(0, p_clip_right - p_x - icon_to_draw->get_width());
2246 if (limit > 0) {
2247 draw_string(font, Vector2(p_x + icon_to_draw->get_width(), int(get_size().height - font->get_height(font_size)) / 2 + font->get_ascent(font_size)), text, HORIZONTAL_ALIGNMENT_LEFT, limit, font_size, color);
2248 }
2249 }
2250
2251 // Use a different color for the currently hovered key.
2252 // The color multiplier is chosen to work with both dark and light editor themes,
2253 // and on both unselected and selected key icons.
2254 draw_texture(
2255 icon_to_draw,
2256 ofs,
2257 p_index == hovering_key_idx ? get_theme_color(SNAME("folder_icon_color"), SNAME("FileDialog")) : Color(1, 1, 1));
2258}
2259
2260// Helper.
2261void AnimationTrackEdit::draw_rect_clipped(const Rect2 &p_rect, const Color &p_color, bool p_filled) {
2262 int clip_left = timeline->get_name_limit();
2263 int clip_right = get_size().width - timeline->get_buttons_width();
2264
2265 if (p_rect.position.x > clip_right) {
2266 return;
2267 }
2268 if (p_rect.position.x + p_rect.size.x < clip_left) {
2269 return;
2270 }
2271 Rect2 clip = Rect2(clip_left, 0, clip_right - clip_left, get_size().height);
2272 draw_rect(clip.intersection(p_rect), p_color, p_filled);
2273}
2274
2275void AnimationTrackEdit::draw_bg(int p_clip_left, int p_clip_right) {
2276}
2277
2278void AnimationTrackEdit::draw_fg(int p_clip_left, int p_clip_right) {
2279}
2280
2281void AnimationTrackEdit::draw_texture_region_clipped(const Ref<Texture2D> &p_texture, const Rect2 &p_rect, const Rect2 &p_region) {
2282 int clip_left = timeline->get_name_limit();
2283 int clip_right = get_size().width - timeline->get_buttons_width();
2284
2285 // Clip left and right.
2286 if (clip_left > p_rect.position.x + p_rect.size.x) {
2287 return;
2288 }
2289 if (clip_right < p_rect.position.x) {
2290 return;
2291 }
2292
2293 Rect2 rect = p_rect;
2294 Rect2 region = p_region;
2295
2296 if (clip_left > rect.position.x) {
2297 int rect_pixels = (clip_left - rect.position.x);
2298 int region_pixels = rect_pixels * region.size.x / rect.size.x;
2299
2300 rect.position.x += rect_pixels;
2301 rect.size.x -= rect_pixels;
2302
2303 region.position.x += region_pixels;
2304 region.size.x -= region_pixels;
2305 }
2306
2307 if (clip_right < rect.position.x + rect.size.x) {
2308 int rect_pixels = rect.position.x + rect.size.x - clip_right;
2309 int region_pixels = rect_pixels * region.size.x / rect.size.x;
2310
2311 rect.size.x -= rect_pixels;
2312 region.size.x -= region_pixels;
2313 }
2314
2315 draw_texture_rect_region(p_texture, rect, region);
2316}
2317
2318int AnimationTrackEdit::get_track() const {
2319 return track;
2320}
2321
2322Ref<Animation> AnimationTrackEdit::get_animation() const {
2323 return animation;
2324}
2325
2326void AnimationTrackEdit::set_animation_and_track(const Ref<Animation> &p_animation, int p_track, bool p_read_only) {
2327 animation = p_animation;
2328 read_only = p_read_only;
2329
2330 track = p_track;
2331 queue_redraw();
2332
2333 ERR_FAIL_INDEX(track, animation->get_track_count());
2334
2335 node_path = animation->track_get_path(p_track);
2336 type_icon = _get_key_type_icon();
2337 selected_icon = get_editor_theme_icon(SNAME("KeySelected"));
2338}
2339
2340NodePath AnimationTrackEdit::get_path() const {
2341 return node_path;
2342}
2343
2344Size2 AnimationTrackEdit::get_minimum_size() const {
2345 Ref<Texture2D> texture = get_editor_theme_icon(SNAME("Object"));
2346 Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
2347 int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
2348 int separation = get_theme_constant(SNAME("v_separation"), SNAME("ItemList"));
2349
2350 int max_h = MAX(texture->get_height(), font->get_height(font_size));
2351 max_h = MAX(max_h, get_key_height());
2352
2353 return Vector2(1, max_h + separation);
2354}
2355
2356void AnimationTrackEdit::set_timeline(AnimationTimelineEdit *p_timeline) {
2357 timeline = p_timeline;
2358 timeline->set_track_edit(this);
2359 timeline->connect("zoom_changed", callable_mp(this, &AnimationTrackEdit::_zoom_changed));
2360 timeline->connect("name_limit_changed", callable_mp(this, &AnimationTrackEdit::_zoom_changed));
2361}
2362
2363void AnimationTrackEdit::set_editor(AnimationTrackEditor *p_editor) {
2364 editor = p_editor;
2365}
2366
2367void AnimationTrackEdit::_play_position_draw() {
2368 if (!animation.is_valid() || play_position_pos < 0) {
2369 return;
2370 }
2371
2372 float scale = timeline->get_zoom_scale();
2373 int h = get_size().height;
2374
2375 int px = (-timeline->get_value() + play_position_pos) * scale + timeline->get_name_limit();
2376
2377 if (px >= timeline->get_name_limit() && px < (get_size().width - timeline->get_buttons_width())) {
2378 Color color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
2379 play_position->draw_line(Point2(px, 0), Point2(px, h), color, Math::round(2 * EDSCALE));
2380 }
2381}
2382
2383void AnimationTrackEdit::set_play_position(float p_pos) {
2384 play_position_pos = p_pos;
2385 play_position->queue_redraw();
2386}
2387
2388void AnimationTrackEdit::update_play_position() {
2389 play_position->queue_redraw();
2390}
2391
2392void AnimationTrackEdit::set_root(Node *p_root) {
2393 root = p_root;
2394}
2395
2396void AnimationTrackEdit::_zoom_changed() {
2397 queue_redraw();
2398 play_position->queue_redraw();
2399}
2400
2401void AnimationTrackEdit::_path_submitted(const String &p_text) {
2402 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
2403 undo_redo->create_action(TTR("Change Track Path"));
2404 undo_redo->add_do_method(animation.ptr(), "track_set_path", track, p_text);
2405 undo_redo->add_undo_method(animation.ptr(), "track_set_path", track, animation->track_get_path(track));
2406 undo_redo->commit_action();
2407 path_popup->hide();
2408}
2409
2410bool AnimationTrackEdit::_is_value_key_valid(const Variant &p_key_value, Variant::Type &r_valid_type) const {
2411 if (root == nullptr) {
2412 return false;
2413 }
2414
2415 Ref<Resource> res;
2416 Vector<StringName> leftover_path;
2417 Node *node = root->get_node_and_resource(animation->track_get_path(track), res, leftover_path);
2418
2419 Object *obj = nullptr;
2420 if (res.is_valid()) {
2421 obj = res.ptr();
2422 } else if (node) {
2423 obj = node;
2424 }
2425
2426 bool prop_exists = false;
2427 if (obj) {
2428 r_valid_type = obj->get_static_property_type_indexed(leftover_path, &prop_exists);
2429 }
2430
2431 return (!prop_exists || Variant::can_convert(p_key_value.get_type(), r_valid_type));
2432}
2433
2434Ref<Texture2D> AnimationTrackEdit::_get_key_type_icon() const {
2435 const Ref<Texture2D> type_icons[9] = {
2436 get_editor_theme_icon(SNAME("KeyValue")),
2437 get_editor_theme_icon(SNAME("KeyTrackPosition")),
2438 get_editor_theme_icon(SNAME("KeyTrackRotation")),
2439 get_editor_theme_icon(SNAME("KeyTrackScale")),
2440 get_editor_theme_icon(SNAME("KeyTrackBlendShape")),
2441 get_editor_theme_icon(SNAME("KeyCall")),
2442 get_editor_theme_icon(SNAME("KeyBezier")),
2443 get_editor_theme_icon(SNAME("KeyAudio")),
2444 get_editor_theme_icon(SNAME("KeyAnimation"))
2445 };
2446 return type_icons[animation->track_get_type(track)];
2447}
2448
2449String AnimationTrackEdit::get_tooltip(const Point2 &p_pos) const {
2450 if (check_rect.has_point(p_pos)) {
2451 return TTR("Toggle this track on/off.");
2452 }
2453
2454 // Don't overlap track keys if they start at 0.
2455 if (path_rect.has_point(p_pos + Size2(type_icon->get_width(), 0))) {
2456 return animation->track_get_path(track);
2457 }
2458
2459 if (update_mode_rect.has_point(p_pos)) {
2460 if (animation->track_get_type(track) == Animation::TYPE_AUDIO) {
2461 return TTR("Use Blend");
2462 } else {
2463 return TTR("Update Mode (How this property is set)");
2464 }
2465 }
2466
2467 if (interp_mode_rect.has_point(p_pos)) {
2468 return TTR("Interpolation Mode");
2469 }
2470
2471 if (loop_wrap_rect.has_point(p_pos)) {
2472 return TTR("Loop Wrap Mode (Interpolate end with beginning on loop)");
2473 }
2474
2475 if (remove_rect.has_point(p_pos)) {
2476 return TTR("Remove this track.");
2477 }
2478
2479 int limit = timeline->get_name_limit();
2480 int limit_end = get_size().width - timeline->get_buttons_width();
2481 // Left Border including space occupied by keyframes on t=0.
2482 int limit_start_hitbox = limit - type_icon->get_width();
2483
2484 if (p_pos.x >= limit_start_hitbox && p_pos.x <= limit_end) {
2485 int key_idx = -1;
2486 float key_distance = 1e20;
2487
2488 // Select should happen in the opposite order of drawing for more accurate overlap select.
2489 for (int i = animation->track_get_key_count(track) - 1; i >= 0; i--) {
2490 Rect2 rect = const_cast<AnimationTrackEdit *>(this)->get_key_rect(i, timeline->get_zoom_scale());
2491 float offset = animation->track_get_key_time(track, i) - timeline->get_value();
2492 offset = offset * timeline->get_zoom_scale() + limit;
2493 rect.position.x += offset;
2494
2495 if (rect.has_point(p_pos)) {
2496 if (const_cast<AnimationTrackEdit *>(this)->is_key_selectable_by_distance()) {
2497 float distance = ABS(offset - p_pos.x);
2498 if (key_idx == -1 || distance < key_distance) {
2499 key_idx = i;
2500 key_distance = distance;
2501 }
2502 } else {
2503 // First one does it.
2504 break;
2505 }
2506 }
2507 }
2508
2509 if (key_idx != -1) {
2510 String text = TTR("Time (s):") + " " + TS->format_number(rtos(Math::snapped(animation->track_get_key_time(track, key_idx), 0.0001))) + "\n";
2511 switch (animation->track_get_type(track)) {
2512 case Animation::TYPE_POSITION_3D: {
2513 Vector3 t = animation->track_get_key_value(track, key_idx);
2514 text += TTR("Position:") + " " + String(t) + "\n";
2515 } break;
2516 case Animation::TYPE_ROTATION_3D: {
2517 Quaternion t = animation->track_get_key_value(track, key_idx);
2518 text += TTR("Rotation:") + " " + String(t) + "\n";
2519 } break;
2520 case Animation::TYPE_SCALE_3D: {
2521 Vector3 t = animation->track_get_key_value(track, key_idx);
2522 text += TTR("Scale:") + " " + String(t) + "\n";
2523 } break;
2524 case Animation::TYPE_BLEND_SHAPE: {
2525 float t = animation->track_get_key_value(track, key_idx);
2526 text += TTR("Blend Shape:") + " " + itos(t) + "\n";
2527 } break;
2528 case Animation::TYPE_VALUE: {
2529 const Variant &v = animation->track_get_key_value(track, key_idx);
2530 text += TTR("Type:") + " " + Variant::get_type_name(v.get_type()) + "\n";
2531 Variant::Type valid_type = Variant::NIL;
2532 text += TTR("Value:") + " " + String(v);
2533 if (!_is_value_key_valid(v, valid_type)) {
2534 text += " " + vformat(TTR("(Invalid, expected type: %s)"), Variant::get_type_name(valid_type));
2535 }
2536 text += "\n" + TTR("Easing:") + " " + rtos(animation->track_get_key_transition(track, key_idx));
2537
2538 } break;
2539 case Animation::TYPE_METHOD: {
2540 Dictionary d = animation->track_get_key_value(track, key_idx);
2541 if (d.has("method")) {
2542 text += String(d["method"]);
2543 }
2544 text += "(";
2545 Vector<Variant> args;
2546 if (d.has("args")) {
2547 args = d["args"];
2548 }
2549 for (int i = 0; i < args.size(); i++) {
2550 if (i > 0) {
2551 text += ", ";
2552 }
2553 text += args[i].get_construct_string();
2554 }
2555 text += ")\n";
2556
2557 } break;
2558 case Animation::TYPE_BEZIER: {
2559 float h = animation->bezier_track_get_key_value(track, key_idx);
2560 text += TTR("Value:") + " " + rtos(h) + "\n";
2561 Vector2 ih = animation->bezier_track_get_key_in_handle(track, key_idx);
2562 text += TTR("In-Handle:") + " " + ih + "\n";
2563 Vector2 oh = animation->bezier_track_get_key_out_handle(track, key_idx);
2564 text += TTR("Out-Handle:") + " " + oh + "\n";
2565 int hm = animation->bezier_track_get_key_handle_mode(track, key_idx);
2566 switch (hm) {
2567 case Animation::HANDLE_MODE_FREE: {
2568 text += TTR("Handle mode: Free\n");
2569 } break;
2570 case Animation::HANDLE_MODE_LINEAR: {
2571 text += TTR("Handle mode: Linear\n");
2572 } break;
2573 case Animation::HANDLE_MODE_BALANCED: {
2574 text += TTR("Handle mode: Balanced\n");
2575 } break;
2576 case Animation::HANDLE_MODE_MIRRORED: {
2577 text += TTR("Handle mode: Mirrored\n");
2578 } break;
2579 }
2580 } break;
2581 case Animation::TYPE_AUDIO: {
2582 String stream_name = "null";
2583 Ref<Resource> stream = animation->audio_track_get_key_stream(track, key_idx);
2584 if (stream.is_valid()) {
2585 if (stream->get_path().is_resource_file()) {
2586 stream_name = stream->get_path().get_file();
2587 } else if (!stream->get_name().is_empty()) {
2588 stream_name = stream->get_name();
2589 } else {
2590 stream_name = stream->get_class();
2591 }
2592 }
2593
2594 text += TTR("Stream:") + " " + stream_name + "\n";
2595 float so = animation->audio_track_get_key_start_offset(track, key_idx);
2596 text += TTR("Start (s):") + " " + rtos(so) + "\n";
2597 float eo = animation->audio_track_get_key_end_offset(track, key_idx);
2598 text += TTR("End (s):") + " " + rtos(eo) + "\n";
2599 } break;
2600 case Animation::TYPE_ANIMATION: {
2601 String name = animation->animation_track_get_key_animation(track, key_idx);
2602 text += TTR("Animation Clip:") + " " + name + "\n";
2603 } break;
2604 }
2605 return text;
2606 }
2607 }
2608
2609 return Control::get_tooltip(p_pos);
2610}
2611
2612void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
2613 ERR_FAIL_COND(p_event.is_null());
2614
2615 if (p_event->is_pressed()) {
2616 if (ED_GET_SHORTCUT("animation_editor/duplicate_selection")->matches_event(p_event)) {
2617 if (!read_only) {
2618 emit_signal(SNAME("duplicate_request"));
2619 }
2620 accept_event();
2621 }
2622
2623 if (ED_GET_SHORTCUT("animation_editor/duplicate_selection_transposed")->matches_event(p_event)) {
2624 if (!read_only) {
2625 emit_signal(SNAME("duplicate_transpose_request"));
2626 }
2627 accept_event();
2628 }
2629
2630 if (ED_GET_SHORTCUT("animation_editor/delete_selection")->matches_event(p_event)) {
2631 if (!read_only) {
2632 emit_signal(SNAME("delete_request"));
2633 }
2634 accept_event();
2635 }
2636 }
2637
2638 Ref<InputEventMouseButton> mb = p_event;
2639 if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
2640 Point2 pos = mb->get_position();
2641
2642 if (!read_only) {
2643 if (check_rect.has_point(pos)) {
2644 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
2645 undo_redo->create_action(TTR("Toggle Track Enabled"));
2646 undo_redo->add_do_method(animation.ptr(), "track_set_enabled", track, !animation->track_is_enabled(track));
2647 undo_redo->add_undo_method(animation.ptr(), "track_set_enabled", track, animation->track_is_enabled(track));
2648 undo_redo->commit_action();
2649 queue_redraw();
2650 accept_event();
2651 }
2652
2653 // Don't overlap track keys if they start at 0.
2654 if (path_rect.has_point(pos + Size2(type_icon->get_width(), 0))) {
2655 clicking_on_name = true;
2656 accept_event();
2657 }
2658
2659 if (update_mode_rect.has_point(pos)) {
2660 if (!menu) {
2661 menu = memnew(PopupMenu);
2662 add_child(menu);
2663 menu->connect("id_pressed", callable_mp(this, &AnimationTrackEdit::_menu_selected));
2664 }
2665 menu->clear();
2666 if (animation->track_get_type(track) == Animation::TYPE_AUDIO) {
2667 menu->add_icon_item(get_editor_theme_icon(SNAME("UseBlendEnable")), TTR("Use Blend"), MENU_USE_BLEND_ENABLED);
2668 menu->add_icon_item(get_editor_theme_icon(SNAME("UseBlendDisable")), TTR("Don't Use Blend"), MENU_USE_BLEND_DISABLED);
2669 } else {
2670 menu->add_icon_item(get_editor_theme_icon(SNAME("TrackContinuous")), TTR("Continuous"), MENU_CALL_MODE_CONTINUOUS);
2671 menu->add_icon_item(get_editor_theme_icon(SNAME("TrackDiscrete")), TTR("Discrete"), MENU_CALL_MODE_DISCRETE);
2672 menu->add_icon_item(get_editor_theme_icon(SNAME("TrackCapture")), TTR("Capture"), MENU_CALL_MODE_CAPTURE);
2673 }
2674 menu->reset_size();
2675
2676 Vector2 popup_pos = get_screen_position() + update_mode_rect.position + Vector2(0, update_mode_rect.size.height);
2677 menu->set_position(popup_pos);
2678 menu->popup();
2679 accept_event();
2680 }
2681
2682 if (interp_mode_rect.has_point(pos)) {
2683 if (!menu) {
2684 menu = memnew(PopupMenu);
2685 add_child(menu);
2686 menu->connect("id_pressed", callable_mp(this, &AnimationTrackEdit::_menu_selected));
2687 }
2688 menu->clear();
2689 menu->add_icon_item(get_editor_theme_icon(SNAME("InterpRaw")), TTR("Nearest"), MENU_INTERPOLATION_NEAREST);
2690 menu->add_icon_item(get_editor_theme_icon(SNAME("InterpLinear")), TTR("Linear"), MENU_INTERPOLATION_LINEAR);
2691 menu->add_icon_item(get_editor_theme_icon(SNAME("InterpCubic")), TTR("Cubic"), MENU_INTERPOLATION_CUBIC);
2692 // Check whether it is angle property.
2693 AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
2694 if (ape) {
2695 AnimationPlayer *ap = ape->get_player();
2696 if (ap) {
2697 NodePath npath = animation->track_get_path(track);
2698 Node *nd = ap->get_node(ap->get_root())->get_node(NodePath(npath.get_concatenated_names()));
2699 StringName prop = npath.get_concatenated_subnames();
2700 PropertyInfo prop_info;
2701 ClassDB::get_property_info(nd->get_class(), prop, &prop_info);
2702 bool is_angle = prop_info.type == Variant::FLOAT && prop_info.hint_string.find("radians") != -1;
2703 if (is_angle) {
2704 menu->add_icon_item(get_editor_theme_icon(SNAME("InterpLinearAngle")), TTR("Linear Angle"), MENU_INTERPOLATION_LINEAR_ANGLE);
2705 menu->add_icon_item(get_editor_theme_icon(SNAME("InterpCubicAngle")), TTR("Cubic Angle"), MENU_INTERPOLATION_CUBIC_ANGLE);
2706 }
2707 }
2708 }
2709 menu->reset_size();
2710
2711 Vector2 popup_pos = get_screen_position() + interp_mode_rect.position + Vector2(0, interp_mode_rect.size.height);
2712 menu->set_position(popup_pos);
2713 menu->popup();
2714 accept_event();
2715 }
2716
2717 if (loop_wrap_rect.has_point(pos)) {
2718 if (!menu) {
2719 menu = memnew(PopupMenu);
2720 add_child(menu);
2721 menu->connect("id_pressed", callable_mp(this, &AnimationTrackEdit::_menu_selected));
2722 }
2723 menu->clear();
2724 menu->add_icon_item(get_editor_theme_icon(SNAME("InterpWrapClamp")), TTR("Clamp Loop Interp"), MENU_LOOP_CLAMP);
2725 menu->add_icon_item(get_editor_theme_icon(SNAME("InterpWrapLoop")), TTR("Wrap Loop Interp"), MENU_LOOP_WRAP);
2726 menu->reset_size();
2727
2728 Vector2 popup_pos = get_screen_position() + loop_wrap_rect.position + Vector2(0, loop_wrap_rect.size.height);
2729 menu->set_position(popup_pos);
2730 menu->popup();
2731 accept_event();
2732 }
2733
2734 if (remove_rect.has_point(pos)) {
2735 emit_signal(SNAME("remove_request"), track);
2736 accept_event();
2737 return;
2738 }
2739 }
2740
2741 // Check keyframes.
2742
2743 if (!animation->track_is_compressed(track)) { // Selecting compressed keyframes for editing is not possible.
2744
2745 float scale = timeline->get_zoom_scale();
2746 int limit = timeline->get_name_limit();
2747 int limit_end = get_size().width - timeline->get_buttons_width();
2748 // Left Border including space occupied by keyframes on t=0.
2749 int limit_start_hitbox = limit - type_icon->get_width();
2750
2751 if (pos.x >= limit_start_hitbox && pos.x <= limit_end) {
2752 int key_idx = -1;
2753 float key_distance = 1e20;
2754
2755 // Select should happen in the opposite order of drawing for more accurate overlap select.
2756 for (int i = animation->track_get_key_count(track) - 1; i >= 0; i--) {
2757 Rect2 rect = get_key_rect(i, scale);
2758 float offset = animation->track_get_key_time(track, i) - timeline->get_value();
2759 offset = offset * scale + limit;
2760 rect.position.x += offset;
2761
2762 if (rect.has_point(pos)) {
2763 if (is_key_selectable_by_distance()) {
2764 float distance = ABS(offset - pos.x);
2765 if (key_idx == -1 || distance < key_distance) {
2766 key_idx = i;
2767 key_distance = distance;
2768 }
2769 } else {
2770 // First one does it.
2771 key_idx = i;
2772 break;
2773 }
2774 }
2775 }
2776
2777 if (key_idx != -1) {
2778 if (mb->is_command_or_control_pressed() || mb->is_shift_pressed()) {
2779 if (editor->is_key_selected(track, key_idx)) {
2780 emit_signal(SNAME("deselect_key"), key_idx);
2781 } else {
2782 emit_signal(SNAME("select_key"), key_idx, false);
2783 moving_selection_attempt = true;
2784 select_single_attempt = -1;
2785 moving_selection_from_ofs = (mb->get_position().x - limit) / timeline->get_zoom_scale();
2786 }
2787 } else {
2788 if (!editor->is_key_selected(track, key_idx)) {
2789 emit_signal(SNAME("select_key"), key_idx, true);
2790 select_single_attempt = -1;
2791 } else {
2792 select_single_attempt = key_idx;
2793 }
2794
2795 moving_selection_attempt = true;
2796 moving_selection_from_ofs = (mb->get_position().x - limit) / timeline->get_zoom_scale();
2797 }
2798
2799 if (read_only) {
2800 moving_selection_attempt = false;
2801 moving_selection_from_ofs = 0.0f;
2802 }
2803 accept_event();
2804 }
2805 }
2806 }
2807 }
2808
2809 if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) {
2810 Point2 pos = mb->get_position();
2811 if (pos.x >= timeline->get_name_limit() && pos.x <= get_size().width - timeline->get_buttons_width()) {
2812 // Can do something with menu too! show insert key.
2813 float offset = (pos.x - timeline->get_name_limit()) / timeline->get_zoom_scale();
2814 if (!read_only) {
2815 if (!menu) {
2816 menu = memnew(PopupMenu);
2817 add_child(menu);
2818 menu->connect("id_pressed", callable_mp(this, &AnimationTrackEdit::_menu_selected));
2819 }
2820
2821 menu->clear();
2822 menu->add_icon_item(get_editor_theme_icon(SNAME("Key")), TTR("Insert Key"), MENU_KEY_INSERT);
2823 if (editor->is_selection_active()) {
2824 menu->add_separator();
2825 menu->add_icon_item(get_editor_theme_icon(SNAME("Duplicate")), TTR("Duplicate Key(s)"), MENU_KEY_DUPLICATE);
2826
2827 AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();
2828 if (!player->has_animation(SceneStringNames::get_singleton()->RESET) || animation != player->get_animation(SceneStringNames::get_singleton()->RESET)) {
2829 menu->add_icon_item(get_editor_theme_icon(SNAME("Reload")), TTR("Add RESET Value(s)"), MENU_KEY_ADD_RESET);
2830 }
2831
2832 menu->add_separator();
2833 menu->add_icon_item(get_editor_theme_icon(SNAME("Remove")), TTR("Delete Key(s)"), MENU_KEY_DELETE);
2834 }
2835 menu->reset_size();
2836
2837 menu->set_position(get_screen_position() + get_local_mouse_position());
2838 menu->popup();
2839
2840 insert_at_pos = offset + timeline->get_value();
2841 accept_event();
2842 }
2843 }
2844 }
2845
2846 if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT && clicking_on_name) {
2847 if (!path) {
2848 path_popup = memnew(Popup);
2849 path_popup->set_wrap_controls(true);
2850 add_child(path_popup);
2851 path = memnew(LineEdit);
2852 path_popup->add_child(path);
2853 path->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
2854 path->connect("text_submitted", callable_mp(this, &AnimationTrackEdit::_path_submitted));
2855 }
2856
2857 path->set_text(animation->track_get_path(track));
2858 Vector2 theme_ofs = path->get_theme_stylebox(SNAME("normal"), SNAME("LineEdit"))->get_offset();
2859 path_popup->set_position(get_screen_position() + path_rect.position - theme_ofs);
2860 path_popup->set_size(path_rect.size);
2861 path_popup->popup();
2862 path->grab_focus();
2863 path->set_caret_column(path->get_text().length());
2864 clicking_on_name = false;
2865 }
2866
2867 if (mb.is_valid() && moving_selection_attempt) {
2868 if (!mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
2869 moving_selection_attempt = false;
2870 if (moving_selection) {
2871 emit_signal(SNAME("move_selection_commit"));
2872 } else if (select_single_attempt != -1) {
2873 emit_signal(SNAME("select_key"), select_single_attempt, true);
2874 }
2875 moving_selection = false;
2876 select_single_attempt = -1;
2877 }
2878
2879 if (moving_selection && mb->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) {
2880 moving_selection_attempt = false;
2881 moving_selection = false;
2882 emit_signal(SNAME("move_selection_cancel"));
2883 }
2884 }
2885
2886 Ref<InputEventMouseMotion> mm = p_event;
2887 if (mm.is_valid()) {
2888 const int previous_hovering_key_idx = hovering_key_idx;
2889
2890 // Hovering compressed keyframes for editing is not possible.
2891 if (!animation->track_is_compressed(track)) {
2892 const float scale = timeline->get_zoom_scale();
2893 const int limit = timeline->get_name_limit();
2894 const int limit_end = get_size().width - timeline->get_buttons_width();
2895 // Left Border including space occupied by keyframes on t=0.
2896 const int limit_start_hitbox = limit - type_icon->get_width();
2897 const Point2 pos = mm->get_position();
2898
2899 if (pos.x >= limit_start_hitbox && pos.x <= limit_end) {
2900 // Use the same logic as key selection to ensure that hovering accurately represents
2901 // which key will be selected when clicking.
2902 int key_idx = -1;
2903 float key_distance = 1e20;
2904
2905 hovering_key_idx = -1;
2906
2907 // Hovering should happen in the opposite order of drawing for more accurate overlap hovering.
2908 for (int i = animation->track_get_key_count(track) - 1; i >= 0; i--) {
2909 Rect2 rect = get_key_rect(i, scale);
2910 float offset = animation->track_get_key_time(track, i) - timeline->get_value();
2911 offset = offset * scale + limit;
2912 rect.position.x += offset;
2913
2914 if (rect.has_point(pos)) {
2915 if (is_key_selectable_by_distance()) {
2916 const float distance = ABS(offset - pos.x);
2917 if (key_idx == -1 || distance < key_distance) {
2918 key_idx = i;
2919 key_distance = distance;
2920 hovering_key_idx = i;
2921 }
2922 } else {
2923 // First one does it.
2924 hovering_key_idx = i;
2925 break;
2926 }
2927 }
2928 }
2929
2930 if (hovering_key_idx != previous_hovering_key_idx) {
2931 // Required to draw keyframe hover feedback on the correct keyframe.
2932 queue_redraw();
2933 }
2934 }
2935 }
2936 }
2937
2938 if (mm.is_valid() && mm->get_button_mask().has_flag(MouseButtonMask::LEFT) && moving_selection_attempt) {
2939 if (!moving_selection) {
2940 moving_selection = true;
2941 emit_signal(SNAME("move_selection_begin"));
2942 }
2943
2944 float new_ofs = (mm->get_position().x - timeline->get_name_limit()) / timeline->get_zoom_scale();
2945 emit_signal(SNAME("move_selection"), new_ofs - moving_selection_from_ofs);
2946 }
2947}
2948
2949Variant AnimationTrackEdit::get_drag_data(const Point2 &p_point) {
2950 if (!clicking_on_name) {
2951 return Variant();
2952 }
2953
2954 Dictionary drag_data;
2955 drag_data["type"] = "animation_track";
2956 String base_path = animation->track_get_path(track);
2957 base_path = base_path.get_slice(":", 0); // Remove sub-path.
2958 drag_data["group"] = base_path;
2959 drag_data["index"] = track;
2960
2961 Button *tb = memnew(Button);
2962 tb->set_flat(true);
2963 tb->set_text(path_cache);
2964 tb->set_icon(icon_cache);
2965 set_drag_preview(tb);
2966
2967 clicking_on_name = false;
2968
2969 return drag_data;
2970}
2971
2972bool AnimationTrackEdit::can_drop_data(const Point2 &p_point, const Variant &p_data) const {
2973 Dictionary d = p_data;
2974 if (!d.has("type")) {
2975 return false;
2976 }
2977
2978 String type = d["type"];
2979 if (type != "animation_track") {
2980 return false;
2981 }
2982
2983 // Don't allow moving tracks outside their groups.
2984 if (get_editor()->is_grouping_tracks()) {
2985 String base_path = animation->track_get_path(track);
2986 base_path = base_path.get_slice(":", 0); // Remove sub-path.
2987 if (d["group"] != base_path) {
2988 return false;
2989 }
2990 }
2991
2992 if (p_point.y < get_size().height / 2) {
2993 dropping_at = -1;
2994 } else {
2995 dropping_at = 1;
2996 }
2997
2998 const_cast<AnimationTrackEdit *>(this)->queue_redraw();
2999
3000 return true;
3001}
3002
3003void AnimationTrackEdit::drop_data(const Point2 &p_point, const Variant &p_data) {
3004 Dictionary d = p_data;
3005 if (!d.has("type")) {
3006 return;
3007 }
3008
3009 String type = d["type"];
3010 if (type != "animation_track") {
3011 return;
3012 }
3013
3014 // Don't allow moving tracks outside their groups.
3015 if (get_editor()->is_grouping_tracks()) {
3016 String base_path = animation->track_get_path(track);
3017 base_path = base_path.get_slice(":", 0); // Remove sub-path.
3018 if (d["group"] != base_path) {
3019 return;
3020 }
3021 }
3022
3023 int from_track = d["index"];
3024
3025 if (dropping_at < 0) {
3026 emit_signal(SNAME("dropped"), from_track, track);
3027 } else {
3028 emit_signal(SNAME("dropped"), from_track, track + 1);
3029 }
3030}
3031
3032void AnimationTrackEdit::_menu_selected(int p_index) {
3033 switch (p_index) {
3034 case MENU_CALL_MODE_CONTINUOUS:
3035 case MENU_CALL_MODE_DISCRETE:
3036 case MENU_CALL_MODE_CAPTURE: {
3037 Animation::UpdateMode update_mode = Animation::UpdateMode(p_index);
3038 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
3039 undo_redo->create_action(TTR("Change Animation Update Mode"));
3040 undo_redo->add_do_method(animation.ptr(), "value_track_set_update_mode", track, update_mode);
3041 undo_redo->add_undo_method(animation.ptr(), "value_track_set_update_mode", track, animation->value_track_get_update_mode(track));
3042 undo_redo->commit_action();
3043 queue_redraw();
3044
3045 } break;
3046 case MENU_INTERPOLATION_NEAREST:
3047 case MENU_INTERPOLATION_LINEAR:
3048 case MENU_INTERPOLATION_CUBIC:
3049 case MENU_INTERPOLATION_LINEAR_ANGLE:
3050 case MENU_INTERPOLATION_CUBIC_ANGLE: {
3051 Animation::InterpolationType interp_mode = Animation::InterpolationType(p_index - MENU_INTERPOLATION_NEAREST);
3052 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
3053 undo_redo->create_action(TTR("Change Animation Interpolation Mode"));
3054 undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_type", track, interp_mode);
3055 undo_redo->add_undo_method(animation.ptr(), "track_set_interpolation_type", track, animation->track_get_interpolation_type(track));
3056 undo_redo->commit_action();
3057 queue_redraw();
3058 } break;
3059 case MENU_LOOP_WRAP:
3060 case MENU_LOOP_CLAMP: {
3061 bool loop_wrap = p_index == MENU_LOOP_WRAP;
3062 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
3063 undo_redo->create_action(TTR("Change Animation Loop Mode"));
3064 undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_loop_wrap", track, loop_wrap);
3065 undo_redo->add_undo_method(animation.ptr(), "track_set_interpolation_loop_wrap", track, animation->track_get_interpolation_loop_wrap(track));
3066 undo_redo->commit_action();
3067 queue_redraw();
3068
3069 } break;
3070 case MENU_KEY_INSERT: {
3071 emit_signal(SNAME("insert_key"), insert_at_pos);
3072 } break;
3073 case MENU_KEY_DUPLICATE: {
3074 emit_signal(SNAME("duplicate_request"));
3075 } break;
3076 case MENU_KEY_ADD_RESET: {
3077 emit_signal(SNAME("create_reset_request"));
3078
3079 } break;
3080 case MENU_KEY_DELETE: {
3081 emit_signal(SNAME("delete_request"));
3082
3083 } break;
3084 case MENU_USE_BLEND_ENABLED:
3085 case MENU_USE_BLEND_DISABLED: {
3086 bool use_blend = p_index == MENU_USE_BLEND_ENABLED;
3087 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
3088 undo_redo->create_action(TTR("Change Animation Use Blend"));
3089 undo_redo->add_do_method(animation.ptr(), "audio_track_set_use_blend", track, use_blend);
3090 undo_redo->add_undo_method(animation.ptr(), "audio_track_set_use_blend", track, animation->audio_track_is_use_blend(track));
3091 undo_redo->commit_action();
3092 queue_redraw();
3093 } break;
3094 }
3095}
3096
3097void AnimationTrackEdit::cancel_drop() {
3098 if (dropping_at != 0) {
3099 dropping_at = 0;
3100 queue_redraw();
3101 }
3102}
3103
3104void AnimationTrackEdit::set_in_group(bool p_enable) {
3105 in_group = p_enable;
3106 queue_redraw();
3107}
3108
3109void AnimationTrackEdit::append_to_selection(const Rect2 &p_box, bool p_deselection) {
3110 if (animation->track_is_compressed(track)) {
3111 return; // Compressed keyframes can't be edited
3112 }
3113 // Left Border including space occupied by keyframes on t=0.
3114 int limit_start_hitbox = timeline->get_name_limit() - type_icon->get_width();
3115 Rect2 select_rect(limit_start_hitbox, 0, get_size().width - timeline->get_name_limit() - timeline->get_buttons_width(), get_size().height);
3116 select_rect = select_rect.intersection(p_box);
3117
3118 // Select should happen in the opposite order of drawing for more accurate overlap select.
3119 for (int i = animation->track_get_key_count(track) - 1; i >= 0; i--) {
3120 Rect2 rect = const_cast<AnimationTrackEdit *>(this)->get_key_rect(i, timeline->get_zoom_scale());
3121 float offset = animation->track_get_key_time(track, i) - timeline->get_value();
3122 offset = offset * timeline->get_zoom_scale() + timeline->get_name_limit();
3123 rect.position.x += offset;
3124
3125 if (select_rect.intersects(rect)) {
3126 if (p_deselection) {
3127 emit_signal(SNAME("deselect_key"), i);
3128 } else {
3129 emit_signal(SNAME("select_key"), i, false);
3130 }
3131 }
3132 }
3133}
3134
3135void AnimationTrackEdit::_bind_methods() {
3136 ADD_SIGNAL(MethodInfo("timeline_changed", PropertyInfo(Variant::FLOAT, "position"), PropertyInfo(Variant::BOOL, "drag"), PropertyInfo(Variant::BOOL, "timeline_only")));
3137 ADD_SIGNAL(MethodInfo("remove_request", PropertyInfo(Variant::INT, "track")));
3138 ADD_SIGNAL(MethodInfo("dropped", PropertyInfo(Variant::INT, "from_track"), PropertyInfo(Variant::INT, "to_track")));
3139 ADD_SIGNAL(MethodInfo("insert_key", PropertyInfo(Variant::FLOAT, "offset")));
3140 ADD_SIGNAL(MethodInfo("select_key", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::BOOL, "single")));
3141 ADD_SIGNAL(MethodInfo("deselect_key", PropertyInfo(Variant::INT, "index")));
3142
3143 ADD_SIGNAL(MethodInfo("move_selection_begin"));
3144 ADD_SIGNAL(MethodInfo("move_selection", PropertyInfo(Variant::FLOAT, "offset")));
3145 ADD_SIGNAL(MethodInfo("move_selection_commit"));
3146 ADD_SIGNAL(MethodInfo("move_selection_cancel"));
3147
3148 ADD_SIGNAL(MethodInfo("duplicate_request"));
3149 ADD_SIGNAL(MethodInfo("create_reset_request"));
3150 ADD_SIGNAL(MethodInfo("duplicate_transpose_request"));
3151 ADD_SIGNAL(MethodInfo("delete_request"));
3152}
3153
3154AnimationTrackEdit::AnimationTrackEdit() {
3155 play_position = memnew(Control);
3156 play_position->set_mouse_filter(MOUSE_FILTER_PASS);
3157 add_child(play_position);
3158 play_position->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
3159 play_position->connect("draw", callable_mp(this, &AnimationTrackEdit::_play_position_draw));
3160 set_focus_mode(FOCUS_CLICK);
3161 set_mouse_filter(MOUSE_FILTER_PASS); // Scroll has to work too for selection.
3162}
3163
3164//////////////////////////////////////
3165
3166AnimationTrackEdit *AnimationTrackEditPlugin::create_value_track_edit(Object *p_object, Variant::Type p_type, const String &p_property, PropertyHint p_hint, const String &p_hint_string, int p_usage) {
3167 if (get_script_instance()) {
3168 Variant args[6] = {
3169 p_object,
3170 p_type,
3171 p_property,
3172 p_hint,
3173 p_hint_string,
3174 p_usage
3175 };
3176
3177 Variant *argptrs[6] = {
3178 &args[0],
3179 &args[1],
3180 &args[2],
3181 &args[3],
3182 &args[4],
3183 &args[5]
3184 };
3185
3186 Callable::CallError ce;
3187 return Object::cast_to<AnimationTrackEdit>(get_script_instance()->callp("create_value_track_edit", (const Variant **)&argptrs, 6, ce).operator Object *());
3188 }
3189 return nullptr;
3190}
3191
3192AnimationTrackEdit *AnimationTrackEditPlugin::create_audio_track_edit() {
3193 if (get_script_instance()) {
3194 return Object::cast_to<AnimationTrackEdit>(get_script_instance()->call("create_audio_track_edit").operator Object *());
3195 }
3196 return nullptr;
3197}
3198
3199AnimationTrackEdit *AnimationTrackEditPlugin::create_animation_track_edit(Object *p_object) {
3200 if (get_script_instance()) {
3201 return Object::cast_to<AnimationTrackEdit>(get_script_instance()->call("create_animation_track_edit", p_object).operator Object *());
3202 }
3203 return nullptr;
3204}
3205
3206///////////////////////////////////////
3207
3208void AnimationTrackEditGroup::_notification(int p_what) {
3209 switch (p_what) {
3210 case NOTIFICATION_DRAW: {
3211 Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
3212 int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
3213 int separation = get_theme_constant(SNAME("h_separation"), SNAME("ItemList"));
3214 Color color = get_theme_color(SNAME("font_color"), SNAME("Label"));
3215
3216 if (root && root->has_node(node)) {
3217 Node *n = root->get_node(node);
3218 if (n && EditorNode::get_singleton()->get_editor_selection()->is_selected(n)) {
3219 color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
3220 }
3221 }
3222
3223 Color bgcol = get_theme_color(SNAME("dark_color_2"), EditorStringName(Editor));
3224 bgcol.a *= 0.6;
3225 draw_rect(Rect2(Point2(), get_size()), bgcol);
3226 Color linecolor = color;
3227 linecolor.a = 0.2;
3228
3229 draw_line(Point2(), Point2(get_size().width, 0), linecolor, Math::round(EDSCALE));
3230 draw_line(Point2(timeline->get_name_limit(), 0), Point2(timeline->get_name_limit(), get_size().height), linecolor, Math::round(EDSCALE));
3231 draw_line(Point2(get_size().width - timeline->get_buttons_width(), 0), Point2(get_size().width - timeline->get_buttons_width(), get_size().height), linecolor, Math::round(EDSCALE));
3232
3233 int ofs = 0;
3234 draw_texture(icon, Point2(ofs, int(get_size().height - icon->get_height()) / 2));
3235 ofs += separation + icon->get_width();
3236 draw_string(font, Point2(ofs, int(get_size().height - font->get_height(font_size)) / 2 + font->get_ascent(font_size)), node_name, HORIZONTAL_ALIGNMENT_LEFT, timeline->get_name_limit() - ofs, font_size, color);
3237
3238 int px = (-timeline->get_value() + timeline->get_play_position()) * timeline->get_zoom_scale() + timeline->get_name_limit();
3239
3240 if (px >= timeline->get_name_limit() && px < (get_size().width - timeline->get_buttons_width())) {
3241 Color accent = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
3242 draw_line(Point2(px, 0), Point2(px, get_size().height), accent, Math::round(2 * EDSCALE));
3243 }
3244 } break;
3245 }
3246}
3247
3248void AnimationTrackEditGroup::set_type_and_name(const Ref<Texture2D> &p_type, const String &p_name, const NodePath &p_node) {
3249 icon = p_type;
3250 node_name = p_name;
3251 node = p_node;
3252 queue_redraw();
3253 update_minimum_size();
3254}
3255
3256Size2 AnimationTrackEditGroup::get_minimum_size() const {
3257 Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
3258 int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
3259 int separation = get_theme_constant(SNAME("v_separation"), SNAME("ItemList"));
3260
3261 return Vector2(0, MAX(font->get_height(font_size), icon->get_height()) + separation);
3262}
3263
3264void AnimationTrackEditGroup::set_timeline(AnimationTimelineEdit *p_timeline) {
3265 timeline = p_timeline;
3266 timeline->connect("zoom_changed", callable_mp(this, &AnimationTrackEditGroup::_zoom_changed));
3267 timeline->connect("name_limit_changed", callable_mp(this, &AnimationTrackEditGroup::_zoom_changed));
3268}
3269
3270void AnimationTrackEditGroup::set_root(Node *p_root) {
3271 root = p_root;
3272 queue_redraw();
3273}
3274
3275void AnimationTrackEditGroup::_zoom_changed() {
3276 queue_redraw();
3277}
3278
3279AnimationTrackEditGroup::AnimationTrackEditGroup() {
3280 set_mouse_filter(MOUSE_FILTER_PASS);
3281}
3282
3283//////////////////////////////////////
3284
3285void AnimationTrackEditor::add_track_edit_plugin(const Ref<AnimationTrackEditPlugin> &p_plugin) {
3286 if (track_edit_plugins.has(p_plugin)) {
3287 return;
3288 }
3289 track_edit_plugins.push_back(p_plugin);
3290}
3291
3292void AnimationTrackEditor::remove_track_edit_plugin(const Ref<AnimationTrackEditPlugin> &p_plugin) {
3293 track_edit_plugins.erase(p_plugin);
3294}
3295
3296void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim, bool p_read_only) {
3297 if (animation != p_anim && _get_track_selected() >= 0) {
3298 track_edits[_get_track_selected()]->release_focus();
3299 }
3300 if (animation.is_valid()) {
3301 animation->disconnect_changed(callable_mp(this, &AnimationTrackEditor::_animation_changed));
3302 _clear_selection();
3303 }
3304 animation = p_anim;
3305 read_only = p_read_only;
3306 timeline->set_animation(p_anim, read_only);
3307
3308 _cancel_bezier_edit();
3309 _update_tracks();
3310
3311 if (animation.is_valid()) {
3312 animation->connect_changed(callable_mp(this, &AnimationTrackEditor::_animation_changed));
3313
3314 hscroll->show();
3315 edit->set_disabled(read_only);
3316 step->set_block_signals(true);
3317
3318 _update_step_spinbox();
3319 step->set_block_signals(false);
3320 step->set_read_only(false);
3321 snap->set_disabled(false);
3322 snap_mode->set_disabled(false);
3323
3324 imported_anim_warning->hide();
3325 for (int i = 0; i < animation->get_track_count(); i++) {
3326 if (animation->track_is_imported(i)) {
3327 imported_anim_warning->show();
3328 break;
3329 }
3330 }
3331
3332 _check_bezier_exist();
3333 } else {
3334 hscroll->hide();
3335 edit->set_disabled(true);
3336 step->set_block_signals(true);
3337 step->set_value(0);
3338 step->set_block_signals(false);
3339 step->set_read_only(true);
3340 snap->set_disabled(true);
3341 snap_mode->set_disabled(true);
3342 bezier_edit_icon->set_disabled(true);
3343 }
3344}
3345
3346void AnimationTrackEditor::_check_bezier_exist() {
3347 bool is_exist = false;
3348 for (int i = 0; i < animation->get_track_count(); i++) {
3349 if (animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) {
3350 is_exist = true;
3351 break;
3352 }
3353 }
3354 if (is_exist) {
3355 bezier_edit_icon->set_disabled(false);
3356 } else {
3357 if (bezier_edit->is_visible()) {
3358 _cancel_bezier_edit();
3359 }
3360 bezier_edit_icon->set_disabled(true);
3361 }
3362}
3363
3364Ref<Animation> AnimationTrackEditor::get_current_animation() const {
3365 return animation;
3366}
3367
3368void AnimationTrackEditor::_root_removed() {
3369 root = nullptr;
3370}
3371
3372void AnimationTrackEditor::set_root(Node *p_root) {
3373 if (root) {
3374 root->disconnect("tree_exiting", callable_mp(this, &AnimationTrackEditor::_root_removed));
3375 }
3376
3377 root = p_root;
3378
3379 if (root) {
3380 root->connect("tree_exiting", callable_mp(this, &AnimationTrackEditor::_root_removed), CONNECT_ONE_SHOT);
3381 }
3382
3383 _update_tracks();
3384}
3385
3386Node *AnimationTrackEditor::get_root() const {
3387 return root;
3388}
3389
3390void AnimationTrackEditor::update_keying() {
3391 bool keying_enabled = false;
3392
3393 EditorSelectionHistory *editor_history = EditorNode::get_singleton()->get_editor_selection_history();
3394 if (is_visible_in_tree() && animation.is_valid() && editor_history->get_path_size() > 0) {
3395 Object *obj = ObjectDB::get_instance(editor_history->get_path_object(0));
3396 keying_enabled = Object::cast_to<Node>(obj) != nullptr;
3397 }
3398
3399 if (keying_enabled == keying) {
3400 return;
3401 }
3402
3403 keying = keying_enabled;
3404
3405 emit_signal(SNAME("keying_changed"));
3406}
3407
3408bool AnimationTrackEditor::has_keying() const {
3409 return keying;
3410}
3411
3412Dictionary AnimationTrackEditor::get_state() const {
3413 Dictionary state;
3414 state["fps_mode"] = timeline->is_using_fps();
3415 state["zoom"] = zoom->get_value();
3416 state["offset"] = timeline->get_value();
3417 state["v_scroll"] = scroll->get_v_scroll_bar()->get_value();
3418 return state;
3419}
3420
3421void AnimationTrackEditor::set_state(const Dictionary &p_state) {
3422 if (p_state.has("fps_mode")) {
3423 bool fps_mode = p_state["fps_mode"];
3424 if (fps_mode) {
3425 snap_mode->select(1);
3426 } else {
3427 snap_mode->select(0);
3428 }
3429 _snap_mode_changed(snap_mode->get_selected());
3430 } else {
3431 snap_mode->select(0);
3432 _snap_mode_changed(snap_mode->get_selected());
3433 }
3434 if (p_state.has("zoom")) {
3435 zoom->set_value(p_state["zoom"]);
3436 } else {
3437 zoom->set_value(1.0);
3438 }
3439 if (p_state.has("offset")) {
3440 timeline->set_value(p_state["offset"]);
3441 } else {
3442 timeline->set_value(0);
3443 }
3444 if (p_state.has("v_scroll")) {
3445 scroll->get_v_scroll_bar()->set_value(p_state["v_scroll"]);
3446 } else {
3447 scroll->get_v_scroll_bar()->set_value(0);
3448 }
3449}
3450
3451void AnimationTrackEditor::cleanup() {
3452 set_animation(Ref<Animation>(), read_only);
3453}
3454
3455void AnimationTrackEditor::_name_limit_changed() {
3456 _redraw_tracks();
3457}
3458
3459void AnimationTrackEditor::_timeline_changed(float p_new_pos, bool p_drag, bool p_timeline_only) {
3460 emit_signal(SNAME("timeline_changed"), p_new_pos, p_drag, p_timeline_only);
3461}
3462
3463void AnimationTrackEditor::_track_remove_request(int p_track) {
3464 _animation_track_remove_request(p_track, animation);
3465}
3466
3467void AnimationTrackEditor::_animation_track_remove_request(int p_track, Ref<Animation> p_from_animation) {
3468 if (p_from_animation->track_is_compressed(p_track)) {
3469 EditorNode::get_singleton()->show_warning(TTR("Compressed tracks can't be edited or removed. Re-import the animation with compression disabled in order to edit."));
3470 return;
3471 }
3472 int idx = p_track;
3473 if (idx >= 0 && idx < p_from_animation->get_track_count()) {
3474 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
3475 undo_redo->create_action(TTR("Remove Anim Track"), UndoRedo::MERGE_DISABLE, p_from_animation.ptr());
3476
3477 // Remove corresponding reset tracks if they are no longer needed.
3478 AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();
3479 if (player->has_animation(SceneStringNames::get_singleton()->RESET)) {
3480 Ref<Animation> reset = player->get_animation(SceneStringNames::get_singleton()->RESET);
3481 if (reset != p_from_animation) {
3482 for (int i = 0; i < reset->get_track_count(); i++) {
3483 if (reset->track_get_path(i) == p_from_animation->track_get_path(p_track)) {
3484 // Check if the reset track isn't used by other animations.
3485 bool used = false;
3486 List<StringName> animation_list;
3487 player->get_animation_list(&animation_list);
3488
3489 for (const StringName &anim_name : animation_list) {
3490 Ref<Animation> anim = player->get_animation(anim_name);
3491 if (anim == p_from_animation || anim == reset) {
3492 continue;
3493 }
3494
3495 for (int j = 0; j < anim->get_track_count(); j++) {
3496 if (anim->track_get_path(j) == reset->track_get_path(i)) {
3497 used = true;
3498 break;
3499 }
3500 }
3501
3502 if (used) {
3503 break;
3504 }
3505 }
3506
3507 if (!used) {
3508 _animation_track_remove_request(i, reset);
3509 }
3510 break;
3511 }
3512 }
3513 }
3514 }
3515
3516 undo_redo->add_do_method(this, "_clear_selection", false);
3517 undo_redo->add_do_method(p_from_animation.ptr(), "remove_track", idx);
3518 undo_redo->add_undo_method(p_from_animation.ptr(), "add_track", p_from_animation->track_get_type(idx), idx);
3519 undo_redo->add_undo_method(p_from_animation.ptr(), "track_set_path", idx, p_from_animation->track_get_path(idx));
3520
3521 // TODO interpolation.
3522 for (int i = 0; i < p_from_animation->track_get_key_count(idx); i++) {
3523 Variant v = p_from_animation->track_get_key_value(idx, i);
3524 float time = p_from_animation->track_get_key_time(idx, i);
3525 float trans = p_from_animation->track_get_key_transition(idx, i);
3526
3527 undo_redo->add_undo_method(p_from_animation.ptr(), "track_insert_key", idx, time, v);
3528 undo_redo->add_undo_method(p_from_animation.ptr(), "track_set_key_transition", idx, i, trans);
3529 }
3530
3531 undo_redo->add_undo_method(p_from_animation.ptr(), "track_set_interpolation_type", idx, p_from_animation->track_get_interpolation_type(idx));
3532 if (p_from_animation->track_get_type(idx) == Animation::TYPE_VALUE) {
3533 undo_redo->add_undo_method(p_from_animation.ptr(), "value_track_set_update_mode", idx, p_from_animation->value_track_get_update_mode(idx));
3534 }
3535 if (animation->track_get_type(idx) == Animation::TYPE_AUDIO) {
3536 undo_redo->add_undo_method(animation.ptr(), "audio_track_set_use_blend", idx, animation->audio_track_is_use_blend(idx));
3537 }
3538
3539 undo_redo->commit_action();
3540 }
3541}
3542
3543void AnimationTrackEditor::_track_grab_focus(int p_track) {
3544 // Don't steal focus if not working with the track editor.
3545 if (Object::cast_to<AnimationTrackEdit>(get_viewport()->gui_get_focus_owner())) {
3546 track_edits[p_track]->grab_focus();
3547 }
3548}
3549
3550void AnimationTrackEditor::set_anim_pos(float p_pos) {
3551 timeline->set_play_position(p_pos);
3552 for (int i = 0; i < track_edits.size(); i++) {
3553 track_edits[i]->set_play_position(p_pos);
3554 }
3555 _redraw_groups();
3556 bezier_edit->set_play_position(p_pos);
3557}
3558
3559static bool track_type_is_resettable(Animation::TrackType p_type) {
3560 switch (p_type) {
3561 case Animation::TYPE_VALUE:
3562 [[fallthrough]];
3563 case Animation::TYPE_BLEND_SHAPE:
3564 [[fallthrough]];
3565 case Animation::TYPE_BEZIER:
3566 [[fallthrough]];
3567 case Animation::TYPE_POSITION_3D:
3568 [[fallthrough]];
3569 case Animation::TYPE_ROTATION_3D:
3570 [[fallthrough]];
3571 case Animation::TYPE_SCALE_3D:
3572 return true;
3573 default:
3574 return false;
3575 }
3576}
3577
3578void AnimationTrackEditor::make_insert_queue() {
3579 insert_data.clear();
3580 insert_queue = true;
3581}
3582
3583void AnimationTrackEditor::commit_insert_queue() {
3584 bool reset_allowed = true;
3585 AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();
3586 if (player->has_animation(SceneStringNames::get_singleton()->RESET) && player->get_animation(SceneStringNames::get_singleton()->RESET) == animation) {
3587 // Avoid messing with the reset animation itself.
3588 reset_allowed = false;
3589 } else {
3590 bool some_resettable = false;
3591 for (int i = 0; i < insert_data.size(); i++) {
3592 if (track_type_is_resettable(insert_data[i].type)) {
3593 some_resettable = true;
3594 break;
3595 }
3596 }
3597 if (!some_resettable) {
3598 reset_allowed = false;
3599 }
3600 }
3601
3602 // Organize insert data.
3603 int num_tracks = 0;
3604 String last_track_query;
3605 bool all_bezier = true;
3606 for (int i = 0; i < insert_data.size(); i++) {
3607 if (insert_data[i].type != Animation::TYPE_VALUE && insert_data[i].type != Animation::TYPE_BEZIER) {
3608 all_bezier = false;
3609 }
3610
3611 if (insert_data[i].track_idx == -1) {
3612 ++num_tracks;
3613 last_track_query = insert_data[i].query;
3614 }
3615
3616 if (insert_data[i].type != Animation::TYPE_VALUE) {
3617 continue;
3618 }
3619
3620 switch (insert_data[i].value.get_type()) {
3621 case Variant::INT:
3622 case Variant::FLOAT:
3623 case Variant::VECTOR2:
3624 case Variant::VECTOR3:
3625 case Variant::QUATERNION:
3626 case Variant::PLANE:
3627 case Variant::COLOR: {
3628 // Valid.
3629 } break;
3630 default: {
3631 all_bezier = false;
3632 }
3633 }
3634 }
3635
3636 // Skip the confirmation dialog if the user holds Shift while clicking the key icon.
3637 if (!Input::get_singleton()->is_key_pressed(Key::SHIFT) && num_tracks > 0) {
3638 String shortcut_hint = TTR("Hold Shift when clicking the key icon to skip this dialog.");
3639 // Potentially a new key, does not exist.
3640 if (num_tracks == 1) {
3641 // TRANSLATORS: %s will be replaced by a phrase describing the target of track.
3642 insert_confirm_text->set_text(vformat(TTR("Create new track for %s and insert key?") + "\n\n" + shortcut_hint, last_track_query));
3643 } else {
3644 insert_confirm_text->set_text(vformat(TTR("Create %d new tracks and insert keys?") + "\n\n" + shortcut_hint, num_tracks));
3645 }
3646
3647 insert_confirm_bezier->set_visible(all_bezier);
3648 insert_confirm_reset->set_visible(reset_allowed);
3649
3650 insert_confirm->set_ok_button_text(TTR("Create"));
3651 insert_confirm->popup_centered();
3652 } else {
3653 _insert_track(reset_allowed && EDITOR_GET("editors/animation/default_create_reset_tracks"), all_bezier && EDITOR_GET("editors/animation/default_create_bezier_tracks"));
3654 }
3655
3656 insert_queue = false;
3657}
3658
3659void AnimationTrackEditor::_query_insert(const InsertData &p_id) {
3660 if (!insert_queue) {
3661 insert_data.clear();
3662 }
3663
3664 for (const InsertData &E : insert_data) {
3665 // Prevent insertion of multiple tracks.
3666 if (E.path == p_id.path && E.type == p_id.type) {
3667 return; // Already inserted a track this frame.
3668 }
3669 }
3670
3671 insert_data.push_back(p_id);
3672
3673 // Without queue, commit immediately.
3674 if (!insert_queue) {
3675 commit_insert_queue();
3676 }
3677}
3678
3679void AnimationTrackEditor::_insert_track(bool p_reset_wanted, bool p_create_beziers) {
3680 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
3681 undo_redo->create_action(TTR("Animation Insert Key"));
3682
3683 Ref<Animation> reset_anim;
3684 if (p_reset_wanted) {
3685 reset_anim = _create_and_get_reset_animation();
3686 }
3687
3688 TrackIndices next_tracks(animation.ptr(), reset_anim.ptr());
3689 bool advance = false;
3690 while (insert_data.size()) {
3691 if (insert_data.front()->get().advance) {
3692 advance = true;
3693 }
3694 next_tracks = _confirm_insert(insert_data.front()->get(), next_tracks, p_reset_wanted, reset_anim, p_create_beziers);
3695 insert_data.pop_front();
3696 }
3697
3698 undo_redo->commit_action();
3699
3700 if (advance) {
3701 _edit_menu_pressed(EDIT_GOTO_NEXT_STEP_TIMELINE_ONLY);
3702 }
3703}
3704
3705void AnimationTrackEditor::insert_transform_key(Node3D *p_node, const String &p_sub, const Animation::TrackType p_type, const Variant p_value) {
3706 ERR_FAIL_NULL(root);
3707 ERR_FAIL_COND_MSG(
3708 (p_type != Animation::TYPE_POSITION_3D && p_type != Animation::TYPE_ROTATION_3D && p_type != Animation::TYPE_SCALE_3D),
3709 "Track type must be Position/Rotation/Scale 3D.");
3710 if (!keying) {
3711 return;
3712 }
3713 if (!animation.is_valid()) {
3714 return;
3715 }
3716
3717 // Let's build a node path.
3718 String path = root->get_path_to(p_node, true);
3719 if (!p_sub.is_empty()) {
3720 path += ":" + p_sub;
3721 }
3722
3723 NodePath np = path;
3724
3725 int track_idx = -1;
3726
3727 for (int i = 0; i < animation->get_track_count(); i++) {
3728 if (animation->track_get_path(i) != np) {
3729 continue;
3730 }
3731 if (animation->track_get_type(i) != p_type) {
3732 continue;
3733 }
3734 track_idx = i;
3735 }
3736
3737 InsertData id;
3738 id.path = np;
3739 // TRANSLATORS: This describes the target of new animation track, will be inserted into another string.
3740 id.query = vformat(TTR("node '%s'"), p_node->get_name());
3741 id.advance = false;
3742 id.track_idx = track_idx;
3743 id.value = p_value;
3744 id.type = p_type;
3745 _query_insert(id);
3746}
3747
3748bool AnimationTrackEditor::has_track(Node3D *p_node, const String &p_sub, const Animation::TrackType p_type) {
3749 ERR_FAIL_NULL_V(root, false);
3750 if (!keying) {
3751 return false;
3752 }
3753 if (!animation.is_valid()) {
3754 return false;
3755 }
3756
3757 // Let's build a node path.
3758 String path = root->get_path_to(p_node, true);
3759 if (!p_sub.is_empty()) {
3760 path += ":" + p_sub;
3761 }
3762
3763 int track_id = animation->find_track(path, p_type);
3764 if (track_id >= 0) {
3765 return true;
3766 }
3767 return false;
3768}
3769
3770void AnimationTrackEditor::_insert_animation_key(NodePath p_path, const Variant &p_value) {
3771 String path = p_path;
3772
3773 // Animation property is a special case, always creates an animation track.
3774 for (int i = 0; i < animation->get_track_count(); i++) {
3775 String np = animation->track_get_path(i);
3776
3777 if (path == np && animation->track_get_type(i) == Animation::TYPE_ANIMATION) {
3778 // Exists.
3779 InsertData id;
3780 id.path = path;
3781 id.track_idx = i;
3782 id.value = p_value;
3783 id.type = Animation::TYPE_ANIMATION;
3784 // TRANSLATORS: This describes the target of new animation track, will be inserted into another string.
3785 id.query = TTR("animation");
3786 id.advance = false;
3787 // Dialog insert.
3788 _query_insert(id);
3789 return;
3790 }
3791 }
3792
3793 InsertData id;
3794 id.path = path;
3795 id.track_idx = -1;
3796 id.value = p_value;
3797 id.type = Animation::TYPE_ANIMATION;
3798 id.query = TTR("animation");
3799 id.advance = false;
3800 // Dialog insert.
3801 _query_insert(id);
3802}
3803
3804void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_property, const Variant &p_value, bool p_only_if_exists) {
3805 ERR_FAIL_NULL(root);
3806
3807 // Let's build a node path.
3808 Node *node = p_node;
3809 String path = root->get_path_to(node, true);
3810
3811 if (Object::cast_to<AnimationPlayer>(node) && p_property == "current_animation") {
3812 if (node == AnimationPlayerEditor::get_singleton()->get_player()) {
3813 EditorNode::get_singleton()->show_warning(TTR("AnimationPlayer can't animate itself, only other players."));
3814 return;
3815 }
3816 _insert_animation_key(path, p_value);
3817 return;
3818 }
3819
3820 EditorSelectionHistory *history = EditorNode::get_singleton()->get_editor_selection_history();
3821 for (int i = 1; i < history->get_path_size(); i++) {
3822 String prop = history->get_path_property(i);
3823 ERR_FAIL_COND(prop.is_empty());
3824 path += ":" + prop;
3825 }
3826
3827 path += ":" + p_property;
3828
3829 NodePath np = path;
3830
3831 // Locate track.
3832
3833 bool inserted = false;
3834
3835 for (int i = 0; i < animation->get_track_count(); i++) {
3836 if (animation->track_get_type(i) == Animation::TYPE_VALUE) {
3837 if (animation->track_get_path(i) != np) {
3838 continue;
3839 }
3840
3841 InsertData id;
3842 id.path = np;
3843 id.track_idx = i;
3844 id.value = p_value;
3845 id.type = Animation::TYPE_VALUE;
3846 // TRANSLATORS: This describes the target of new animation track, will be inserted into another string.
3847 id.query = vformat(TTR("property '%s'"), p_property);
3848 id.advance = false;
3849 // Dialog insert.
3850 _query_insert(id);
3851 inserted = true;
3852 } else if (animation->track_get_type(i) == Animation::TYPE_BEZIER) {
3853 Variant value;
3854 String track_path = animation->track_get_path(i);
3855 if (track_path == np) {
3856 value = p_value; // All good.
3857 } else {
3858 int sep = track_path.rfind(":");
3859 if (sep != -1) {
3860 String base_path = track_path.substr(0, sep);
3861 if (base_path == np) {
3862 String value_name = track_path.substr(sep + 1);
3863 value = p_value.get(value_name);
3864 } else {
3865 continue;
3866 }
3867 } else {
3868 continue;
3869 }
3870 }
3871
3872 InsertData id;
3873 id.path = animation->track_get_path(i);
3874 id.track_idx = i;
3875 id.value = value;
3876 id.type = Animation::TYPE_BEZIER;
3877 id.query = vformat(TTR("property '%s'"), p_property);
3878 id.advance = false;
3879 // Dialog insert.
3880 _query_insert(id);
3881 inserted = true;
3882 }
3883 }
3884
3885 if (inserted || p_only_if_exists) {
3886 return;
3887 }
3888 InsertData id;
3889 id.path = np;
3890 id.track_idx = -1;
3891 id.value = p_value;
3892 id.type = Animation::TYPE_VALUE;
3893 id.query = vformat(TTR("property '%s'"), p_property);
3894 id.advance = false;
3895 // Dialog insert.
3896 _query_insert(id);
3897}
3898
3899void AnimationTrackEditor::insert_value_key(const String &p_property, const Variant &p_value, bool p_advance) {
3900 EditorSelectionHistory *history = EditorNode::get_singleton()->get_editor_selection_history();
3901
3902 ERR_FAIL_NULL(root);
3903 ERR_FAIL_COND(history->get_path_size() == 0);
3904 Object *obj = ObjectDB::get_instance(history->get_path_object(0));
3905 ERR_FAIL_COND(!Object::cast_to<Node>(obj));
3906
3907 // Let's build a node path.
3908 Node *node = Object::cast_to<Node>(obj);
3909 String path = root->get_path_to(node, true);
3910
3911 if (Object::cast_to<AnimationPlayer>(node) && p_property == "current_animation") {
3912 if (node == AnimationPlayerEditor::get_singleton()->get_player()) {
3913 EditorNode::get_singleton()->show_warning(TTR("AnimationPlayer can't animate itself, only other players."));
3914 return;
3915 }
3916 _insert_animation_key(path, p_value);
3917 return;
3918 }
3919
3920 for (int i = 1; i < history->get_path_size(); i++) {
3921 String prop = history->get_path_property(i);
3922 ERR_FAIL_COND(prop.is_empty());
3923 path += ":" + prop;
3924 }
3925
3926 path += ":" + p_property;
3927
3928 NodePath np = path;
3929
3930 // Locate track.
3931
3932 bool inserted = false;
3933
3934 make_insert_queue();
3935 for (int i = 0; i < animation->get_track_count(); i++) {
3936 if (animation->track_get_type(i) == Animation::TYPE_VALUE) {
3937 if (animation->track_get_path(i) != np) {
3938 continue;
3939 }
3940
3941 InsertData id;
3942 id.path = np;
3943 id.track_idx = i;
3944 id.value = p_value;
3945 id.type = Animation::TYPE_VALUE;
3946 id.query = vformat(TTR("property '%s'"), p_property);
3947 id.advance = p_advance;
3948 // Dialog insert.
3949 _query_insert(id);
3950 inserted = true;
3951 } else if (animation->track_get_type(i) == Animation::TYPE_BEZIER) {
3952 Variant value;
3953 if (animation->track_get_path(i) == np) {
3954 value = p_value; // All good.
3955 } else {
3956 String tpath = animation->track_get_path(i);
3957 int index = tpath.rfind(":");
3958 if (NodePath(tpath.substr(0, index + 1)) == np) {
3959 String subindex = tpath.substr(index + 1, tpath.length() - index);
3960 value = p_value.get(subindex);
3961 } else {
3962 continue;
3963 }
3964 }
3965
3966 InsertData id;
3967 id.path = animation->track_get_path(i);
3968 id.track_idx = i;
3969 id.value = value;
3970 id.type = Animation::TYPE_BEZIER;
3971 id.query = vformat(TTR("property '%s'"), p_property);
3972 id.advance = p_advance;
3973 // Dialog insert.
3974 _query_insert(id);
3975 inserted = true;
3976 }
3977 }
3978 commit_insert_queue();
3979
3980 if (!inserted) {
3981 InsertData id;
3982 id.path = np;
3983 id.track_idx = -1;
3984 id.value = p_value;
3985 id.type = Animation::TYPE_VALUE;
3986 id.query = vformat(TTR("property '%s'"), p_property);
3987 id.advance = p_advance;
3988 // Dialog insert.
3989 _query_insert(id);
3990 }
3991}
3992
3993Ref<Animation> AnimationTrackEditor::_create_and_get_reset_animation() {
3994 AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();
3995 if (player->has_animation(SceneStringNames::get_singleton()->RESET)) {
3996 return player->get_animation(SceneStringNames::get_singleton()->RESET);
3997 } else {
3998 Ref<AnimationLibrary> al;
3999 if (!player->has_animation_library("")) {
4000 al.instantiate();
4001 player->add_animation_library("", al);
4002 } else {
4003 al = player->get_animation_library("");
4004 }
4005
4006 Ref<Animation> reset_anim;
4007 reset_anim.instantiate();
4008 reset_anim->set_length(ANIM_MIN_LENGTH);
4009 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
4010 undo_redo->add_do_method(al.ptr(), "add_animation", SceneStringNames::get_singleton()->RESET, reset_anim);
4011 undo_redo->add_do_method(AnimationPlayerEditor::get_singleton(), "_animation_player_changed", player);
4012 undo_redo->add_undo_method(al.ptr(), "remove_animation", SceneStringNames::get_singleton()->RESET);
4013 undo_redo->add_undo_method(AnimationPlayerEditor::get_singleton(), "_animation_player_changed", player);
4014 return reset_anim;
4015 }
4016}
4017
4018void AnimationTrackEditor::_confirm_insert_list() {
4019 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
4020 undo_redo->create_action(TTR("Animation Insert Key"));
4021
4022 bool create_reset = insert_confirm_reset->is_visible() && insert_confirm_reset->is_pressed();
4023 Ref<Animation> reset_anim;
4024 if (create_reset) {
4025 reset_anim = _create_and_get_reset_animation();
4026 }
4027
4028 TrackIndices next_tracks(animation.ptr(), reset_anim.ptr());
4029 bool advance = false;
4030 while (insert_data.size()) {
4031 if (insert_data.front()->get().advance) {
4032 advance = true;
4033 }
4034 next_tracks = _confirm_insert(insert_data.front()->get(), next_tracks, create_reset, reset_anim, insert_confirm_bezier->is_pressed());
4035 insert_data.pop_front();
4036 }
4037
4038 undo_redo->commit_action();
4039
4040 if (advance) {
4041 _edit_menu_pressed(EDIT_GOTO_NEXT_STEP_TIMELINE_ONLY);
4042 }
4043}
4044
4045PropertyInfo AnimationTrackEditor::_find_hint_for_track(int p_idx, NodePath &r_base_path, Variant *r_current_val) {
4046 r_base_path = NodePath();
4047 ERR_FAIL_COND_V(!animation.is_valid(), PropertyInfo());
4048 ERR_FAIL_INDEX_V(p_idx, animation->get_track_count(), PropertyInfo());
4049
4050 if (!root) {
4051 return PropertyInfo();
4052 }
4053
4054 NodePath path = animation->track_get_path(p_idx);
4055
4056 if (!root->has_node_and_resource(path)) {
4057 return PropertyInfo();
4058 }
4059
4060 Ref<Resource> res;
4061 Vector<StringName> leftover_path;
4062 Node *node = root->get_node_and_resource(path, res, leftover_path, true);
4063
4064 if (node) {
4065 r_base_path = node->get_path();
4066 }
4067
4068 if (leftover_path.is_empty()) {
4069 if (r_current_val) {
4070 if (res.is_valid()) {
4071 *r_current_val = res;
4072 } else if (node) {
4073 *r_current_val = node;
4074 }
4075 }
4076 return PropertyInfo();
4077 }
4078
4079 Variant property_info_base;
4080 if (res.is_valid()) {
4081 property_info_base = res;
4082 if (r_current_val) {
4083 *r_current_val = res->get_indexed(leftover_path);
4084 }
4085 } else if (node) {
4086 property_info_base = node;
4087 if (r_current_val) {
4088 *r_current_val = node->get_indexed(leftover_path);
4089 }
4090 }
4091
4092 for (int i = 0; i < leftover_path.size() - 1; i++) {
4093 bool valid;
4094 property_info_base = property_info_base.get_named(leftover_path[i], valid);
4095 }
4096
4097 List<PropertyInfo> pinfo;
4098 property_info_base.get_property_list(&pinfo);
4099
4100 for (const PropertyInfo &E : pinfo) {
4101 if (E.name == leftover_path[leftover_path.size() - 1]) {
4102 return E;
4103 }
4104 }
4105
4106 return PropertyInfo();
4107}
4108
4109static Vector<String> _get_bezier_subindices_for_type(Variant::Type p_type, bool *r_valid = nullptr) {
4110 Vector<String> subindices;
4111 if (r_valid) {
4112 *r_valid = true;
4113 }
4114 switch (p_type) {
4115 case Variant::INT: {
4116 subindices.push_back("");
4117 } break;
4118 case Variant::FLOAT: {
4119 subindices.push_back("");
4120 } break;
4121 case Variant::VECTOR2: {
4122 subindices.push_back(":x");
4123 subindices.push_back(":y");
4124 } break;
4125 case Variant::VECTOR3: {
4126 subindices.push_back(":x");
4127 subindices.push_back(":y");
4128 subindices.push_back(":z");
4129 } break;
4130 case Variant::QUATERNION: {
4131 subindices.push_back(":x");
4132 subindices.push_back(":y");
4133 subindices.push_back(":z");
4134 subindices.push_back(":w");
4135 } break;
4136 case Variant::COLOR: {
4137 subindices.push_back(":r");
4138 subindices.push_back(":g");
4139 subindices.push_back(":b");
4140 subindices.push_back(":a");
4141 } break;
4142 case Variant::PLANE: {
4143 subindices.push_back(":x");
4144 subindices.push_back(":y");
4145 subindices.push_back(":z");
4146 subindices.push_back(":d");
4147 } break;
4148 default: {
4149 if (r_valid) {
4150 *r_valid = false;
4151 }
4152 }
4153 }
4154
4155 return subindices;
4156}
4157
4158AnimationTrackEditor::TrackIndices AnimationTrackEditor::_confirm_insert(InsertData p_id, TrackIndices p_next_tracks, bool p_reset_wanted, Ref<Animation> p_reset_anim, bool p_create_beziers) {
4159 bool created = false;
4160
4161 bool create_normal_track = p_id.track_idx < 0;
4162 bool create_reset_track = p_reset_wanted && track_type_is_resettable(p_id.type);
4163
4164 Animation::UpdateMode update_mode = Animation::UPDATE_DISCRETE;
4165 if (create_normal_track || create_reset_track) {
4166 if (p_id.type == Animation::TYPE_VALUE || p_id.type == Animation::TYPE_BEZIER) {
4167 // Hack.
4168 NodePath np;
4169 animation->add_track(p_id.type);
4170 animation->track_set_path(animation->get_track_count() - 1, p_id.path);
4171 PropertyInfo h = _find_hint_for_track(animation->get_track_count() - 1, np);
4172 animation->remove_track(animation->get_track_count() - 1); // Hack.
4173
4174 if (h.type == Variant::FLOAT ||
4175 h.type == Variant::VECTOR2 ||
4176 h.type == Variant::RECT2 ||
4177 h.type == Variant::VECTOR3 ||
4178 h.type == Variant::AABB ||
4179 h.type == Variant::QUATERNION ||
4180 h.type == Variant::COLOR ||
4181 h.type == Variant::PLANE ||
4182 h.type == Variant::TRANSFORM2D ||
4183 h.type == Variant::TRANSFORM3D) {
4184 update_mode = Animation::UPDATE_CONTINUOUS;
4185 }
4186 }
4187 }
4188
4189 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
4190 if (create_normal_track) {
4191 if (p_create_beziers) {
4192 bool valid;
4193 Vector<String> subindices = _get_bezier_subindices_for_type(p_id.value.get_type(), &valid);
4194 if (valid) {
4195 for (int i = 0; i < subindices.size(); i++) {
4196 InsertData id = p_id;
4197 id.type = Animation::TYPE_BEZIER;
4198 id.value = p_id.value.get(subindices[i].substr(1, subindices[i].length()));
4199 id.path = String(p_id.path) + subindices[i];
4200 p_next_tracks = _confirm_insert(id, p_next_tracks, p_reset_wanted, p_reset_anim, false);
4201 }
4202
4203 return p_next_tracks;
4204 }
4205 }
4206 created = true;
4207
4208 p_id.track_idx = p_next_tracks.normal;
4209
4210 undo_redo->add_do_method(animation.ptr(), "add_track", p_id.type);
4211 undo_redo->add_do_method(animation.ptr(), "track_set_path", p_id.track_idx, p_id.path);
4212 if (p_id.type == Animation::TYPE_VALUE) {
4213 undo_redo->add_do_method(animation.ptr(), "value_track_set_update_mode", p_id.track_idx, update_mode);
4214 }
4215 }
4216
4217 float time = timeline->get_play_position();
4218 Variant value;
4219
4220 switch (p_id.type) {
4221 case Animation::TYPE_POSITION_3D:
4222 case Animation::TYPE_ROTATION_3D:
4223 case Animation::TYPE_SCALE_3D:
4224 case Animation::TYPE_BLEND_SHAPE:
4225 case Animation::TYPE_VALUE: {
4226 value = p_id.value;
4227
4228 } break;
4229 case Animation::TYPE_BEZIER: {
4230 Array array;
4231 array.resize(5);
4232 array[0] = p_id.value;
4233 array[1] = -0.25;
4234 array[2] = 0;
4235 array[3] = 0.25;
4236 array[4] = 0;
4237 value = array;
4238 bezier_edit_icon->set_disabled(false);
4239
4240 } break;
4241 case Animation::TYPE_ANIMATION: {
4242 value = p_id.value;
4243 } break;
4244 default: {
4245 }
4246 }
4247
4248 undo_redo->add_do_method(animation.ptr(), "track_insert_key", p_id.track_idx, time, value);
4249
4250 if (created) {
4251 // Just remove the track.
4252 undo_redo->add_undo_method(this, "_clear_selection", false);
4253 undo_redo->add_undo_method(animation.ptr(), "remove_track", animation->get_track_count());
4254 p_next_tracks.normal++;
4255 } else {
4256 undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_id.track_idx, time);
4257 int existing = animation->track_find_key(p_id.track_idx, time, Animation::FIND_MODE_APPROX);
4258 if (existing != -1) {
4259 Variant v = animation->track_get_key_value(p_id.track_idx, existing);
4260 float trans = animation->track_get_key_transition(p_id.track_idx, existing);
4261 undo_redo->add_undo_method(animation.ptr(), "track_insert_key", p_id.track_idx, time, v, trans);
4262 }
4263 }
4264
4265 if (create_reset_track) {
4266 Animation *reset_anim = p_reset_anim.ptr();
4267 for (int i = 0; i < reset_anim->get_track_count(); i++) {
4268 if (reset_anim->track_get_path(i) == p_id.path) {
4269 create_reset_track = false;
4270 break;
4271 }
4272 }
4273 if (create_reset_track) {
4274 undo_redo->add_do_method(reset_anim, "add_track", p_id.type);
4275 undo_redo->add_do_method(reset_anim, "track_set_path", p_next_tracks.reset, p_id.path);
4276 if (p_id.type == Animation::TYPE_VALUE) {
4277 undo_redo->add_do_method(reset_anim, "value_track_set_update_mode", p_next_tracks.reset, update_mode);
4278 }
4279 undo_redo->add_do_method(reset_anim, "track_insert_key", p_next_tracks.reset, 0.0f, value);
4280 undo_redo->add_undo_method(reset_anim, "remove_track", reset_anim->get_track_count());
4281 p_next_tracks.reset++;
4282 }
4283 }
4284
4285 return p_next_tracks;
4286}
4287
4288void AnimationTrackEditor::show_select_node_warning(bool p_show) {
4289 info_message->set_visible(p_show);
4290}
4291
4292bool AnimationTrackEditor::is_key_selected(int p_track, int p_key) const {
4293 SelectedKey sk;
4294 sk.key = p_key;
4295 sk.track = p_track;
4296
4297 return selection.has(sk);
4298}
4299
4300bool AnimationTrackEditor::is_selection_active() const {
4301 return selection.size();
4302}
4303
4304bool AnimationTrackEditor::is_snap_enabled() const {
4305 return snap->is_pressed() ^ Input::get_singleton()->is_key_pressed(Key::CTRL);
4306}
4307
4308void AnimationTrackEditor::_update_tracks() {
4309 int selected = _get_track_selected();
4310
4311 while (track_vbox->get_child_count()) {
4312 memdelete(track_vbox->get_child(0));
4313 }
4314
4315 timeline->set_track_edit(nullptr);
4316
4317 track_edits.clear();
4318 groups.clear();
4319
4320 if (animation.is_null()) {
4321 return;
4322 }
4323
4324 bool file_read_only = false;
4325 if (!animation->get_path().is_resource_file()) {
4326 int srpos = animation->get_path().find("::");
4327 if (srpos != -1) {
4328 String base = animation->get_path().substr(0, srpos);
4329 if (ResourceLoader::get_resource_type(base) == "PackedScene") {
4330 if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) {
4331 file_read_only = true;
4332 }
4333 } else {
4334 if (FileAccess::exists(base + ".import")) {
4335 file_read_only = true;
4336 }
4337 }
4338 }
4339 } else {
4340 if (FileAccess::exists(animation->get_path() + ".import")) {
4341 file_read_only = true;
4342 }
4343 }
4344
4345 RBMap<String, VBoxContainer *> group_sort;
4346
4347 bool use_grouping = !view_group->is_pressed();
4348 bool use_filter = selected_filter->is_pressed();
4349
4350 for (int i = 0; i < animation->get_track_count(); i++) {
4351 AnimationTrackEdit *track_edit = nullptr;
4352
4353 // Find hint and info for plugin.
4354
4355 if (use_filter) {
4356 NodePath path = animation->track_get_path(i);
4357
4358 if (root && root->has_node(path)) {
4359 Node *node = root->get_node(path);
4360 if (!node) {
4361 continue; // No node, no filter.
4362 }
4363 if (!EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) {
4364 continue; // Skip track due to not selected.
4365 }
4366 }
4367 }
4368
4369 if (animation->track_get_type(i) == Animation::TYPE_VALUE) {
4370 NodePath path = animation->track_get_path(i);
4371
4372 if (root && root->has_node_and_resource(path)) {
4373 Ref<Resource> res;
4374 NodePath base_path;
4375 Vector<StringName> leftover_path;
4376 Node *node = root->get_node_and_resource(path, res, leftover_path, true);
4377 PropertyInfo pinfo = _find_hint_for_track(i, base_path);
4378
4379 Object *object = node;
4380 if (res.is_valid()) {
4381 object = res.ptr();
4382 }
4383
4384 if (object && !leftover_path.is_empty()) {
4385 if (pinfo.name.is_empty()) {
4386 pinfo.name = leftover_path[leftover_path.size() - 1];
4387 }
4388
4389 for (int j = 0; j < track_edit_plugins.size(); j++) {
4390 track_edit = track_edit_plugins.write[j]->create_value_track_edit(object, pinfo.type, pinfo.name, pinfo.hint, pinfo.hint_string, pinfo.usage);
4391 if (track_edit) {
4392 break;
4393 }
4394 }
4395 }
4396 }
4397 }
4398 if (animation->track_get_type(i) == Animation::TYPE_AUDIO) {
4399 for (int j = 0; j < track_edit_plugins.size(); j++) {
4400 track_edit = track_edit_plugins.write[j]->create_audio_track_edit();
4401 if (track_edit) {
4402 break;
4403 }
4404 }
4405 }
4406
4407 if (animation->track_get_type(i) == Animation::TYPE_ANIMATION) {
4408 NodePath path = animation->track_get_path(i);
4409
4410 Node *node = nullptr;
4411 if (root && root->has_node(path)) {
4412 node = root->get_node(path);
4413 }
4414
4415 if (node && Object::cast_to<AnimationPlayer>(node)) {
4416 for (int j = 0; j < track_edit_plugins.size(); j++) {
4417 track_edit = track_edit_plugins.write[j]->create_animation_track_edit(node);
4418 if (track_edit) {
4419 break;
4420 }
4421 }
4422 }
4423 }
4424
4425 if (track_edit == nullptr) {
4426 // No valid plugin_found.
4427 track_edit = memnew(AnimationTrackEdit);
4428 }
4429
4430 track_edits.push_back(track_edit);
4431
4432 if (use_grouping) {
4433 String base_path = animation->track_get_path(i);
4434 base_path = base_path.get_slice(":", 0); // Remove sub-path.
4435
4436 if (!group_sort.has(base_path)) {
4437 AnimationTrackEditGroup *g = memnew(AnimationTrackEditGroup);
4438 Ref<Texture2D> icon = get_editor_theme_icon(SNAME("Node"));
4439 String name = base_path;
4440 String tooltip;
4441 if (root && root->has_node(base_path)) {
4442 Node *n = root->get_node(base_path);
4443 if (n) {
4444 icon = EditorNode::get_singleton()->get_object_icon(n, "Node");
4445 name = n->get_name();
4446 tooltip = root->get_path_to(n);
4447 }
4448 }
4449
4450 g->set_type_and_name(icon, name, animation->track_get_path(i));
4451 g->set_root(root);
4452 g->set_tooltip_text(tooltip);
4453 g->set_timeline(timeline);
4454 groups.push_back(g);
4455 VBoxContainer *vb = memnew(VBoxContainer);
4456 vb->add_theme_constant_override("separation", 0);
4457 vb->add_child(g);
4458 track_vbox->add_child(vb);
4459 group_sort[base_path] = vb;
4460 }
4461
4462 track_edit->set_in_group(true);
4463 group_sort[base_path]->add_child(track_edit);
4464
4465 } else {
4466 track_edit->set_in_group(false);
4467 track_vbox->add_child(track_edit);
4468 }
4469
4470 track_edit->set_timeline(timeline);
4471 track_edit->set_root(root);
4472 track_edit->set_animation_and_track(animation, i, file_read_only);
4473 track_edit->set_play_position(timeline->get_play_position());
4474 track_edit->set_editor(this);
4475
4476 if (selected == i) {
4477 track_edit->grab_focus();
4478 }
4479
4480 track_edit->connect("timeline_changed", callable_mp(this, &AnimationTrackEditor::_timeline_changed));
4481 track_edit->connect("remove_request", callable_mp(this, &AnimationTrackEditor::_track_remove_request), CONNECT_DEFERRED);
4482 track_edit->connect("dropped", callable_mp(this, &AnimationTrackEditor::_dropped_track), CONNECT_DEFERRED);
4483 track_edit->connect("insert_key", callable_mp(this, &AnimationTrackEditor::_insert_key_from_track).bind(i), CONNECT_DEFERRED);
4484 track_edit->connect("select_key", callable_mp(this, &AnimationTrackEditor::_key_selected).bind(i), CONNECT_DEFERRED);
4485 track_edit->connect("deselect_key", callable_mp(this, &AnimationTrackEditor::_key_deselected).bind(i), CONNECT_DEFERRED);
4486 track_edit->connect("move_selection_begin", callable_mp(this, &AnimationTrackEditor::_move_selection_begin));
4487 track_edit->connect("move_selection", callable_mp(this, &AnimationTrackEditor::_move_selection));
4488 track_edit->connect("move_selection_commit", callable_mp(this, &AnimationTrackEditor::_move_selection_commit));
4489 track_edit->connect("move_selection_cancel", callable_mp(this, &AnimationTrackEditor::_move_selection_cancel));
4490
4491 track_edit->connect("duplicate_request", callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed).bind(EDIT_DUPLICATE_SELECTION), CONNECT_DEFERRED);
4492 track_edit->connect("duplicate_transpose_request", callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed).bind(EDIT_DUPLICATE_TRANSPOSED), CONNECT_DEFERRED);
4493 track_edit->connect("create_reset_request", callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed).bind(EDIT_ADD_RESET_KEY), CONNECT_DEFERRED);
4494 track_edit->connect("delete_request", callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed).bind(EDIT_DELETE_SELECTION), CONNECT_DEFERRED);
4495 }
4496}
4497
4498void AnimationTrackEditor::_redraw_tracks() {
4499 for (int i = 0; i < track_edits.size(); i++) {
4500 track_edits[i]->queue_redraw();
4501 }
4502}
4503
4504void AnimationTrackEditor::_redraw_groups() {
4505 for (int i = 0; i < groups.size(); i++) {
4506 groups[i]->queue_redraw();
4507 }
4508}
4509
4510void AnimationTrackEditor::_animation_changed() {
4511 if (animation_changing_awaiting_update) {
4512 return; // All will be updated, don't bother with anything.
4513 }
4514
4515 _check_bezier_exist();
4516
4517 if (key_edit) {
4518 if (key_edit->setting) {
4519 // If editing a key, just redraw the edited track, makes refresh less costly.
4520 if (key_edit->track < track_edits.size()) {
4521 if (animation->track_get_type(key_edit->track) == Animation::TYPE_BEZIER) {
4522 bezier_edit->queue_redraw();
4523 } else {
4524 track_edits[key_edit->track]->queue_redraw();
4525 }
4526 }
4527 return;
4528 } else {
4529 _update_key_edit();
4530 }
4531 }
4532
4533 animation_changing_awaiting_update = true;
4534 call_deferred(SNAME("_animation_update"));
4535}
4536
4537void AnimationTrackEditor::_snap_mode_changed(int p_mode) {
4538 timeline->set_use_fps(p_mode == 1);
4539 if (key_edit) {
4540 key_edit->set_use_fps(p_mode == 1);
4541 }
4542 _update_step_spinbox();
4543}
4544
4545void AnimationTrackEditor::_update_step_spinbox() {
4546 if (!animation.is_valid()) {
4547 return;
4548 }
4549 step->set_block_signals(true);
4550
4551 if (timeline->is_using_fps()) {
4552 if (animation->get_step() == 0) {
4553 step->set_value(0);
4554 } else {
4555 step->set_value(1.0 / animation->get_step());
4556 }
4557
4558 } else {
4559 step->set_value(animation->get_step());
4560 }
4561
4562 step->set_block_signals(false);
4563}
4564
4565void AnimationTrackEditor::_animation_update() {
4566 timeline->queue_redraw();
4567 timeline->update_values();
4568
4569 bool same = true;
4570
4571 if (animation.is_null()) {
4572 return;
4573 }
4574
4575 if (track_edits.size() == animation->get_track_count()) {
4576 // Check tracks are the same.
4577
4578 for (int i = 0; i < track_edits.size(); i++) {
4579 if (track_edits[i]->get_path() != animation->track_get_path(i)) {
4580 same = false;
4581 break;
4582 }
4583 }
4584 } else {
4585 same = false;
4586 }
4587
4588 if (same) {
4589 _redraw_tracks();
4590 _redraw_groups();
4591 } else {
4592 _update_tracks();
4593 }
4594
4595 bezier_edit->queue_redraw();
4596
4597 _update_step_spinbox();
4598 emit_signal(SNAME("animation_step_changed"), animation->get_step());
4599 emit_signal(SNAME("animation_len_changed"), animation->get_length());
4600
4601 animation_changing_awaiting_update = false;
4602}
4603
4604MenuButton *AnimationTrackEditor::get_edit_menu() {
4605 return edit;
4606}
4607
4608void AnimationTrackEditor::_notification(int p_what) {
4609 switch (p_what) {
4610 case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
4611 panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/animation_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EDITOR_GET("editors/panning/simple_panning")));
4612 } break;
4613
4614 case NOTIFICATION_ENTER_TREE: {
4615 panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/animation_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EDITOR_GET("editors/panning/simple_panning")));
4616 [[fallthrough]];
4617 }
4618 case NOTIFICATION_THEME_CHANGED: {
4619 zoom_icon->set_texture(get_editor_theme_icon(SNAME("Zoom")));
4620 bezier_edit_icon->set_icon(get_editor_theme_icon(SNAME("EditBezier")));
4621 snap->set_icon(get_editor_theme_icon(SNAME("Snap")));
4622 view_group->set_icon(get_editor_theme_icon(view_group->is_pressed() ? SNAME("AnimationTrackList") : SNAME("AnimationTrackGroup")));
4623 selected_filter->set_icon(get_editor_theme_icon(SNAME("AnimationFilter")));
4624 imported_anim_warning->set_icon(get_editor_theme_icon(SNAME("NodeWarning")));
4625 main_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Tree")));
4626 edit->get_popup()->set_item_icon(edit->get_popup()->get_item_index(EDIT_APPLY_RESET), get_editor_theme_icon(SNAME("Reload")));
4627 } break;
4628
4629 case NOTIFICATION_READY: {
4630 EditorNode::get_singleton()->get_editor_selection()->connect("selection_changed", callable_mp(this, &AnimationTrackEditor::_selection_changed));
4631 } break;
4632
4633 case NOTIFICATION_VISIBILITY_CHANGED: {
4634 update_keying();
4635 } break;
4636 }
4637}
4638
4639void AnimationTrackEditor::_update_scroll(double) {
4640 _redraw_tracks();
4641 _redraw_groups();
4642}
4643
4644void AnimationTrackEditor::_update_step(double p_new_step) {
4645 if (animation.is_null()) {
4646 return;
4647 }
4648
4649 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
4650 undo_redo->create_action(TTR("Change Animation Step"));
4651 float step_value = p_new_step;
4652 if (timeline->is_using_fps()) {
4653 if (step_value != 0.0) {
4654 step_value = 1.0 / step_value;
4655 }
4656 timeline->queue_redraw();
4657 }
4658 undo_redo->add_do_method(animation.ptr(), "set_step", step_value);
4659 undo_redo->add_undo_method(animation.ptr(), "set_step", animation->get_step());
4660 step->set_block_signals(true);
4661 undo_redo->commit_action();
4662 step->set_block_signals(false);
4663 emit_signal(SNAME("animation_step_changed"), step_value);
4664}
4665
4666void AnimationTrackEditor::_update_length(double p_new_len) {
4667 emit_signal(SNAME("animation_len_changed"), p_new_len);
4668}
4669
4670void AnimationTrackEditor::_dropped_track(int p_from_track, int p_to_track) {
4671 if (p_from_track == p_to_track || p_from_track == p_to_track - 1) {
4672 return;
4673 }
4674
4675 _clear_selection(true);
4676 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
4677 undo_redo->create_action(TTR("Rearrange Tracks"));
4678 undo_redo->add_do_method(animation.ptr(), "track_move_to", p_from_track, p_to_track);
4679 // Take into account that the position of the tracks that come after the one removed will change.
4680 int to_track_real = p_to_track > p_from_track ? p_to_track - 1 : p_to_track;
4681 undo_redo->add_undo_method(animation.ptr(), "track_move_to", to_track_real, p_to_track > p_from_track ? p_from_track : p_from_track + 1);
4682 undo_redo->add_do_method(this, "_track_grab_focus", to_track_real);
4683 undo_redo->add_undo_method(this, "_track_grab_focus", p_from_track);
4684 undo_redo->commit_action();
4685}
4686
4687void AnimationTrackEditor::_new_track_node_selected(NodePath p_path) {
4688 ERR_FAIL_NULL(root);
4689 Node *node = get_node(p_path);
4690 ERR_FAIL_NULL(node);
4691 NodePath path_to = root->get_path_to(node, true);
4692
4693 if (adding_track_type == Animation::TYPE_BLEND_SHAPE && !node->is_class("MeshInstance3D")) {
4694 EditorNode::get_singleton()->show_warning(TTR("Blend Shape tracks only apply to MeshInstance3D nodes."));
4695 return;
4696 }
4697
4698 if ((adding_track_type == Animation::TYPE_POSITION_3D || adding_track_type == Animation::TYPE_ROTATION_3D || adding_track_type == Animation::TYPE_SCALE_3D) && !node->is_class("Node3D")) {
4699 EditorNode::get_singleton()->show_warning(TTR("Position/Rotation/Scale 3D tracks only apply to 3D-based nodes."));
4700 return;
4701 }
4702
4703 switch (adding_track_type) {
4704 case Animation::TYPE_VALUE: {
4705 adding_track_path = path_to;
4706 prop_selector->set_type_filter(Vector<Variant::Type>());
4707 prop_selector->select_property_from_instance(node);
4708 } break;
4709 case Animation::TYPE_BLEND_SHAPE: {
4710 adding_track_path = path_to;
4711 Vector<Variant::Type> filter;
4712 filter.push_back(Variant::FLOAT);
4713 prop_selector->set_type_filter(filter);
4714 prop_selector->select_property_from_instance(node);
4715 } break;
4716 case Animation::TYPE_POSITION_3D:
4717 case Animation::TYPE_ROTATION_3D:
4718 case Animation::TYPE_SCALE_3D:
4719 case Animation::TYPE_METHOD: {
4720 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
4721 undo_redo->create_action(TTR("Add Track"));
4722 undo_redo->add_do_method(animation.ptr(), "add_track", adding_track_type);
4723 undo_redo->add_do_method(animation.ptr(), "track_set_path", animation->get_track_count(), path_to);
4724 undo_redo->add_undo_method(animation.ptr(), "remove_track", animation->get_track_count());
4725 undo_redo->commit_action();
4726
4727 } break;
4728 case Animation::TYPE_BEZIER: {
4729 Vector<Variant::Type> filter;
4730 filter.push_back(Variant::INT);
4731 filter.push_back(Variant::FLOAT);
4732 filter.push_back(Variant::VECTOR2);
4733 filter.push_back(Variant::VECTOR3);
4734 filter.push_back(Variant::QUATERNION);
4735 filter.push_back(Variant::PLANE);
4736 filter.push_back(Variant::COLOR);
4737
4738 adding_track_path = path_to;
4739 prop_selector->set_type_filter(filter);
4740 prop_selector->select_property_from_instance(node);
4741 } break;
4742 case Animation::TYPE_AUDIO: {
4743 if (!node->is_class("AudioStreamPlayer") && !node->is_class("AudioStreamPlayer2D") && !node->is_class("AudioStreamPlayer3D")) {
4744 EditorNode::get_singleton()->show_warning(TTR("Audio tracks can only point to nodes of type:\n-AudioStreamPlayer\n-AudioStreamPlayer2D\n-AudioStreamPlayer3D"));
4745 return;
4746 }
4747
4748 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
4749 undo_redo->create_action(TTR("Add Track"));
4750 undo_redo->add_do_method(animation.ptr(), "add_track", adding_track_type);
4751 undo_redo->add_do_method(animation.ptr(), "track_set_path", animation->get_track_count(), path_to);
4752 undo_redo->add_undo_method(animation.ptr(), "remove_track", animation->get_track_count());
4753 undo_redo->commit_action();
4754
4755 } break;
4756 case Animation::TYPE_ANIMATION: {
4757 if (!node->is_class("AnimationPlayer")) {
4758 EditorNode::get_singleton()->show_warning(TTR("Animation tracks can only point to AnimationPlayer nodes."));
4759 return;
4760 }
4761
4762 if (node == AnimationPlayerEditor::get_singleton()->get_player()) {
4763 EditorNode::get_singleton()->show_warning(TTR("AnimationPlayer can't animate itself, only other players."));
4764 return;
4765 }
4766
4767 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
4768 undo_redo->create_action(TTR("Add Track"));
4769 undo_redo->add_do_method(animation.ptr(), "add_track", adding_track_type);
4770 undo_redo->add_do_method(animation.ptr(), "track_set_path", animation->get_track_count(), path_to);
4771 undo_redo->add_undo_method(animation.ptr(), "remove_track", animation->get_track_count());
4772 undo_redo->commit_action();
4773
4774 } break;
4775 }
4776}
4777
4778void AnimationTrackEditor::_add_track(int p_type) {
4779 if (!root) {
4780 EditorNode::get_singleton()->show_warning(TTR("Not possible to add a new track without a root"));
4781 return;
4782 }
4783 adding_track_type = p_type;
4784 pick_track->popup_scenetree_dialog();
4785 pick_track->get_filter_line_edit()->clear();
4786 pick_track->get_filter_line_edit()->grab_focus();
4787}
4788
4789void AnimationTrackEditor::_new_track_property_selected(String p_name) {
4790 String full_path = String(adding_track_path) + ":" + p_name;
4791
4792 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
4793 if (adding_track_type == Animation::TYPE_VALUE) {
4794 Animation::UpdateMode update_mode = Animation::UPDATE_DISCRETE;
4795 {
4796 // Hack.
4797 NodePath np;
4798 animation->add_track(Animation::TYPE_VALUE);
4799 animation->track_set_path(animation->get_track_count() - 1, full_path);
4800 PropertyInfo h = _find_hint_for_track(animation->get_track_count() - 1, np);
4801 animation->remove_track(animation->get_track_count() - 1); // Hack.
4802 if (h.type == Variant::FLOAT ||
4803 h.type == Variant::VECTOR2 ||
4804 h.type == Variant::RECT2 ||
4805 h.type == Variant::VECTOR3 ||
4806 h.type == Variant::AABB ||
4807 h.type == Variant::QUATERNION ||
4808 h.type == Variant::COLOR ||
4809 h.type == Variant::PLANE ||
4810 h.type == Variant::TRANSFORM2D ||
4811 h.type == Variant::TRANSFORM3D) {
4812 update_mode = Animation::UPDATE_CONTINUOUS;
4813 }
4814 }
4815
4816 undo_redo->create_action(TTR("Add Track"));
4817 undo_redo->add_do_method(animation.ptr(), "add_track", adding_track_type);
4818 undo_redo->add_do_method(animation.ptr(), "track_set_path", animation->get_track_count(), full_path);
4819 undo_redo->add_do_method(animation.ptr(), "value_track_set_update_mode", animation->get_track_count(), update_mode);
4820 undo_redo->add_undo_method(animation.ptr(), "remove_track", animation->get_track_count());
4821 undo_redo->commit_action();
4822 } else {
4823 Vector<String> subindices;
4824 {
4825 // Hack.
4826 NodePath np;
4827 animation->add_track(Animation::TYPE_VALUE);
4828 animation->track_set_path(animation->get_track_count() - 1, full_path);
4829 PropertyInfo h = _find_hint_for_track(animation->get_track_count() - 1, np);
4830 animation->remove_track(animation->get_track_count() - 1); // Hack.
4831 bool valid;
4832 subindices = _get_bezier_subindices_for_type(h.type, &valid);
4833 if (!valid) {
4834 EditorNode::get_singleton()->show_warning(TTR("Invalid track for Bezier (no suitable sub-properties)"));
4835 return;
4836 }
4837 }
4838
4839 undo_redo->create_action(TTR("Add Bezier Track"));
4840 int base_track = animation->get_track_count();
4841 for (int i = 0; i < subindices.size(); i++) {
4842 undo_redo->add_do_method(animation.ptr(), "add_track", adding_track_type);
4843 undo_redo->add_do_method(animation.ptr(), "track_set_path", base_track + i, full_path + subindices[i]);
4844 undo_redo->add_undo_method(animation.ptr(), "remove_track", base_track);
4845 }
4846 undo_redo->commit_action();
4847 }
4848}
4849
4850void AnimationTrackEditor::_timeline_value_changed(double) {
4851 timeline->update_play_position();
4852
4853 _redraw_tracks();
4854 for (int i = 0; i < track_edits.size(); i++) {
4855 track_edits[i]->update_play_position();
4856 }
4857 _redraw_groups();
4858
4859 bezier_edit->queue_redraw();
4860 bezier_edit->update_play_position();
4861}
4862
4863int AnimationTrackEditor::_get_track_selected() {
4864 for (int i = 0; i < track_edits.size(); i++) {
4865 if (track_edits[i]->has_focus()) {
4866 return i;
4867 }
4868 }
4869
4870 return -1;
4871}
4872
4873void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) {
4874 ERR_FAIL_INDEX(p_track, animation->get_track_count());
4875
4876 if (snap->is_pressed() && step->get_value() != 0) {
4877 p_ofs = snap_time(p_ofs);
4878 }
4879 while (animation->track_find_key(p_track, p_ofs, Animation::FIND_MODE_APPROX) != -1) { // Make sure insertion point is valid.
4880 p_ofs += 0.0001;
4881 }
4882
4883 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
4884 switch (animation->track_get_type(p_track)) {
4885 case Animation::TYPE_POSITION_3D: {
4886 if (!root->has_node(animation->track_get_path(p_track))) {
4887 EditorNode::get_singleton()->show_warning(TTR("Track path is invalid, so can't add a key."));
4888 return;
4889 }
4890 Node3D *base = Object::cast_to<Node3D>(root->get_node(animation->track_get_path(p_track)));
4891
4892 if (!base) {
4893 EditorNode::get_singleton()->show_warning(TTR("Track is not of type Node3D, can't insert key"));
4894 return;
4895 }
4896
4897 Vector3 pos = base->get_position();
4898
4899 undo_redo->create_action(TTR("Add Position Key"));
4900 undo_redo->add_do_method(animation.ptr(), "position_track_insert_key", p_track, p_ofs, pos);
4901 undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs);
4902 undo_redo->commit_action();
4903
4904 } break;
4905 case Animation::TYPE_ROTATION_3D: {
4906 if (!root->has_node(animation->track_get_path(p_track))) {
4907 EditorNode::get_singleton()->show_warning(TTR("Track path is invalid, so can't add a key."));
4908 return;
4909 }
4910 Node3D *base = Object::cast_to<Node3D>(root->get_node(animation->track_get_path(p_track)));
4911
4912 if (!base) {
4913 EditorNode::get_singleton()->show_warning(TTR("Track is not of type Node3D, can't insert key"));
4914 return;
4915 }
4916
4917 Quaternion rot = base->get_transform().basis.operator Quaternion();
4918
4919 undo_redo->create_action(TTR("Add Rotation Key"));
4920 undo_redo->add_do_method(animation.ptr(), "rotation_track_insert_key", p_track, p_ofs, rot);
4921 undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs);
4922 undo_redo->commit_action();
4923
4924 } break;
4925 case Animation::TYPE_SCALE_3D: {
4926 if (!root->has_node(animation->track_get_path(p_track))) {
4927 EditorNode::get_singleton()->show_warning(TTR("Track path is invalid, so can't add a key."));
4928 return;
4929 }
4930 Node3D *base = Object::cast_to<Node3D>(root->get_node(animation->track_get_path(p_track)));
4931
4932 if (!base) {
4933 EditorNode::get_singleton()->show_warning(TTR("Track is not of type Node3D, can't insert key"));
4934 return;
4935 }
4936
4937 undo_redo->create_action(TTR("Add Scale Key"));
4938 undo_redo->add_do_method(animation.ptr(), "scale_track_insert_key", p_track, p_ofs, base->get_scale());
4939 undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs);
4940 undo_redo->commit_action();
4941
4942 } break;
4943 case Animation::TYPE_BLEND_SHAPE:
4944 case Animation::TYPE_VALUE: {
4945 NodePath bp;
4946 Variant value;
4947 _find_hint_for_track(p_track, bp, &value);
4948
4949 undo_redo->create_action(TTR("Add Track Key"));
4950 undo_redo->add_do_method(animation.ptr(), "track_insert_key", p_track, p_ofs, value);
4951 undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
4952 undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs);
4953 undo_redo->commit_action();
4954
4955 } break;
4956 case Animation::TYPE_METHOD: {
4957 if (!root->has_node(animation->track_get_path(p_track))) {
4958 EditorNode::get_singleton()->show_warning(TTR("Track path is invalid, so can't add a method key."));
4959 return;
4960 }
4961 Node *base = root->get_node(animation->track_get_path(p_track));
4962
4963 method_selector->select_method_from_instance(base);
4964
4965 insert_key_from_track_call_ofs = p_ofs;
4966 insert_key_from_track_call_track = p_track;
4967
4968 } break;
4969 case Animation::TYPE_BEZIER: {
4970 NodePath bp;
4971 Variant value;
4972 _find_hint_for_track(p_track, bp, &value);
4973 Array arr;
4974 arr.resize(5);
4975 arr[0] = value;
4976 arr[1] = -0.25;
4977 arr[2] = 0;
4978 arr[3] = 0.25;
4979 arr[4] = 0;
4980
4981 undo_redo->create_action(TTR("Add Track Key"));
4982 undo_redo->add_do_method(animation.ptr(), "track_insert_key", p_track, p_ofs, arr);
4983 undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs);
4984 undo_redo->commit_action();
4985
4986 } break;
4987 case Animation::TYPE_AUDIO: {
4988 Dictionary ak;
4989 ak["stream"] = Ref<Resource>();
4990 ak["start_offset"] = 0;
4991 ak["end_offset"] = 0;
4992
4993 undo_redo->create_action(TTR("Add Track Key"));
4994 undo_redo->add_do_method(animation.ptr(), "track_insert_key", p_track, p_ofs, ak);
4995 undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs);
4996 undo_redo->commit_action();
4997 } break;
4998 case Animation::TYPE_ANIMATION: {
4999 StringName anim = "[stop]";
5000
5001 undo_redo->create_action(TTR("Add Track Key"));
5002 undo_redo->add_do_method(animation.ptr(), "track_insert_key", p_track, p_ofs, anim);
5003 undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs);
5004 undo_redo->commit_action();
5005 } break;
5006 }
5007}
5008
5009void AnimationTrackEditor::_add_method_key(const String &p_method) {
5010 if (!root->has_node(animation->track_get_path(insert_key_from_track_call_track))) {
5011 EditorNode::get_singleton()->show_warning(TTR("Track path is invalid, so can't add a method key."));
5012 return;
5013 }
5014 Node *base = root->get_node(animation->track_get_path(insert_key_from_track_call_track));
5015
5016 List<MethodInfo> minfo;
5017 base->get_method_list(&minfo);
5018
5019 for (const MethodInfo &E : minfo) {
5020 if (E.name == p_method) {
5021 Dictionary d;
5022 d["method"] = p_method;
5023 Array params;
5024 int first_defarg = E.arguments.size() - E.default_arguments.size();
5025
5026 for (int i = 0; i < E.arguments.size(); i++) {
5027 if (i >= first_defarg) {
5028 Variant arg = E.default_arguments[i - first_defarg];
5029 params.push_back(arg);
5030 } else {
5031 Callable::CallError ce;
5032 Variant arg;
5033 Variant::construct(E.arguments[i].type, arg, nullptr, 0, ce);
5034 params.push_back(arg);
5035 }
5036 }
5037 d["args"] = params;
5038
5039 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
5040 undo_redo->create_action(TTR("Add Method Track Key"));
5041 undo_redo->add_do_method(animation.ptr(), "track_insert_key", insert_key_from_track_call_track, insert_key_from_track_call_ofs, d);
5042 undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", insert_key_from_track_call_track, insert_key_from_track_call_ofs);
5043 undo_redo->commit_action();
5044
5045 return;
5046 }
5047 }
5048
5049 EditorNode::get_singleton()->show_warning(TTR("Method not found in object:") + " " + p_method);
5050}
5051
5052void AnimationTrackEditor::_key_selected(int p_key, bool p_single, int p_track) {
5053 ERR_FAIL_INDEX(p_track, animation->get_track_count());
5054 ERR_FAIL_INDEX(p_key, animation->track_get_key_count(p_track));
5055
5056 SelectedKey sk;
5057 sk.key = p_key;
5058 sk.track = p_track;
5059
5060 if (p_single) {
5061 _clear_selection();
5062 }
5063
5064 KeyInfo ki;
5065 ki.pos = animation->track_get_key_time(p_track, p_key);
5066 selection[sk] = ki;
5067
5068 _redraw_tracks();
5069 _update_key_edit();
5070}
5071
5072void AnimationTrackEditor::_key_deselected(int p_key, int p_track) {
5073 ERR_FAIL_INDEX(p_track, animation->get_track_count());
5074 ERR_FAIL_INDEX(p_key, animation->track_get_key_count(p_track));
5075
5076 SelectedKey sk;
5077 sk.key = p_key;
5078 sk.track = p_track;
5079
5080 selection.erase(sk);
5081
5082 _redraw_tracks();
5083 _update_key_edit();
5084}
5085
5086void AnimationTrackEditor::_move_selection_begin() {
5087 moving_selection = true;
5088 moving_selection_offset = 0;
5089}
5090
5091void AnimationTrackEditor::_move_selection(float p_offset) {
5092 moving_selection_offset = p_offset;
5093 _redraw_tracks();
5094}
5095
5096struct _AnimMoveRestore {
5097 int track = 0;
5098 float time = 0;
5099 Variant key;
5100 float transition = 0;
5101};
5102// Used for undo/redo.
5103
5104void AnimationTrackEditor::_clear_key_edit() {
5105 if (key_edit) {
5106 // If key edit is the object being inspected, remove it first.
5107 if (InspectorDock::get_inspector_singleton()->get_edited_object() == key_edit) {
5108 EditorNode::get_singleton()->push_item(nullptr);
5109 }
5110
5111 // Then actually delete it.
5112 memdelete(key_edit);
5113 key_edit = nullptr;
5114 }
5115
5116 if (multi_key_edit) {
5117 if (InspectorDock::get_inspector_singleton()->get_edited_object() == multi_key_edit) {
5118 EditorNode::get_singleton()->push_item(nullptr);
5119 }
5120
5121 memdelete(multi_key_edit);
5122 multi_key_edit = nullptr;
5123 }
5124}
5125
5126void AnimationTrackEditor::_clear_selection(bool p_update) {
5127 selection.clear();
5128
5129 if (p_update) {
5130 _redraw_tracks();
5131 }
5132
5133 _clear_key_edit();
5134}
5135
5136void AnimationTrackEditor::_update_key_edit() {
5137 _clear_key_edit();
5138 if (!animation.is_valid()) {
5139 return;
5140 }
5141
5142 if (selection.size() == 1) {
5143 key_edit = memnew(AnimationTrackKeyEdit);
5144 key_edit->animation = animation;
5145 key_edit->animation_read_only = read_only;
5146 key_edit->track = selection.front()->key().track;
5147 key_edit->use_fps = timeline->is_using_fps();
5148
5149 int key_id = selection.front()->key().key;
5150 if (key_id >= animation->track_get_key_count(key_edit->track)) {
5151 _clear_key_edit();
5152 return; // Probably in the process of rearranging the keys.
5153 }
5154 float ofs = animation->track_get_key_time(key_edit->track, key_id);
5155 key_edit->key_ofs = ofs;
5156 key_edit->root_path = root;
5157
5158 NodePath np;
5159 key_edit->hint = _find_hint_for_track(key_edit->track, np);
5160 key_edit->base = np;
5161
5162 EditorNode::get_singleton()->push_item(key_edit);
5163 } else if (selection.size() > 1) {
5164 multi_key_edit = memnew(AnimationMultiTrackKeyEdit);
5165 multi_key_edit->animation = animation;
5166 multi_key_edit->animation_read_only = read_only;
5167
5168 RBMap<int, List<float>> key_ofs_map;
5169 RBMap<int, NodePath> base_map;
5170 int first_track = -1;
5171 for (const KeyValue<SelectedKey, KeyInfo> &E : selection) {
5172 int track = E.key.track;
5173 if (first_track < 0) {
5174 first_track = track;
5175 }
5176
5177 if (!key_ofs_map.has(track)) {
5178 key_ofs_map[track] = List<float>();
5179 base_map[track] = NodePath();
5180 }
5181
5182 int key_id = E.key.key;
5183 if (key_id >= animation->track_get_key_count(track)) {
5184 _clear_key_edit();
5185 return; // Probably in the process of rearranging the keys.
5186 }
5187 key_ofs_map[track].push_back(animation->track_get_key_time(track, E.key.key));
5188 }
5189 multi_key_edit->key_ofs_map = key_ofs_map;
5190 multi_key_edit->base_map = base_map;
5191 multi_key_edit->hint = _find_hint_for_track(first_track, base_map[first_track]);
5192 multi_key_edit->use_fps = timeline->is_using_fps();
5193 multi_key_edit->root_path = root;
5194
5195 EditorNode::get_singleton()->push_item(multi_key_edit);
5196 }
5197}
5198
5199void AnimationTrackEditor::_clear_selection_for_anim(const Ref<Animation> &p_anim) {
5200 if (animation != p_anim) {
5201 return;
5202 }
5203
5204 _clear_selection();
5205}
5206
5207void AnimationTrackEditor::_select_at_anim(const Ref<Animation> &p_anim, int p_track, float p_pos) {
5208 if (animation != p_anim) {
5209 return;
5210 }
5211
5212 int idx = animation->track_find_key(p_track, p_pos, Animation::FIND_MODE_APPROX);
5213 ERR_FAIL_COND(idx < 0);
5214
5215 SelectedKey sk;
5216 sk.track = p_track;
5217 sk.key = idx;
5218 KeyInfo ki;
5219 ki.pos = p_pos;
5220
5221 selection.insert(sk, ki);
5222 _update_key_edit();
5223}
5224
5225void AnimationTrackEditor::_move_selection_commit() {
5226 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
5227 undo_redo->create_action(TTR("Animation Move Keys"));
5228
5229 List<_AnimMoveRestore> to_restore;
5230
5231 float motion = moving_selection_offset;
5232 // 1 - remove the keys.
5233 for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
5234 undo_redo->add_do_method(animation.ptr(), "track_remove_key", E->key().track, E->key().key);
5235 }
5236 // 2 - Remove overlapped keys.
5237 for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
5238 float newtime = snap_time(E->get().pos + motion);
5239 int idx = animation->track_find_key(E->key().track, newtime, Animation::FIND_MODE_APPROX);
5240 if (idx == -1) {
5241 continue;
5242 }
5243 SelectedKey sk;
5244 sk.key = idx;
5245 sk.track = E->key().track;
5246 if (selection.has(sk)) {
5247 continue; // Already in selection, don't save.
5248 }
5249
5250 undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_time", E->key().track, newtime);
5251 _AnimMoveRestore amr;
5252
5253 amr.key = animation->track_get_key_value(E->key().track, idx);
5254 amr.track = E->key().track;
5255 amr.time = newtime;
5256 amr.transition = animation->track_get_key_transition(E->key().track, idx);
5257
5258 to_restore.push_back(amr);
5259 }
5260
5261 // 3 - Move the keys (Reinsert them).
5262 for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
5263 float newpos = snap_time(E->get().pos + motion);
5264 undo_redo->add_do_method(animation.ptr(), "track_insert_key", E->key().track, newpos, animation->track_get_key_value(E->key().track, E->key().key), animation->track_get_key_transition(E->key().track, E->key().key));
5265 }
5266
5267 // 4 - (Undo) Remove inserted keys.
5268 for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
5269 float newpos = snap_time(E->get().pos + motion);
5270 undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", E->key().track, newpos);
5271 }
5272
5273 // 5 - (Undo) Reinsert keys.
5274 for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
5275 undo_redo->add_undo_method(animation.ptr(), "track_insert_key", E->key().track, E->get().pos, animation->track_get_key_value(E->key().track, E->key().key), animation->track_get_key_transition(E->key().track, E->key().key));
5276 }
5277
5278 // 6 - (Undo) Reinsert overlapped keys.
5279 for (_AnimMoveRestore &amr : to_restore) {
5280 undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, amr.transition);
5281 }
5282
5283 undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);
5284 undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
5285
5286 // 7 - Reselect.
5287 for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
5288 float oldpos = E->get().pos;
5289 float newpos = snap_time(oldpos + motion);
5290
5291 undo_redo->add_do_method(this, "_select_at_anim", animation, E->key().track, newpos);
5292 undo_redo->add_undo_method(this, "_select_at_anim", animation, E->key().track, oldpos);
5293 }
5294
5295 moving_selection = false;
5296 undo_redo->add_do_method(this, "_redraw_tracks");
5297 undo_redo->add_undo_method(this, "_redraw_tracks");
5298 undo_redo->commit_action();
5299}
5300
5301void AnimationTrackEditor::_move_selection_cancel() {
5302 moving_selection = false;
5303 _redraw_tracks();
5304}
5305
5306bool AnimationTrackEditor::is_moving_selection() const {
5307 return moving_selection;
5308}
5309
5310float AnimationTrackEditor::get_moving_selection_offset() const {
5311 return moving_selection_offset;
5312}
5313
5314void AnimationTrackEditor::_box_selection_draw() {
5315 const Rect2 selection_rect = Rect2(Point2(), box_selection->get_size());
5316 box_selection->draw_rect(selection_rect, get_theme_color(SNAME("box_selection_fill_color"), EditorStringName(Editor)));
5317 box_selection->draw_rect(selection_rect, get_theme_color(SNAME("box_selection_stroke_color"), EditorStringName(Editor)), false, Math::round(EDSCALE));
5318}
5319
5320void AnimationTrackEditor::_scroll_input(const Ref<InputEvent> &p_event) {
5321 if (panner->gui_input(p_event)) {
5322 scroll->accept_event();
5323 return;
5324 }
5325
5326 Ref<InputEventMouseButton> mb = p_event;
5327
5328 if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) {
5329 if (mb->is_pressed()) {
5330 box_selecting = true;
5331 box_selecting_from = scroll->get_global_transform().xform(mb->get_position());
5332 box_select_rect = Rect2();
5333 } else if (box_selecting) {
5334 if (box_selection->is_visible_in_tree()) {
5335 // Only if moved.
5336 for (int i = 0; i < track_edits.size(); i++) {
5337 Rect2 local_rect = box_select_rect;
5338 local_rect.position -= track_edits[i]->get_global_position();
5339 track_edits[i]->append_to_selection(local_rect, mb->is_command_or_control_pressed());
5340 }
5341
5342 if (_get_track_selected() == -1 && track_edits.size() > 0) { // Minimal hack to make shortcuts work.
5343 track_edits[track_edits.size() - 1]->grab_focus();
5344 }
5345 } else {
5346 _clear_selection(true); // Clear it.
5347 }
5348
5349 box_selection->hide();
5350 box_selecting = false;
5351 }
5352 }
5353
5354 Ref<InputEventMouseMotion> mm = p_event;
5355
5356 if (mm.is_valid() && box_selecting) {
5357 if (!mm->get_button_mask().has_flag(MouseButtonMask::LEFT)) {
5358 // No longer.
5359 box_selection->hide();
5360 box_selecting = false;
5361 return;
5362 }
5363
5364 if (!box_selection->is_visible_in_tree()) {
5365 if (!mm->is_command_or_control_pressed() && !mm->is_shift_pressed()) {
5366 _clear_selection(true);
5367 }
5368 box_selection->show();
5369 }
5370
5371 Vector2 from = box_selecting_from;
5372 Vector2 to = scroll->get_global_transform().xform(mm->get_position());
5373
5374 if (from.x > to.x) {
5375 SWAP(from.x, to.x);
5376 }
5377
5378 if (from.y > to.y) {
5379 SWAP(from.y, to.y);
5380 }
5381
5382 Rect2 rect(from, to - from);
5383 Rect2 scroll_rect = Rect2(scroll->get_global_position(), scroll->get_size());
5384 rect = scroll_rect.intersection(rect);
5385 box_selection->set_position(rect.position);
5386 box_selection->set_size(rect.size);
5387
5388 box_select_rect = rect;
5389 }
5390}
5391
5392void AnimationTrackEditor::_toggle_bezier_edit() {
5393 if (bezier_edit->is_visible()) {
5394 _cancel_bezier_edit();
5395 } else {
5396 int track_count = animation->get_track_count();
5397 for (int i = 0; i < track_count; ++i) {
5398 if (animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) {
5399 _bezier_edit(i);
5400 return;
5401 }
5402 }
5403 }
5404}
5405
5406void AnimationTrackEditor::_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event) {
5407 Ref<InputEventWithModifiers> iewm = p_event;
5408 if (iewm.is_valid() && iewm->is_alt_pressed()) {
5409 if (p_scroll_vec.x < 0 || p_scroll_vec.y < 0) {
5410 goto_prev_step(true);
5411 } else {
5412 goto_next_step(true);
5413 }
5414 } else {
5415 timeline->set_value(timeline->get_value() - p_scroll_vec.x / timeline->get_zoom_scale());
5416 scroll->set_v_scroll(scroll->get_v_scroll() - p_scroll_vec.y);
5417 }
5418}
5419
5420void AnimationTrackEditor::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event) {
5421 double current_zoom_value = timeline->get_zoom()->get_value();
5422 timeline->get_zoom()->set_value(MAX(0.01, current_zoom_value * p_zoom_factor));
5423}
5424
5425void AnimationTrackEditor::_cancel_bezier_edit() {
5426 bezier_edit->hide();
5427 scroll->show();
5428 bezier_edit_icon->set_pressed(false);
5429}
5430
5431void AnimationTrackEditor::_bezier_edit(int p_for_track) {
5432 _clear_selection(); // Bezier probably wants to use a separate selection mode.
5433 bezier_edit->set_root(root);
5434 bezier_edit->set_animation_and_track(animation, p_for_track, read_only);
5435 scroll->hide();
5436 bezier_edit->show();
5437 // Search everything within the track and curve - edit it.
5438}
5439
5440void AnimationTrackEditor::_bezier_track_set_key_handle_mode(Animation *p_anim, int p_track, int p_index, Animation::HandleMode p_mode, Animation::HandleSetMode p_set_mode) {
5441 if (!p_anim) {
5442 return;
5443 }
5444 p_anim->bezier_track_set_key_handle_mode(p_track, p_index, p_mode, p_set_mode);
5445}
5446
5447void AnimationTrackEditor::_anim_duplicate_keys(bool transpose) {
5448 // Duplicait!
5449 if (selection.size() && animation.is_valid() && (!transpose || (_get_track_selected() >= 0 && _get_track_selected() < animation->get_track_count()))) {
5450 int top_track = 0x7FFFFFFF;
5451 float top_time = 1e10;
5452 for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
5453 const SelectedKey &sk = E->key();
5454
5455 float t = animation->track_get_key_time(sk.track, sk.key);
5456 if (t < top_time) {
5457 top_time = t;
5458 }
5459 if (sk.track < top_track) {
5460 top_track = sk.track;
5461 }
5462 }
5463 ERR_FAIL_COND(top_track == 0x7FFFFFFF || top_time == 1e10);
5464
5465 //
5466
5467 int start_track = transpose ? _get_track_selected() : top_track;
5468
5469 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
5470 undo_redo->create_action(TTR("Animation Duplicate Keys"));
5471
5472 List<Pair<int, float>> new_selection_values;
5473
5474 for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
5475 const SelectedKey &sk = E->key();
5476
5477 float t = animation->track_get_key_time(sk.track, sk.key);
5478
5479 float dst_time = t + (timeline->get_play_position() - top_time);
5480 int dst_track = sk.track + (start_track - top_track);
5481
5482 if (dst_track < 0 || dst_track >= animation->get_track_count()) {
5483 continue;
5484 }
5485
5486 if (animation->track_get_type(dst_track) != animation->track_get_type(sk.track)) {
5487 continue;
5488 }
5489
5490 int existing_idx = animation->track_find_key(dst_track, dst_time, Animation::FIND_MODE_APPROX);
5491
5492 undo_redo->add_do_method(animation.ptr(), "track_insert_key", dst_track, dst_time, animation->track_get_key_value(E->key().track, E->key().key), animation->track_get_key_transition(E->key().track, E->key().key));
5493 undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", dst_track, dst_time);
5494
5495 Pair<int, float> p;
5496 p.first = dst_track;
5497 p.second = dst_time;
5498 new_selection_values.push_back(p);
5499
5500 if (existing_idx != -1) {
5501 undo_redo->add_undo_method(animation.ptr(), "track_insert_key", dst_track, dst_time, animation->track_get_key_value(dst_track, existing_idx), animation->track_get_key_transition(dst_track, existing_idx));
5502 }
5503 }
5504
5505 undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);
5506 undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
5507
5508 // Reselect duplicated.
5509 RBMap<SelectedKey, KeyInfo> new_selection;
5510 for (const Pair<int, float> &E : new_selection_values) {
5511 undo_redo->add_do_method(this, "_select_at_anim", animation, E.first, E.second);
5512 }
5513 for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
5514 undo_redo->add_undo_method(this, "_select_at_anim", animation, E->key().track, E->get().pos);
5515 }
5516
5517 undo_redo->add_do_method(this, "_redraw_tracks");
5518 undo_redo->add_undo_method(this, "_redraw_tracks");
5519 undo_redo->commit_action();
5520 }
5521}
5522
5523void AnimationTrackEditor::_edit_menu_about_to_popup() {
5524 AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();
5525 edit->get_popup()->set_item_disabled(edit->get_popup()->get_item_index(EDIT_APPLY_RESET), !player->can_apply_reset());
5526}
5527
5528void AnimationTrackEditor::goto_prev_step(bool p_from_mouse_event) {
5529 if (animation.is_null()) {
5530 return;
5531 }
5532 float anim_step = animation->get_step();
5533 if (anim_step == 0) {
5534 anim_step = 1;
5535 }
5536 if (p_from_mouse_event && Input::get_singleton()->is_key_pressed(Key::SHIFT)) {
5537 // Use more precise snapping when holding Shift.
5538 // This is used when scrobbling the timeline using Alt + Mouse wheel.
5539 anim_step *= 0.25;
5540 }
5541
5542 float pos = timeline->get_play_position();
5543 pos = Math::snapped(pos - anim_step, anim_step);
5544 if (pos < 0) {
5545 pos = 0;
5546 }
5547 set_anim_pos(pos);
5548 emit_signal(SNAME("timeline_changed"), pos, true, false);
5549}
5550
5551void AnimationTrackEditor::goto_next_step(bool p_from_mouse_event, bool p_timeline_only) {
5552 if (animation.is_null()) {
5553 return;
5554 }
5555 float anim_step = animation->get_step();
5556 if (anim_step == 0) {
5557 anim_step = 1;
5558 }
5559 if (p_from_mouse_event && Input::get_singleton()->is_key_pressed(Key::SHIFT)) {
5560 // Use more precise snapping when holding Shift.
5561 // This is used when scrobbling the timeline using Alt + Mouse wheel.
5562 // Do not use precise snapping when using the menu action or keyboard shortcut,
5563 // as the default keyboard shortcut requires pressing Shift.
5564 anim_step *= 0.25;
5565 }
5566
5567 float pos = timeline->get_play_position();
5568
5569 pos = Math::snapped(pos + anim_step, anim_step);
5570 if (pos > animation->get_length()) {
5571 pos = animation->get_length();
5572 }
5573 set_anim_pos(pos);
5574
5575 emit_signal(SNAME("timeline_changed"), pos, true, p_timeline_only);
5576}
5577
5578void AnimationTrackEditor::_edit_menu_pressed(int p_option) {
5579 last_menu_track_opt = p_option;
5580 switch (p_option) {
5581 case EDIT_COPY_TRACKS: {
5582 track_copy_select->clear();
5583 TreeItem *troot = track_copy_select->create_item();
5584
5585 for (int i = 0; i < animation->get_track_count(); i++) {
5586 NodePath path = animation->track_get_path(i);
5587 Node *node = nullptr;
5588
5589 if (root && root->has_node(path)) {
5590 node = root->get_node(path);
5591 }
5592
5593 String text;
5594 Ref<Texture2D> icon = get_editor_theme_icon(SNAME("Node"));
5595 if (node) {
5596 if (has_theme_icon(node->get_class(), EditorStringName(EditorIcons))) {
5597 icon = get_editor_theme_icon(node->get_class());
5598 }
5599
5600 text = node->get_name();
5601 Vector<StringName> sn = path.get_subnames();
5602 for (int j = 0; j < sn.size(); j++) {
5603 text += ".";
5604 text += sn[j];
5605 }
5606
5607 path = NodePath(node->get_path().get_names(), path.get_subnames(), true); // Store full path instead for copying.
5608 } else {
5609 text = path;
5610 int sep = text.find(":");
5611 if (sep != -1) {
5612 text = text.substr(sep + 1, text.length());
5613 }
5614 }
5615
5616 String track_type;
5617 switch (animation->track_get_type(i)) {
5618 case Animation::TYPE_POSITION_3D:
5619 track_type = TTR("Position");
5620 break;
5621 case Animation::TYPE_ROTATION_3D:
5622 track_type = TTR("Rotation");
5623 break;
5624 case Animation::TYPE_SCALE_3D:
5625 track_type = TTR("Scale");
5626 break;
5627 case Animation::TYPE_BLEND_SHAPE:
5628 track_type = TTR("BlendShape");
5629 break;
5630 case Animation::TYPE_METHOD:
5631 track_type = TTR("Methods");
5632 break;
5633 case Animation::TYPE_BEZIER:
5634 track_type = TTR("Bezier");
5635 break;
5636 case Animation::TYPE_AUDIO:
5637 track_type = TTR("Audio");
5638 break;
5639 default: {
5640 };
5641 }
5642 if (!track_type.is_empty()) {
5643 text += vformat(" (%s)", track_type);
5644 }
5645
5646 TreeItem *it = track_copy_select->create_item(troot);
5647 it->set_editable(0, true);
5648 it->set_selectable(0, true);
5649 it->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
5650 it->set_icon(0, icon);
5651 it->set_text(0, text);
5652 Dictionary md;
5653 md["track_idx"] = i;
5654 md["path"] = path;
5655 it->set_metadata(0, md);
5656 }
5657
5658 track_copy_dialog->popup_centered(Size2(350, 500) * EDSCALE);
5659 } break;
5660 case EDIT_COPY_TRACKS_CONFIRM: {
5661 track_clipboard.clear();
5662 TreeItem *tree_root = track_copy_select->get_root();
5663 if (tree_root) {
5664 TreeItem *it = tree_root->get_first_child();
5665 while (it) {
5666 Dictionary md = it->get_metadata(0);
5667 int idx = md["track_idx"];
5668 if (it->is_checked(0) && idx >= 0 && idx < animation->get_track_count()) {
5669 TrackClipboard tc;
5670 tc.base_path = animation->track_get_path(idx);
5671 tc.full_path = md["path"];
5672 tc.track_type = animation->track_get_type(idx);
5673 tc.interp_type = animation->track_get_interpolation_type(idx);
5674 if (tc.track_type == Animation::TYPE_VALUE) {
5675 tc.update_mode = animation->value_track_get_update_mode(idx);
5676 }
5677 if (tc.track_type == Animation::TYPE_AUDIO) {
5678 tc.use_blend = animation->audio_track_is_use_blend(idx);
5679 }
5680 tc.loop_wrap = animation->track_get_interpolation_loop_wrap(idx);
5681 tc.enabled = animation->track_is_enabled(idx);
5682 for (int i = 0; i < animation->track_get_key_count(idx); i++) {
5683 TrackClipboard::Key k;
5684 k.time = animation->track_get_key_time(idx, i);
5685 k.value = animation->track_get_key_value(idx, i);
5686 k.transition = animation->track_get_key_transition(idx, i);
5687 tc.keys.push_back(k);
5688 }
5689 track_clipboard.push_back(tc);
5690 }
5691 it = it->get_next();
5692 }
5693 }
5694 } break;
5695 case EDIT_PASTE_TRACKS: {
5696 if (track_clipboard.size() == 0) {
5697 EditorNode::get_singleton()->show_warning(TTR("Clipboard is empty!"));
5698 break;
5699 }
5700
5701 int base_track = animation->get_track_count();
5702 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
5703 undo_redo->create_action(TTR("Paste Tracks"));
5704 for (int i = 0; i < track_clipboard.size(); i++) {
5705 undo_redo->add_do_method(animation.ptr(), "add_track", track_clipboard[i].track_type);
5706 Node *exists = nullptr;
5707 NodePath path = track_clipboard[i].base_path;
5708
5709 if (root) {
5710 NodePath np = track_clipboard[i].full_path;
5711 exists = root->get_node(np);
5712 if (exists) {
5713 path = NodePath(root->get_path_to(exists).get_names(), track_clipboard[i].full_path.get_subnames(), false);
5714 }
5715 }
5716
5717 undo_redo->add_do_method(animation.ptr(), "track_set_path", base_track, path);
5718 undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_type", base_track, track_clipboard[i].interp_type);
5719 undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_loop_wrap", base_track, track_clipboard[i].loop_wrap);
5720 undo_redo->add_do_method(animation.ptr(), "track_set_enabled", base_track, track_clipboard[i].enabled);
5721 if (track_clipboard[i].track_type == Animation::TYPE_VALUE) {
5722 undo_redo->add_do_method(animation.ptr(), "value_track_set_update_mode", base_track, track_clipboard[i].update_mode);
5723 }
5724 if (track_clipboard[i].track_type == Animation::TYPE_AUDIO) {
5725 undo_redo->add_do_method(animation.ptr(), "audio_track_set_use_blend", base_track, track_clipboard[i].use_blend);
5726 }
5727
5728 for (int j = 0; j < track_clipboard[i].keys.size(); j++) {
5729 undo_redo->add_do_method(animation.ptr(), "track_insert_key", base_track, track_clipboard[i].keys[j].time, track_clipboard[i].keys[j].value, track_clipboard[i].keys[j].transition);
5730 }
5731
5732 undo_redo->add_undo_method(animation.ptr(), "remove_track", animation->get_track_count());
5733
5734 base_track++;
5735 }
5736
5737 undo_redo->commit_action();
5738 } break;
5739
5740 case EDIT_SCALE_SELECTION:
5741 case EDIT_SCALE_FROM_CURSOR: {
5742 scale_dialog->popup_centered(Size2(200, 100) * EDSCALE);
5743 } break;
5744 case EDIT_SCALE_CONFIRM: {
5745 if (selection.is_empty()) {
5746 return;
5747 }
5748
5749 float from_t = 1e20;
5750 float to_t = -1e20;
5751 float len = -1e20;
5752 float pivot = 0;
5753
5754 for (const KeyValue<SelectedKey, KeyInfo> &E : selection) {
5755 float t = animation->track_get_key_time(E.key.track, E.key.key);
5756 if (t < from_t) {
5757 from_t = t;
5758 }
5759 if (t > to_t) {
5760 to_t = t;
5761 }
5762 }
5763
5764 len = to_t - from_t;
5765 if (last_menu_track_opt == EDIT_SCALE_FROM_CURSOR) {
5766 pivot = timeline->get_play_position();
5767
5768 } else {
5769 pivot = from_t;
5770 }
5771
5772 float s = scale->get_value();
5773 ERR_FAIL_COND_MSG(s == 0, "Can't scale to 0.");
5774
5775 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
5776 undo_redo->create_action(TTR("Animation Scale Keys"));
5777
5778 List<_AnimMoveRestore> to_restore;
5779
5780 // 1 - Remove the keys.
5781 for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
5782 undo_redo->add_do_method(animation.ptr(), "track_remove_key", E->key().track, E->key().key);
5783 }
5784 // 2 - Remove overlapped keys.
5785 for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
5786 float newtime = (E->get().pos - from_t) * s + from_t;
5787 int idx = animation->track_find_key(E->key().track, newtime, Animation::FIND_MODE_APPROX);
5788 if (idx == -1) {
5789 continue;
5790 }
5791 SelectedKey sk;
5792 sk.key = idx;
5793 sk.track = E->key().track;
5794 if (selection.has(sk)) {
5795 continue; // Already in selection, don't save.
5796 }
5797
5798 undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_time", E->key().track, newtime);
5799 _AnimMoveRestore amr;
5800
5801 amr.key = animation->track_get_key_value(E->key().track, idx);
5802 amr.track = E->key().track;
5803 amr.time = newtime;
5804 amr.transition = animation->track_get_key_transition(E->key().track, idx);
5805
5806 to_restore.push_back(amr);
5807 }
5808
5809#define NEW_POS(m_ofs) (((s > 0) ? m_ofs : from_t + (len - (m_ofs - from_t))) - pivot) * ABS(s) + from_t
5810 // 3 - Move the keys (re insert them).
5811 for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
5812 float newpos = NEW_POS(E->get().pos);
5813 undo_redo->add_do_method(animation.ptr(), "track_insert_key", E->key().track, newpos, animation->track_get_key_value(E->key().track, E->key().key), animation->track_get_key_transition(E->key().track, E->key().key));
5814 }
5815
5816 // 4 - (Undo) Remove inserted keys.
5817 for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
5818 float newpos = NEW_POS(E->get().pos);
5819 undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", E->key().track, newpos);
5820 }
5821
5822 // 5 - (Undo) Reinsert keys.
5823 for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
5824 undo_redo->add_undo_method(animation.ptr(), "track_insert_key", E->key().track, E->get().pos, animation->track_get_key_value(E->key().track, E->key().key), animation->track_get_key_transition(E->key().track, E->key().key));
5825 }
5826
5827 // 6 - (Undo) Reinsert overlapped keys.
5828 for (_AnimMoveRestore &amr : to_restore) {
5829 undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, amr.transition);
5830 }
5831
5832 undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);
5833 undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
5834
5835 // 7-reselect.
5836 for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
5837 float oldpos = E->get().pos;
5838 float newpos = NEW_POS(oldpos);
5839 if (newpos >= 0) {
5840 undo_redo->add_do_method(this, "_select_at_anim", animation, E->key().track, newpos);
5841 }
5842 undo_redo->add_undo_method(this, "_select_at_anim", animation, E->key().track, oldpos);
5843 }
5844#undef NEW_POS
5845
5846 undo_redo->add_do_method(this, "_redraw_tracks");
5847 undo_redo->add_undo_method(this, "_redraw_tracks");
5848 undo_redo->commit_action();
5849 } break;
5850
5851 case EDIT_EASE_SELECTION: {
5852 ease_dialog->popup_centered(Size2(200, 100) * EDSCALE);
5853 } break;
5854 case EDIT_EASE_CONFIRM: {
5855 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
5856 undo_redo->create_action(TTR("Make Easing Keys"));
5857
5858 Tween::TransitionType transition_type = static_cast<Tween::TransitionType>(transition_selection->get_selected_id());
5859 Tween::EaseType ease_type = static_cast<Tween::EaseType>(ease_selection->get_selected_id());
5860 float fps = ease_fps->get_value();
5861 double dur_step = 1.0 / fps;
5862
5863 // Organize track and key.
5864 HashMap<int, Vector<int>> keymap;
5865 Vector<int> tracks;
5866 for (const KeyValue<SelectedKey, KeyInfo> &E : selection) {
5867 if (!tracks.has(E.key.track)) {
5868 tracks.append(E.key.track);
5869 }
5870 }
5871 for (int i = 0; i < tracks.size(); i++) {
5872 switch (animation->track_get_type(tracks[i])) {
5873 case Animation::TYPE_VALUE:
5874 case Animation::TYPE_POSITION_3D:
5875 case Animation::TYPE_ROTATION_3D:
5876 case Animation::TYPE_SCALE_3D:
5877 case Animation::TYPE_BLEND_SHAPE: {
5878 Vector<int> keys;
5879 for (const KeyValue<SelectedKey, KeyInfo> &E : selection) {
5880 if (E.key.track == tracks[i]) {
5881 keys.append(E.key.key);
5882 }
5883 }
5884 keys.sort();
5885 keymap.insert(tracks[i], keys);
5886 } break;
5887 default: {
5888 } break;
5889 }
5890 }
5891
5892 // Make easing.
5893 HashMap<int, Vector<int>>::Iterator E = keymap.begin();
5894 while (E) {
5895 int track = E->key;
5896 Vector<int> keys = E->value;
5897 int len = keys.size() - 1;
5898
5899 // Special case for angle interpolation.
5900 bool is_using_angle = animation->track_get_interpolation_type(track) == Animation::INTERPOLATION_LINEAR_ANGLE || animation->track_get_interpolation_type(track) == Animation::INTERPOLATION_CUBIC_ANGLE;
5901
5902 // Make insert queue.
5903 Vector<Pair<real_t, Variant>> insert_queue_new;
5904 for (int i = 0; i < len; i++) {
5905 // Check neighboring keys.
5906 if (keys[i] + 1 == keys[i + 1]) {
5907 double from_t = animation->track_get_key_time(track, keys[i]);
5908 double to_t = animation->track_get_key_time(track, keys[i + 1]);
5909 Variant from_v = animation->track_get_key_value(track, keys[i]);
5910 Variant to_v = animation->track_get_key_value(track, keys[i + 1]);
5911 if (is_using_angle) {
5912 real_t a = from_v;
5913 real_t b = to_v;
5914 real_t to_diff = fmod(b - a, Math_TAU);
5915 to_v = a + fmod(2.0 * to_diff, Math_TAU) - to_diff;
5916 }
5917 Variant delta_v = Animation::subtract_variant(to_v, from_v);
5918 double duration = to_t - from_t;
5919 double fixed_duration = duration - UNIT_EPSILON; // Prevent to overwrap keys...
5920 for (double delta_t = dur_step; delta_t < fixed_duration; delta_t += dur_step) {
5921 Pair<real_t, Variant> keydata;
5922 keydata.first = from_t + delta_t;
5923 keydata.second = Tween::interpolate_variant(from_v, delta_v, delta_t, duration, transition_type, ease_type);
5924 insert_queue_new.append(keydata);
5925 }
5926 }
5927 }
5928
5929 // Do insertion.
5930 for (int i = 0; i < insert_queue_new.size(); i++) {
5931 undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, insert_queue_new[i].first, insert_queue_new[i].second);
5932 undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", track, insert_queue_new[i].first);
5933 }
5934
5935 ++E;
5936 }
5937
5938 undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);
5939 undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
5940 undo_redo->add_do_method(this, "_redraw_tracks");
5941 undo_redo->add_undo_method(this, "_redraw_tracks");
5942 undo_redo->commit_action();
5943 _update_key_edit();
5944
5945 } break;
5946
5947 case EDIT_DUPLICATE_SELECTION: {
5948 if (bezier_edit->is_visible()) {
5949 bezier_edit->duplicate_selection();
5950 break;
5951 }
5952 _anim_duplicate_keys(false);
5953 } break;
5954 case EDIT_DUPLICATE_TRANSPOSED: {
5955 if (bezier_edit->is_visible()) {
5956 EditorNode::get_singleton()->show_warning(TTR("This option does not work for Bezier editing, as it's only a single track."));
5957 break;
5958 }
5959 _anim_duplicate_keys(true);
5960 } break;
5961 case EDIT_ADD_RESET_KEY: {
5962 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
5963 undo_redo->create_action(TTR("Animation Add RESET Keys"));
5964
5965 Ref<Animation> reset = _create_and_get_reset_animation();
5966 int reset_tracks = reset->get_track_count();
5967 HashSet<int> tracks_added;
5968
5969 for (const KeyValue<SelectedKey, KeyInfo> &E : selection) {
5970 const SelectedKey &sk = E.key;
5971
5972 // Only add one key per track.
5973 if (tracks_added.has(sk.track)) {
5974 continue;
5975 }
5976 tracks_added.insert(sk.track);
5977
5978 int dst_track = -1;
5979
5980 const NodePath &path = animation->track_get_path(sk.track);
5981 for (int i = 0; i < reset->get_track_count(); i++) {
5982 if (reset->track_get_path(i) == path) {
5983 dst_track = i;
5984 break;
5985 }
5986 }
5987
5988 int existing_idx = -1;
5989 if (dst_track == -1) {
5990 // If adding multiple tracks, make sure that correct track is referenced.
5991 dst_track = reset_tracks;
5992 reset_tracks++;
5993
5994 undo_redo->add_do_method(reset.ptr(), "add_track", animation->track_get_type(sk.track));
5995 undo_redo->add_do_method(reset.ptr(), "track_set_path", dst_track, path);
5996 undo_redo->add_undo_method(reset.ptr(), "remove_track", dst_track);
5997 } else {
5998 existing_idx = reset->track_find_key(dst_track, 0, Animation::FIND_MODE_APPROX);
5999 }
6000
6001 undo_redo->add_do_method(reset.ptr(), "track_insert_key", dst_track, 0, animation->track_get_key_value(sk.track, sk.key), animation->track_get_key_transition(sk.track, sk.key));
6002 undo_redo->add_undo_method(reset.ptr(), "track_remove_key_at_time", dst_track, 0);
6003
6004 if (existing_idx != -1) {
6005 undo_redo->add_undo_method(reset.ptr(), "track_insert_key", dst_track, 0, reset->track_get_key_value(dst_track, existing_idx), reset->track_get_key_transition(dst_track, existing_idx));
6006 }
6007 }
6008
6009 undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);
6010 undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
6011 undo_redo->add_do_method(this, "_redraw_tracks");
6012 undo_redo->add_undo_method(this, "_redraw_tracks");
6013 undo_redo->commit_action();
6014
6015 } break;
6016 case EDIT_DELETE_SELECTION: {
6017 if (bezier_edit->is_visible()) {
6018 bezier_edit->delete_selection();
6019 break;
6020 }
6021
6022 if (selection.size()) {
6023 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
6024 undo_redo->create_action(TTR("Animation Delete Keys"));
6025
6026 for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
6027 undo_redo->add_do_method(animation.ptr(), "track_remove_key", E->key().track, E->key().key);
6028 undo_redo->add_undo_method(animation.ptr(), "track_insert_key", E->key().track, E->get().pos, animation->track_get_key_value(E->key().track, E->key().key), animation->track_get_key_transition(E->key().track, E->key().key));
6029 }
6030 undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);
6031 undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
6032 undo_redo->add_do_method(this, "_redraw_tracks");
6033 undo_redo->add_undo_method(this, "_redraw_tracks");
6034 undo_redo->commit_action();
6035 _update_key_edit();
6036 }
6037 } break;
6038 case EDIT_GOTO_NEXT_STEP_TIMELINE_ONLY:
6039 case EDIT_GOTO_NEXT_STEP: {
6040 goto_next_step(false, p_option == EDIT_GOTO_NEXT_STEP_TIMELINE_ONLY);
6041 } break;
6042 case EDIT_GOTO_PREV_STEP: {
6043 goto_prev_step(false);
6044 } break;
6045
6046 case EDIT_APPLY_RESET: {
6047 AnimationPlayerEditor::get_singleton()->get_player()->apply_reset(true);
6048 } break;
6049
6050 case EDIT_BAKE_ANIMATION: {
6051 bake_dialog->popup_centered(Size2(200, 100) * EDSCALE);
6052 } break;
6053 case EDIT_BAKE_ANIMATION_CONFIRM: {
6054 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
6055 undo_redo->create_action(TTR("Bake Animation as Linear Keys"));
6056
6057 int track_len = animation->get_track_count();
6058 bool b_trs = bake_trs->is_pressed();
6059 bool b_bs = bake_blendshape->is_pressed();
6060 bool b_v = bake_value->is_pressed();
6061
6062 double anim_len = animation->get_length() + CMP_EPSILON; // For end key.
6063 float fps = bake_fps->get_value();
6064 double dur_step = 1.0 / fps;
6065
6066 for (int i = 0; i < track_len; i++) {
6067 bool do_bake = false;
6068 Animation::TrackType type = animation->track_get_type(i);
6069 do_bake |= b_trs && (type == Animation::TYPE_POSITION_3D || type == Animation::TYPE_ROTATION_3D || type == Animation::TYPE_SCALE_3D);
6070 do_bake |= b_bs && type == Animation::TYPE_BLEND_SHAPE;
6071 do_bake |= b_v && type == Animation::TYPE_VALUE;
6072 if (do_bake && !animation->track_is_compressed(i)) {
6073 Animation::InterpolationType it = animation->track_get_interpolation_type(i);
6074 if (it == Animation::INTERPOLATION_NEAREST) {
6075 continue; // Nearest interpolation cannot be baked.
6076 }
6077
6078 // Special case for angle interpolation.
6079 bool is_using_angle = it == Animation::INTERPOLATION_LINEAR_ANGLE || it == Animation::INTERPOLATION_CUBIC_ANGLE;
6080
6081 // Make insert queue.
6082 Vector<Pair<real_t, Variant>> insert_queue_new;
6083
6084 switch (type) {
6085 case Animation::TYPE_POSITION_3D: {
6086 for (double delta_t = 0.0; delta_t <= anim_len; delta_t += dur_step) {
6087 Pair<real_t, Variant> keydata;
6088 keydata.first = delta_t;
6089 Vector3 v;
6090 animation->try_position_track_interpolate(i, delta_t, &v);
6091 keydata.second = v;
6092 insert_queue_new.append(keydata);
6093 }
6094 } break;
6095 case Animation::TYPE_ROTATION_3D: {
6096 for (double delta_t = 0.0; delta_t <= anim_len; delta_t += dur_step) {
6097 Pair<real_t, Variant> keydata;
6098 keydata.first = delta_t;
6099 Quaternion v;
6100 animation->try_rotation_track_interpolate(i, delta_t, &v);
6101 keydata.second = v;
6102 insert_queue_new.append(keydata);
6103 }
6104 } break;
6105 case Animation::TYPE_SCALE_3D: {
6106 for (double delta_t = 0.0; delta_t <= anim_len; delta_t += dur_step) {
6107 Pair<real_t, Variant> keydata;
6108 keydata.first = delta_t;
6109 Vector3 v;
6110 animation->try_scale_track_interpolate(i, delta_t, &v);
6111 keydata.second = v;
6112 insert_queue_new.append(keydata);
6113 }
6114 } break;
6115 case Animation::TYPE_BLEND_SHAPE: {
6116 for (double delta_t = 0.0; delta_t <= anim_len; delta_t += dur_step) {
6117 Pair<real_t, Variant> keydata;
6118 keydata.first = delta_t;
6119 float v;
6120 animation->try_blend_shape_track_interpolate(i, delta_t, &v);
6121 keydata.second = v;
6122 insert_queue_new.append(keydata);
6123 }
6124 } break;
6125 case Animation::TYPE_VALUE: {
6126 for (double delta_t = 0.0; delta_t < anim_len; delta_t += dur_step) {
6127 Pair<real_t, Variant> keydata;
6128 keydata.first = delta_t;
6129 keydata.second = animation->value_track_interpolate(i, delta_t);
6130 insert_queue_new.append(keydata);
6131 }
6132 } break;
6133 default: {
6134 } break;
6135 }
6136
6137 // Cleanup keys.
6138 int key_len = animation->track_get_key_count(i);
6139 for (int j = key_len - 1; j >= 0; j--) {
6140 undo_redo->add_do_method(animation.ptr(), "track_remove_key", i, j);
6141 }
6142
6143 // Insert keys.
6144 undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_type", i, is_using_angle ? Animation::INTERPOLATION_LINEAR_ANGLE : Animation::INTERPOLATION_LINEAR);
6145 for (int j = insert_queue_new.size() - 1; j >= 0; j--) {
6146 undo_redo->add_do_method(animation.ptr(), "track_insert_key", i, insert_queue_new[j].first, insert_queue_new[j].second);
6147 undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", i, insert_queue_new[j].first);
6148 }
6149
6150 // Undo methods.
6151 undo_redo->add_undo_method(animation.ptr(), "track_set_interpolation_type", i, animation->track_get_interpolation_type(i));
6152 for (int j = key_len - 1; j >= 0; j--) {
6153 undo_redo->add_undo_method(animation.ptr(), "track_insert_key", i, animation->track_get_key_time(i, j), animation->track_get_key_value(i, j), animation->track_get_key_transition(i, j));
6154 }
6155 }
6156 }
6157
6158 undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);
6159 undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
6160 undo_redo->add_do_method(this, "_redraw_tracks");
6161 undo_redo->add_undo_method(this, "_redraw_tracks");
6162 undo_redo->commit_action();
6163 _update_key_edit();
6164
6165 } break;
6166
6167 case EDIT_OPTIMIZE_ANIMATION: {
6168 optimize_dialog->popup_centered(Size2(250, 180) * EDSCALE);
6169
6170 } break;
6171 case EDIT_OPTIMIZE_ANIMATION_CONFIRM: {
6172 animation->optimize(optimize_velocity_error->get_value(), optimize_angular_error->get_value(), optimize_precision_error->get_value());
6173 _redraw_tracks();
6174 _update_key_edit();
6175 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
6176 undo_redo->clear_history(true, undo_redo->get_history_id_for_object(animation.ptr()));
6177 undo_redo->clear_history(true, undo_redo->get_history_id_for_object(this));
6178
6179 } break;
6180 case EDIT_CLEAN_UP_ANIMATION: {
6181 cleanup_dialog->popup_centered(Size2(300, 0) * EDSCALE);
6182
6183 } break;
6184 case EDIT_CLEAN_UP_ANIMATION_CONFIRM: {
6185 if (cleanup_all->is_pressed()) {
6186 List<StringName> names;
6187 AnimationPlayerEditor::get_singleton()->get_player()->get_animation_list(&names);
6188 for (const StringName &E : names) {
6189 _cleanup_animation(AnimationPlayerEditor::get_singleton()->get_player()->get_animation(E));
6190 }
6191 } else {
6192 _cleanup_animation(animation);
6193 }
6194
6195 } break;
6196 }
6197}
6198
6199void AnimationTrackEditor::_cleanup_animation(Ref<Animation> p_animation) {
6200 for (int i = 0; i < p_animation->get_track_count(); i++) {
6201 bool prop_exists = false;
6202 Variant::Type valid_type = Variant::NIL;
6203 Object *obj = nullptr;
6204
6205 Ref<Resource> res;
6206 Vector<StringName> leftover_path;
6207
6208 Node *node = root->get_node_and_resource(p_animation->track_get_path(i), res, leftover_path);
6209
6210 if (res.is_valid()) {
6211 obj = res.ptr();
6212 } else if (node) {
6213 obj = node;
6214 }
6215
6216 if (obj && p_animation->track_get_type(i) == Animation::TYPE_VALUE) {
6217 valid_type = obj->get_static_property_type_indexed(leftover_path, &prop_exists);
6218 }
6219
6220 if (!obj && cleanup_tracks->is_pressed()) {
6221 p_animation->remove_track(i);
6222 i--;
6223 continue;
6224 }
6225
6226 if (!prop_exists || p_animation->track_get_type(i) != Animation::TYPE_VALUE || !cleanup_keys->is_pressed()) {
6227 continue;
6228 }
6229
6230 for (int j = 0; j < p_animation->track_get_key_count(i); j++) {
6231 Variant v = p_animation->track_get_key_value(i, j);
6232
6233 if (!Variant::can_convert(v.get_type(), valid_type)) {
6234 p_animation->track_remove_key(i, j);
6235 j--;
6236 }
6237 }
6238
6239 if (p_animation->track_get_key_count(i) == 0 && cleanup_tracks->is_pressed()) {
6240 p_animation->remove_track(i);
6241 i--;
6242 }
6243 }
6244
6245 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
6246 undo_redo->clear_history(true, undo_redo->get_history_id_for_object(animation.ptr()));
6247 undo_redo->clear_history(true, undo_redo->get_history_id_for_object(this));
6248 _update_tracks();
6249}
6250
6251void AnimationTrackEditor::_view_group_toggle() {
6252 _update_tracks();
6253 view_group->set_icon(get_editor_theme_icon(view_group->is_pressed() ? SNAME("AnimationTrackList") : SNAME("AnimationTrackGroup")));
6254 bezier_edit->set_filtered(selected_filter->is_pressed());
6255}
6256
6257bool AnimationTrackEditor::is_grouping_tracks() {
6258 if (!view_group) {
6259 return false;
6260 }
6261
6262 return !view_group->is_pressed();
6263}
6264
6265void AnimationTrackEditor::_selection_changed() {
6266 if (selected_filter->is_pressed()) {
6267 _update_tracks(); // Needs updatin.
6268 } else {
6269 _redraw_tracks();
6270 _redraw_groups();
6271 }
6272}
6273
6274float AnimationTrackEditor::snap_time(float p_value, bool p_relative) {
6275 if (is_snap_enabled()) {
6276 double snap_increment;
6277 if (timeline->is_using_fps() && step->get_value() > 0) {
6278 snap_increment = 1.0 / step->get_value();
6279 } else {
6280 snap_increment = step->get_value();
6281 }
6282
6283 if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) {
6284 // Use more precise snapping when holding Shift.
6285 snap_increment *= 0.25;
6286 }
6287
6288 if (p_relative) {
6289 double rel = Math::fmod(timeline->get_value(), snap_increment);
6290 p_value = Math::snapped(p_value + rel, snap_increment) - rel;
6291 } else {
6292 p_value = Math::snapped(p_value, snap_increment);
6293 }
6294 }
6295
6296 return p_value;
6297}
6298
6299void AnimationTrackEditor::_show_imported_anim_warning() {
6300 // It looks terrible on a single line but the TTR extractor doesn't support line breaks yet.
6301 EditorNode::get_singleton()->show_warning(
6302 TTR("This animation belongs to an imported scene, so changes to imported tracks will not be saved.\n\nTo modify this animation, navigate to the scene's Advanced Import settings and select the animation.\nSome options, including looping, are available here. To add custom tracks, enable \"Save To File\" and\n\"Keep Custom Tracks\"."),
6303 TTR("Warning: Editing imported animation"));
6304}
6305
6306void AnimationTrackEditor::_select_all_tracks_for_copy() {
6307 TreeItem *track = track_copy_select->get_root()->get_first_child();
6308 if (!track) {
6309 return;
6310 }
6311
6312 bool all_selected = true;
6313 while (track) {
6314 if (!track->is_checked(0)) {
6315 all_selected = false;
6316 }
6317
6318 track = track->get_next();
6319 }
6320
6321 track = track_copy_select->get_root()->get_first_child();
6322 while (track) {
6323 track->set_checked(0, !all_selected);
6324 track = track->get_next();
6325 }
6326}
6327
6328void AnimationTrackEditor::_bind_methods() {
6329 ClassDB::bind_method("_animation_update", &AnimationTrackEditor::_animation_update);
6330 ClassDB::bind_method("_track_grab_focus", &AnimationTrackEditor::_track_grab_focus);
6331 ClassDB::bind_method("_redraw_tracks", &AnimationTrackEditor::_redraw_tracks);
6332 ClassDB::bind_method("_clear_selection_for_anim", &AnimationTrackEditor::_clear_selection_for_anim);
6333 ClassDB::bind_method("_select_at_anim", &AnimationTrackEditor::_select_at_anim);
6334 ClassDB::bind_method("_clear_selection", &AnimationTrackEditor::_clear_selection);
6335
6336 ClassDB::bind_method(D_METHOD("_bezier_track_set_key_handle_mode", "animation", "track_idx", "key_idx", "key_handle_mode", "key_handle_set_mode"), &AnimationTrackEditor::_bezier_track_set_key_handle_mode, DEFVAL(Animation::HANDLE_SET_MODE_NONE));
6337
6338 ADD_SIGNAL(MethodInfo("timeline_changed", PropertyInfo(Variant::FLOAT, "position"), PropertyInfo(Variant::BOOL, "drag"), PropertyInfo(Variant::BOOL, "timeline_only")));
6339 ADD_SIGNAL(MethodInfo("keying_changed"));
6340 ADD_SIGNAL(MethodInfo("animation_len_changed", PropertyInfo(Variant::FLOAT, "len")));
6341 ADD_SIGNAL(MethodInfo("animation_step_changed", PropertyInfo(Variant::FLOAT, "step")));
6342}
6343
6344void AnimationTrackEditor::_pick_track_filter_text_changed(const String &p_newtext) {
6345 TreeItem *root_item = pick_track->get_scene_tree()->get_scene_tree()->get_root();
6346
6347 Vector<Node *> select_candidates;
6348 Node *to_select = nullptr;
6349
6350 String filter = pick_track->get_filter_line_edit()->get_text();
6351
6352 _pick_track_select_recursive(root_item, filter, select_candidates);
6353
6354 if (!select_candidates.is_empty()) {
6355 for (int i = 0; i < select_candidates.size(); ++i) {
6356 Node *candidate = select_candidates[i];
6357
6358 if (((String)candidate->get_name()).to_lower().begins_with(filter.to_lower())) {
6359 to_select = candidate;
6360 break;
6361 }
6362 }
6363
6364 if (!to_select) {
6365 to_select = select_candidates[0];
6366 }
6367 }
6368
6369 pick_track->get_scene_tree()->set_selected(to_select);
6370}
6371
6372void AnimationTrackEditor::_pick_track_select_recursive(TreeItem *p_item, const String &p_filter, Vector<Node *> &p_select_candidates) {
6373 if (!p_item) {
6374 return;
6375 }
6376
6377 NodePath np = p_item->get_metadata(0);
6378 Node *node = get_node(np);
6379
6380 if (!p_filter.is_empty() && ((String)node->get_name()).findn(p_filter) != -1) {
6381 p_select_candidates.push_back(node);
6382 }
6383
6384 TreeItem *c = p_item->get_first_child();
6385
6386 while (c) {
6387 _pick_track_select_recursive(c, p_filter, p_select_candidates);
6388 c = c->get_next();
6389 }
6390}
6391
6392void AnimationTrackEditor::_pick_track_filter_input(const Ref<InputEvent> &p_ie) {
6393 Ref<InputEventKey> k = p_ie;
6394
6395 if (k.is_valid()) {
6396 switch (k->get_keycode()) {
6397 case Key::UP:
6398 case Key::DOWN:
6399 case Key::PAGEUP:
6400 case Key::PAGEDOWN: {
6401 pick_track->get_scene_tree()->get_scene_tree()->gui_input(k);
6402 pick_track->get_filter_line_edit()->accept_event();
6403 } break;
6404 default:
6405 break;
6406 }
6407 }
6408}
6409
6410AnimationTrackEditor::AnimationTrackEditor() {
6411 main_panel = memnew(PanelContainer);
6412 main_panel->set_focus_mode(FOCUS_ALL); // Allow panel to have focus so that shortcuts work as expected.
6413 add_child(main_panel);
6414 main_panel->set_v_size_flags(SIZE_EXPAND_FILL);
6415 HBoxContainer *timeline_scroll = memnew(HBoxContainer);
6416 main_panel->add_child(timeline_scroll);
6417 timeline_scroll->set_v_size_flags(SIZE_EXPAND_FILL);
6418
6419 VBoxContainer *timeline_vbox = memnew(VBoxContainer);
6420 timeline_scroll->add_child(timeline_vbox);
6421 timeline_vbox->set_v_size_flags(SIZE_EXPAND_FILL);
6422 timeline_vbox->set_h_size_flags(SIZE_EXPAND_FILL);
6423 timeline_vbox->add_theme_constant_override("separation", 0);
6424
6425 info_message = memnew(Label);
6426 info_message->set_text(TTR("Select an AnimationPlayer node to create and edit animations."));
6427 info_message->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
6428 info_message->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
6429 info_message->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART);
6430 info_message->set_custom_minimum_size(Size2(100 * EDSCALE, 0));
6431 info_message->set_anchors_and_offsets_preset(PRESET_FULL_RECT, PRESET_MODE_KEEP_SIZE, 8 * EDSCALE);
6432 main_panel->add_child(info_message);
6433
6434 timeline = memnew(AnimationTimelineEdit);
6435 timeline_vbox->add_child(timeline);
6436 timeline->connect("timeline_changed", callable_mp(this, &AnimationTrackEditor::_timeline_changed));
6437 timeline->connect("name_limit_changed", callable_mp(this, &AnimationTrackEditor::_name_limit_changed));
6438 timeline->connect("track_added", callable_mp(this, &AnimationTrackEditor::_add_track));
6439 timeline->connect("value_changed", callable_mp(this, &AnimationTrackEditor::_timeline_value_changed));
6440 timeline->connect("length_changed", callable_mp(this, &AnimationTrackEditor::_update_length));
6441
6442 panner.instantiate();
6443 panner->set_callbacks(callable_mp(this, &AnimationTrackEditor::_pan_callback), callable_mp(this, &AnimationTrackEditor::_zoom_callback));
6444
6445 scroll = memnew(ScrollContainer);
6446 timeline_vbox->add_child(scroll);
6447 scroll->set_v_size_flags(SIZE_EXPAND_FILL);
6448 VScrollBar *sb = scroll->get_v_scroll_bar();
6449 scroll->remove_child(sb);
6450 timeline_scroll->add_child(sb); // Move here so timeline and tracks are always aligned.
6451 scroll->set_focus_mode(FOCUS_CLICK);
6452 scroll->connect("gui_input", callable_mp(this, &AnimationTrackEditor::_scroll_input));
6453 scroll->connect("focus_exited", callable_mp(panner.ptr(), &ViewPanner::release_pan_key));
6454
6455 bezier_edit = memnew(AnimationBezierTrackEdit);
6456 timeline_vbox->add_child(bezier_edit);
6457 bezier_edit->set_editor(this);
6458 bezier_edit->set_timeline(timeline);
6459 bezier_edit->hide();
6460 bezier_edit->set_v_size_flags(SIZE_EXPAND_FILL);
6461 bezier_edit->connect("close_request", callable_mp(this, &AnimationTrackEditor::_cancel_bezier_edit));
6462
6463 timeline_vbox->set_custom_minimum_size(Size2(0, 150) * EDSCALE);
6464
6465 hscroll = memnew(HScrollBar);
6466 hscroll->share(timeline);
6467 hscroll->hide();
6468 hscroll->connect("value_changed", callable_mp(this, &AnimationTrackEditor::_update_scroll));
6469 timeline_vbox->add_child(hscroll);
6470 timeline->set_hscroll(hscroll);
6471
6472 track_vbox = memnew(VBoxContainer);
6473 scroll->add_child(track_vbox);
6474 track_vbox->set_h_size_flags(SIZE_EXPAND_FILL);
6475 scroll->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED);
6476 track_vbox->add_theme_constant_override("separation", 0);
6477
6478 HBoxContainer *bottom_hb = memnew(HBoxContainer);
6479 add_child(bottom_hb);
6480
6481 imported_anim_warning = memnew(Button);
6482 imported_anim_warning->hide();
6483 imported_anim_warning->set_text(TTR("Imported Scene"));
6484 imported_anim_warning->set_tooltip_text(TTR("Warning: Editing imported animation"));
6485 imported_anim_warning->connect("pressed", callable_mp(this, &AnimationTrackEditor::_show_imported_anim_warning));
6486 bottom_hb->add_child(imported_anim_warning);
6487
6488 bottom_hb->add_spacer();
6489
6490 bezier_edit_icon = memnew(Button);
6491 bezier_edit_icon->set_flat(true);
6492 bezier_edit_icon->set_disabled(true);
6493 bezier_edit_icon->set_toggle_mode(true);
6494 bezier_edit_icon->connect("pressed", callable_mp(this, &AnimationTrackEditor::_toggle_bezier_edit));
6495 bezier_edit_icon->set_tooltip_text(TTR("Toggle between the bezier curve editor and track editor."));
6496
6497 bottom_hb->add_child(bezier_edit_icon);
6498
6499 selected_filter = memnew(Button);
6500 selected_filter->set_flat(true);
6501 selected_filter->connect("pressed", callable_mp(this, &AnimationTrackEditor::_view_group_toggle)); // Same function works the same.
6502 selected_filter->set_toggle_mode(true);
6503 selected_filter->set_tooltip_text(TTR("Only show tracks from nodes selected in tree."));
6504
6505 bottom_hb->add_child(selected_filter);
6506
6507 view_group = memnew(Button);
6508 view_group->set_flat(true);
6509 view_group->connect("pressed", callable_mp(this, &AnimationTrackEditor::_view_group_toggle));
6510 view_group->set_toggle_mode(true);
6511 view_group->set_tooltip_text(TTR("Group tracks by node or display them as plain list."));
6512
6513 bottom_hb->add_child(view_group);
6514 bottom_hb->add_child(memnew(VSeparator));
6515
6516 snap = memnew(Button);
6517 snap->set_flat(true);
6518 snap->set_text(TTR("Snap:") + " ");
6519 bottom_hb->add_child(snap);
6520 snap->set_disabled(true);
6521 snap->set_toggle_mode(true);
6522 snap->set_pressed(true);
6523
6524 step = memnew(EditorSpinSlider);
6525 step->set_min(0);
6526 step->set_max(1000000);
6527 step->set_step(0.0001);
6528 step->set_hide_slider(true);
6529 step->set_custom_minimum_size(Size2(100, 0) * EDSCALE);
6530 step->set_tooltip_text(TTR("Animation step value."));
6531 bottom_hb->add_child(step);
6532 step->connect("value_changed", callable_mp(this, &AnimationTrackEditor::_update_step));
6533 step->set_read_only(true);
6534
6535 snap_mode = memnew(OptionButton);
6536 snap_mode->add_item(TTR("Seconds"));
6537 snap_mode->add_item(TTR("FPS"));
6538 bottom_hb->add_child(snap_mode);
6539 snap_mode->connect("item_selected", callable_mp(this, &AnimationTrackEditor::_snap_mode_changed));
6540 snap_mode->set_disabled(true);
6541
6542 bottom_hb->add_child(memnew(VSeparator));
6543
6544 zoom_icon = memnew(TextureRect);
6545 zoom_icon->set_v_size_flags(SIZE_SHRINK_CENTER);
6546 bottom_hb->add_child(zoom_icon);
6547 zoom = memnew(HSlider);
6548 zoom->set_step(0.01);
6549 zoom->set_min(0.0);
6550 zoom->set_max(2.0);
6551 zoom->set_value(1.0);
6552 zoom->set_custom_minimum_size(Size2(200, 0) * EDSCALE);
6553 zoom->set_v_size_flags(SIZE_SHRINK_CENTER);
6554 bottom_hb->add_child(zoom);
6555 timeline->set_zoom(zoom);
6556
6557 edit = memnew(MenuButton);
6558 edit->set_shortcut_context(this);
6559 edit->set_text(TTR("Edit"));
6560 edit->set_flat(false);
6561 edit->set_disabled(true);
6562 edit->set_tooltip_text(TTR("Animation properties."));
6563 edit->get_popup()->add_item(TTR("Copy Tracks"), EDIT_COPY_TRACKS);
6564 edit->get_popup()->add_item(TTR("Paste Tracks"), EDIT_PASTE_TRACKS);
6565 edit->get_popup()->add_separator();
6566 edit->get_popup()->add_item(TTR("Scale Selection"), EDIT_SCALE_SELECTION);
6567 edit->get_popup()->add_item(TTR("Scale From Cursor"), EDIT_SCALE_FROM_CURSOR);
6568 edit->get_popup()->add_separator();
6569 edit->get_popup()->add_item(TTR("Make Easing Selection"), EDIT_EASE_SELECTION);
6570 edit->get_popup()->add_separator();
6571 edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/duplicate_selection", TTR("Duplicate Selection"), KeyModifierMask::CMD_OR_CTRL | Key::D), EDIT_DUPLICATE_SELECTION);
6572 edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/duplicate_selection_transposed", TTR("Duplicate Transposed"), KeyModifierMask::SHIFT | KeyModifierMask::CMD_OR_CTRL | Key::D), EDIT_DUPLICATE_TRANSPOSED);
6573 edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/add_reset_value", TTR("Add RESET Value(s)")));
6574 edit->get_popup()->add_separator();
6575 edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/delete_selection", TTR("Delete Selection"), Key::KEY_DELETE), EDIT_DELETE_SELECTION);
6576
6577 edit->get_popup()->add_separator();
6578 edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/goto_next_step", TTR("Go to Next Step"), KeyModifierMask::CMD_OR_CTRL | Key::RIGHT), EDIT_GOTO_NEXT_STEP);
6579 edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/goto_prev_step", TTR("Go to Previous Step"), KeyModifierMask::CMD_OR_CTRL | Key::LEFT), EDIT_GOTO_PREV_STEP);
6580 edit->get_popup()->add_separator();
6581 edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/apply_reset", TTR("Apply Reset")), EDIT_APPLY_RESET);
6582 edit->get_popup()->add_separator();
6583 edit->get_popup()->add_item(TTR("Bake Animation"), EDIT_BAKE_ANIMATION);
6584 edit->get_popup()->add_item(TTR("Optimize Animation (no undo)"), EDIT_OPTIMIZE_ANIMATION);
6585 edit->get_popup()->add_item(TTR("Clean-Up Animation (no undo)"), EDIT_CLEAN_UP_ANIMATION);
6586
6587 edit->get_popup()->connect("id_pressed", callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed));
6588 edit->get_popup()->connect("about_to_popup", callable_mp(this, &AnimationTrackEditor::_edit_menu_about_to_popup));
6589
6590 pick_track = memnew(SceneTreeDialog);
6591 add_child(pick_track);
6592 pick_track->register_text_enter(pick_track->get_filter_line_edit());
6593 pick_track->set_title(TTR("Pick a node to animate:"));
6594 pick_track->connect("selected", callable_mp(this, &AnimationTrackEditor::_new_track_node_selected));
6595 pick_track->get_filter_line_edit()->connect("text_changed", callable_mp(this, &AnimationTrackEditor::_pick_track_filter_text_changed));
6596 pick_track->get_filter_line_edit()->connect("gui_input", callable_mp(this, &AnimationTrackEditor::_pick_track_filter_input));
6597
6598 prop_selector = memnew(PropertySelector);
6599 add_child(prop_selector);
6600 prop_selector->connect("selected", callable_mp(this, &AnimationTrackEditor::_new_track_property_selected));
6601
6602 method_selector = memnew(PropertySelector);
6603 add_child(method_selector);
6604 method_selector->connect("selected", callable_mp(this, &AnimationTrackEditor::_add_method_key));
6605
6606 insert_confirm = memnew(ConfirmationDialog);
6607 add_child(insert_confirm);
6608 insert_confirm->connect("confirmed", callable_mp(this, &AnimationTrackEditor::_confirm_insert_list));
6609 VBoxContainer *icvb = memnew(VBoxContainer);
6610 insert_confirm->add_child(icvb);
6611 insert_confirm_text = memnew(Label);
6612 icvb->add_child(insert_confirm_text);
6613 HBoxContainer *ichb = memnew(HBoxContainer);
6614 icvb->add_child(ichb);
6615 insert_confirm_bezier = memnew(CheckBox);
6616 insert_confirm_bezier->set_text(TTR("Use Bezier Curves"));
6617 insert_confirm_bezier->set_pressed(EDITOR_GET("editors/animation/default_create_bezier_tracks"));
6618 ichb->add_child(insert_confirm_bezier);
6619 insert_confirm_reset = memnew(CheckBox);
6620 insert_confirm_reset->set_text(TTR("Create RESET Track(s)", ""));
6621 insert_confirm_reset->set_pressed(EDITOR_GET("editors/animation/default_create_reset_tracks"));
6622 ichb->add_child(insert_confirm_reset);
6623
6624 box_selection = memnew(Control);
6625 add_child(box_selection);
6626 box_selection->set_as_top_level(true);
6627 box_selection->set_mouse_filter(MOUSE_FILTER_IGNORE);
6628 box_selection->hide();
6629 box_selection->connect("draw", callable_mp(this, &AnimationTrackEditor::_box_selection_draw));
6630
6631 // Default Plugins.
6632
6633 Ref<AnimationTrackEditDefaultPlugin> def_plugin;
6634 def_plugin.instantiate();
6635 add_track_edit_plugin(def_plugin);
6636
6637 // Dialogs.
6638
6639 optimize_dialog = memnew(ConfirmationDialog);
6640 add_child(optimize_dialog);
6641 optimize_dialog->set_title(TTR("Animation Optimizer"));
6642 VBoxContainer *optimize_vb = memnew(VBoxContainer);
6643 optimize_dialog->add_child(optimize_vb);
6644
6645 optimize_velocity_error = memnew(SpinBox);
6646 optimize_velocity_error->set_max(1.0);
6647 optimize_velocity_error->set_min(0.001);
6648 optimize_velocity_error->set_step(0.001);
6649 optimize_velocity_error->set_value(0.01);
6650 optimize_vb->add_margin_child(TTR("Max Velocity Error:"), optimize_velocity_error);
6651 optimize_angular_error = memnew(SpinBox);
6652 optimize_angular_error->set_max(1.0);
6653 optimize_angular_error->set_min(0.001);
6654 optimize_angular_error->set_step(0.001);
6655 optimize_angular_error->set_value(0.01);
6656 optimize_vb->add_margin_child(TTR("Max Angular Error:"), optimize_angular_error);
6657 optimize_precision_error = memnew(SpinBox);
6658 optimize_precision_error->set_max(6);
6659 optimize_precision_error->set_min(1);
6660 optimize_precision_error->set_step(1);
6661 optimize_precision_error->set_value(3);
6662 optimize_vb->add_margin_child(TTR("Max Precision Error:"), optimize_precision_error);
6663
6664 optimize_dialog->set_ok_button_text(TTR("Optimize"));
6665 optimize_dialog->connect("confirmed", callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed).bind(EDIT_OPTIMIZE_ANIMATION_CONFIRM));
6666
6667 //
6668
6669 cleanup_dialog = memnew(ConfirmationDialog);
6670 add_child(cleanup_dialog);
6671 VBoxContainer *cleanup_vb = memnew(VBoxContainer);
6672 cleanup_dialog->add_child(cleanup_vb);
6673
6674 cleanup_keys = memnew(CheckBox);
6675 cleanup_keys->set_text(TTR("Remove invalid keys"));
6676 cleanup_keys->set_pressed(true);
6677 cleanup_vb->add_child(cleanup_keys);
6678
6679 cleanup_tracks = memnew(CheckBox);
6680 cleanup_tracks->set_text(TTR("Remove unresolved and empty tracks"));
6681 cleanup_tracks->set_pressed(true);
6682 cleanup_vb->add_child(cleanup_tracks);
6683
6684 cleanup_all = memnew(CheckBox);
6685 cleanup_all->set_text(TTR("Clean-up all animations"));
6686 cleanup_vb->add_child(cleanup_all);
6687
6688 cleanup_dialog->set_title(TTR("Clean-Up Animation(s) (NO UNDO!)"));
6689 cleanup_dialog->set_ok_button_text(TTR("Clean-Up"));
6690
6691 cleanup_dialog->connect("confirmed", callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed).bind(EDIT_CLEAN_UP_ANIMATION_CONFIRM));
6692
6693 //
6694 scale_dialog = memnew(ConfirmationDialog);
6695 VBoxContainer *vbc = memnew(VBoxContainer);
6696 scale_dialog->add_child(vbc);
6697
6698 scale = memnew(SpinBox);
6699 scale->set_min(-99999);
6700 scale->set_max(99999);
6701 scale->set_step(0.001);
6702 vbc->add_margin_child(TTR("Scale Ratio:"), scale);
6703 scale_dialog->connect("confirmed", callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed).bind(EDIT_SCALE_CONFIRM));
6704 add_child(scale_dialog);
6705
6706 //
6707 ease_dialog = memnew(ConfirmationDialog);
6708 ease_dialog->set_title(TTR("Select Transition and Easing"));
6709 ease_dialog->connect("confirmed", callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed).bind(EDIT_EASE_CONFIRM));
6710 add_child(ease_dialog);
6711 GridContainer *ease_grid = memnew(GridContainer);
6712 ease_grid->set_columns(2);
6713 ease_dialog->add_child(ease_grid);
6714 transition_selection = memnew(OptionButton);
6715 transition_selection->add_item(TTR("Linear", "Transition Type"), Tween::TRANS_LINEAR);
6716 transition_selection->add_item(TTR("Sine", "Transition Type"), Tween::TRANS_SINE);
6717 transition_selection->add_item(TTR("Quint", "Transition Type"), Tween::TRANS_QUINT);
6718 transition_selection->add_item(TTR("Quart", "Transition Type"), Tween::TRANS_QUART);
6719 transition_selection->add_item(TTR("Quad", "Transition Type"), Tween::TRANS_QUAD);
6720 transition_selection->add_item(TTR("Expo", "Transition Type"), Tween::TRANS_EXPO);
6721 transition_selection->add_item(TTR("Elastic", "Transition Type"), Tween::TRANS_ELASTIC);
6722 transition_selection->add_item(TTR("Cubic", "Transition Type"), Tween::TRANS_CUBIC);
6723 transition_selection->add_item(TTR("Circ", "Transition Type"), Tween::TRANS_CIRC);
6724 transition_selection->add_item(TTR("Bounce", "Transition Type"), Tween::TRANS_BOUNCE);
6725 transition_selection->add_item(TTR("Back", "Transition Type"), Tween::TRANS_BACK);
6726 transition_selection->add_item(TTR("Spring", "Transition Type"), Tween::TRANS_SPRING);
6727 transition_selection->select(Tween::TRANS_LINEAR); // Default
6728 transition_selection->set_auto_translate(false); // Translation context is needed.
6729 ease_selection = memnew(OptionButton);
6730 ease_selection->add_item(TTR("In", "Ease Type"), Tween::EASE_IN);
6731 ease_selection->add_item(TTR("Out", "Ease Type"), Tween::EASE_OUT);
6732 ease_selection->add_item(TTR("InOut", "Ease Type"), Tween::EASE_IN_OUT);
6733 ease_selection->add_item(TTR("OutIn", "Ease Type"), Tween::EASE_OUT_IN);
6734 ease_selection->select(Tween::EASE_IN_OUT); // Default
6735 ease_selection->set_auto_translate(false); // Translation context is needed.
6736 ease_fps = memnew(SpinBox);
6737 ease_fps->set_min(1);
6738 ease_fps->set_max(999);
6739 ease_fps->set_step(1);
6740 ease_fps->set_value(30); // Default
6741 ease_grid->add_child(memnew(Label(TTR("Transition Type:"))));
6742 ease_grid->add_child(transition_selection);
6743 ease_grid->add_child(memnew(Label(TTR("Ease Type:"))));
6744 ease_grid->add_child(ease_selection);
6745 ease_grid->add_child(memnew(Label(TTR("FPS:"))));
6746 ease_grid->add_child(ease_fps);
6747
6748 //
6749 bake_dialog = memnew(ConfirmationDialog);
6750 bake_dialog->set_title(TTR("Animation Baker"));
6751 bake_dialog->connect("confirmed", callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed).bind(EDIT_BAKE_ANIMATION_CONFIRM));
6752 add_child(bake_dialog);
6753 GridContainer *bake_grid = memnew(GridContainer);
6754 bake_grid->set_columns(2);
6755 bake_dialog->add_child(bake_grid);
6756 bake_trs = memnew(CheckBox);
6757 bake_trs->set_pressed(true);
6758 bake_blendshape = memnew(CheckBox);
6759 bake_blendshape->set_pressed(true);
6760 bake_value = memnew(CheckBox);
6761 bake_value->set_pressed(true);
6762 bake_fps = memnew(SpinBox);
6763 bake_fps->set_min(1);
6764 bake_fps->set_max(999);
6765 bake_fps->set_step(1);
6766 bake_fps->set_value(30); // Default
6767 bake_grid->add_child(memnew(Label(TTR("3D Pos/Rot/Scl Track:"))));
6768 bake_grid->add_child(bake_trs);
6769 bake_grid->add_child(memnew(Label(TTR("Blendshape Track:"))));
6770 bake_grid->add_child(bake_blendshape);
6771 bake_grid->add_child(memnew(Label(TTR("Value Track:"))));
6772 bake_grid->add_child(bake_value);
6773 bake_grid->add_child(memnew(Label(TTR("FPS:"))));
6774 bake_grid->add_child(bake_fps);
6775
6776 //
6777 track_copy_dialog = memnew(ConfirmationDialog);
6778 add_child(track_copy_dialog);
6779 track_copy_dialog->set_title(TTR("Select Tracks to Copy"));
6780 track_copy_dialog->set_ok_button_text(TTR("Copy"));
6781
6782 VBoxContainer *track_copy_vbox = memnew(VBoxContainer);
6783 track_copy_dialog->add_child(track_copy_vbox);
6784
6785 Button *select_all_button = memnew(Button);
6786 select_all_button->set_text(TTR("Select All/None"));
6787 select_all_button->connect("pressed", callable_mp(this, &AnimationTrackEditor::_select_all_tracks_for_copy));
6788 track_copy_vbox->add_child(select_all_button);
6789
6790 track_copy_select = memnew(Tree);
6791 track_copy_select->set_h_size_flags(SIZE_EXPAND_FILL);
6792 track_copy_select->set_v_size_flags(SIZE_EXPAND_FILL);
6793 track_copy_select->set_hide_root(true);
6794 track_copy_vbox->add_child(track_copy_select);
6795 track_copy_dialog->connect("confirmed", callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed).bind(EDIT_COPY_TRACKS_CONFIRM));
6796}
6797
6798AnimationTrackEditor::~AnimationTrackEditor() {
6799 if (key_edit) {
6800 memdelete(key_edit);
6801 }
6802 if (multi_key_edit) {
6803 memdelete(multi_key_edit);
6804 }
6805}
6806
6807// AnimationTrackKeyEditEditorPlugin
6808
6809void AnimationTrackKeyEditEditor::_time_edit_entered() {
6810 int key = animation->track_find_key(track, key_ofs, Animation::FIND_MODE_APPROX);
6811 if (key == -1) {
6812 return;
6813 }
6814 key_data_cache.time = animation->track_get_key_time(track, key);
6815 key_data_cache.transition = animation->track_get_key_transition(track, key);
6816 key_data_cache.value = animation->track_get_key_value(track, key);
6817}
6818
6819void AnimationTrackKeyEditEditor::_time_edit_exited() {
6820 real_t new_time = spinner->get_value();
6821
6822 if (use_fps) {
6823 real_t fps = animation->get_step();
6824 if (fps > 0) {
6825 fps = 1.0 / fps;
6826 }
6827 new_time /= fps;
6828 }
6829
6830 if (Math::is_equal_approx(new_time, key_data_cache.time)) {
6831 return; // No change.
6832 }
6833
6834 int existing = animation->track_find_key(track, new_time, Animation::FIND_MODE_APPROX);
6835 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
6836 undo_redo->create_action(TTR("Animation Change Keyframe Time"), UndoRedo::MERGE_ENDS);
6837
6838 if (existing != -1) {
6839 undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_time", track, animation->track_get_key_time(track, existing));
6840 }
6841 undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_time", track, key_data_cache.time);
6842 undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, new_time, key_data_cache.value, key_data_cache.transition);
6843 undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", track, new_time);
6844 undo_redo->add_undo_method(animation.ptr(), "track_insert_key", track, key_data_cache.time, key_data_cache.value, key_data_cache.transition);
6845 if (existing != -1) {
6846 undo_redo->add_undo_method(animation.ptr(), "track_insert_key", track, animation->track_get_key_time(track, existing), animation->track_get_key_value(track, existing), animation->track_get_key_transition(track, existing));
6847 }
6848
6849 // Reselect key.
6850 AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
6851 if (ape) {
6852 AnimationTrackEditor *ate = ape->get_track_editor();
6853 if (ate) {
6854 undo_redo->add_do_method(ate, "_clear_selection_for_anim", animation);
6855 undo_redo->add_undo_method(ate, "_clear_selection_for_anim", animation);
6856 undo_redo->add_do_method(ate, "_select_at_anim", animation, track, new_time);
6857 undo_redo->add_undo_method(ate, "_select_at_anim", animation, track, key_data_cache.time);
6858 }
6859 }
6860
6861 undo_redo->commit_action();
6862}
6863
6864AnimationTrackKeyEditEditor::AnimationTrackKeyEditEditor(Ref<Animation> p_animation, int p_track, real_t p_key_ofs, bool p_use_fps) {
6865 if (!p_animation.is_valid()) {
6866 return;
6867 }
6868
6869 animation = p_animation;
6870 track = p_track;
6871 key_ofs = p_key_ofs;
6872 use_fps = p_use_fps;
6873
6874 set_label("Time");
6875
6876 spinner = memnew(EditorSpinSlider);
6877 spinner->set_focus_mode(Control::FOCUS_CLICK);
6878 spinner->set_min(0);
6879 spinner->set_allow_greater(true);
6880 spinner->set_allow_lesser(true);
6881
6882 if (use_fps) {
6883 spinner->set_step(1);
6884 spinner->set_hide_slider(true);
6885 real_t fps = animation->get_step();
6886 if (fps > 0) {
6887 fps = 1.0 / fps;
6888 }
6889 spinner->set_value(key_ofs * fps);
6890 } else {
6891 spinner->set_step(0.0001);
6892 spinner->set_value(key_ofs);
6893 spinner->set_max(animation->get_length());
6894 }
6895
6896 add_child(spinner);
6897
6898 spinner->connect("grabbed", callable_mp(this, &AnimationTrackKeyEditEditor::_time_edit_entered), CONNECT_DEFERRED);
6899 spinner->connect("ungrabbed", callable_mp(this, &AnimationTrackKeyEditEditor::_time_edit_exited), CONNECT_DEFERRED);
6900 spinner->connect("value_focus_entered", callable_mp(this, &AnimationTrackKeyEditEditor::_time_edit_entered), CONNECT_DEFERRED);
6901 spinner->connect("value_focus_exited", callable_mp(this, &AnimationTrackKeyEditEditor::_time_edit_exited), CONNECT_DEFERRED);
6902}
6903
6904AnimationTrackKeyEditEditor::~AnimationTrackKeyEditEditor() {
6905}
6906