Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/animation/animation_track_editor.cpp
9896 views
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/error/error_macros.h"
35
#include "core/input/input.h"
36
#include "editor/animation/animation_bezier_editor.h"
37
#include "editor/animation/animation_player_editor_plugin.h"
38
#include "editor/docks/inspector_dock.h"
39
#include "editor/editor_node.h"
40
#include "editor/editor_string_names.h"
41
#include "editor/editor_undo_redo_manager.h"
42
#include "editor/gui/editor_spin_slider.h"
43
#include "editor/gui/editor_validation_panel.h"
44
#include "editor/inspector/multi_node_edit.h"
45
#include "editor/scene/scene_tree_editor.h"
46
#include "editor/script/script_editor_plugin.h"
47
#include "editor/settings/editor_settings.h"
48
#include "editor/themes/editor_scale.h"
49
#include "scene/3d/mesh_instance_3d.h"
50
#include "scene/animation/animation_player.h"
51
#include "scene/animation/tween.h"
52
#include "scene/gui/check_box.h"
53
#include "scene/gui/color_picker.h"
54
#include "scene/gui/flow_container.h"
55
#include "scene/gui/grid_container.h"
56
#include "scene/gui/option_button.h"
57
#include "scene/gui/panel_container.h"
58
#include "scene/gui/separator.h"
59
#include "scene/gui/slider.h"
60
#include "scene/gui/spin_box.h"
61
#include "scene/gui/texture_rect.h"
62
#include "scene/gui/view_panner.h"
63
#include "scene/main/window.h"
64
#include "servers/audio/audio_stream.h"
65
66
constexpr double FPS_DECIMAL = 1.0;
67
constexpr double SECOND_DECIMAL = 0.0001;
68
69
void AnimationTrackKeyEdit::_bind_methods() {
70
ClassDB::bind_method(D_METHOD("_update_obj"), &AnimationTrackKeyEdit::_update_obj);
71
ClassDB::bind_method(D_METHOD("_key_ofs_changed"), &AnimationTrackKeyEdit::_key_ofs_changed);
72
ClassDB::bind_method(D_METHOD("_hide_script_from_inspector"), &AnimationTrackKeyEdit::_hide_script_from_inspector);
73
ClassDB::bind_method(D_METHOD("_hide_metadata_from_inspector"), &AnimationTrackKeyEdit::_hide_metadata_from_inspector);
74
ClassDB::bind_method(D_METHOD("get_root_path"), &AnimationTrackKeyEdit::get_root_path);
75
ClassDB::bind_method(D_METHOD("_dont_undo_redo"), &AnimationTrackKeyEdit::_dont_undo_redo);
76
ClassDB::bind_method(D_METHOD("_is_read_only"), &AnimationTrackKeyEdit::_is_read_only);
77
}
78
79
void AnimationTrackKeyEdit::_fix_node_path(Variant &value) {
80
NodePath np = value;
81
82
if (np == NodePath()) {
83
return;
84
}
85
86
Node *root = EditorNode::get_singleton()->get_tree()->get_root();
87
88
Node *np_node = root->get_node_or_null(np);
89
ERR_FAIL_NULL(np_node);
90
91
Node *edited_node = root->get_node_or_null(base);
92
ERR_FAIL_NULL(edited_node);
93
94
value = edited_node->get_path_to(np_node);
95
}
96
97
void AnimationTrackKeyEdit::_update_obj(const Ref<Animation> &p_anim) {
98
if (setting || animation != p_anim) {
99
return;
100
}
101
102
notify_change();
103
}
104
105
void AnimationTrackKeyEdit::_key_ofs_changed(const Ref<Animation> &p_anim, float from, float to) {
106
if (animation != p_anim || from != key_ofs) {
107
return;
108
}
109
110
key_ofs = to;
111
112
if (setting) {
113
return;
114
}
115
116
notify_change();
117
}
118
119
bool AnimationTrackKeyEdit::_set(const StringName &p_name, const Variant &p_value) {
120
int key = animation->track_find_key(track, key_ofs, Animation::FIND_MODE_APPROX);
121
ERR_FAIL_COND_V(key == -1, false);
122
123
String name = p_name;
124
if (name == "easing") {
125
float val = p_value;
126
float prev_val = animation->track_get_key_transition(track, key);
127
setting = true;
128
129
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
130
undo_redo->create_action(TTR("Animation Change Transition"), UndoRedo::MERGE_ENDS);
131
undo_redo->add_do_method(animation.ptr(), "track_set_key_transition", track, key, val);
132
undo_redo->add_undo_method(animation.ptr(), "track_set_key_transition", track, key, prev_val);
133
undo_redo->add_do_method(this, "_update_obj", animation);
134
undo_redo->add_undo_method(this, "_update_obj", animation);
135
AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
136
if (ape) {
137
undo_redo->add_do_method(ape, "_animation_update_key_frame");
138
undo_redo->add_undo_method(ape, "_animation_update_key_frame");
139
}
140
undo_redo->commit_action();
141
142
setting = false;
143
return true;
144
}
145
146
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
147
switch (animation->track_get_type(track)) {
148
case Animation::TYPE_POSITION_3D:
149
case Animation::TYPE_ROTATION_3D:
150
case Animation::TYPE_SCALE_3D: {
151
if (name == "position" || name == "rotation" || name == "scale") {
152
Variant old = animation->track_get_key_value(track, key);
153
setting = true;
154
String action_name;
155
switch (animation->track_get_type(track)) {
156
case Animation::TYPE_POSITION_3D:
157
action_name = TTR("Animation Change Position3D");
158
break;
159
case Animation::TYPE_ROTATION_3D:
160
action_name = TTR("Animation Change Rotation3D");
161
break;
162
case Animation::TYPE_SCALE_3D:
163
action_name = TTR("Animation Change Scale3D");
164
break;
165
default: {
166
}
167
}
168
169
undo_redo->create_action(action_name);
170
undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, p_value);
171
undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, old);
172
undo_redo->add_do_method(this, "_update_obj", animation);
173
undo_redo->add_undo_method(this, "_update_obj", animation);
174
undo_redo->commit_action();
175
176
setting = false;
177
return true;
178
}
179
180
} break;
181
case Animation::TYPE_BLEND_SHAPE:
182
case Animation::TYPE_VALUE: {
183
if (name == "value") {
184
Variant value = p_value;
185
186
if (value.get_type() == Variant::NODE_PATH) {
187
_fix_node_path(value);
188
}
189
190
setting = true;
191
192
undo_redo->create_action(TTR("Animation Change Keyframe Value"), UndoRedo::MERGE_ENDS);
193
Variant prev = animation->track_get_key_value(track, key);
194
undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, value);
195
undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, prev);
196
undo_redo->add_do_method(this, "_update_obj", animation);
197
undo_redo->add_undo_method(this, "_update_obj", animation);
198
AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
199
if (ape) {
200
undo_redo->add_do_method(ape, "_animation_update_key_frame");
201
undo_redo->add_undo_method(ape, "_animation_update_key_frame");
202
}
203
undo_redo->commit_action();
204
205
setting = false;
206
return true;
207
}
208
} break;
209
case Animation::TYPE_METHOD: {
210
Dictionary d_old = animation->track_get_key_value(track, key);
211
Dictionary d_new = d_old.duplicate();
212
213
bool change_notify_deserved = false;
214
bool mergeable = false;
215
216
if (name == "name") {
217
d_new["method"] = p_value;
218
} else if (name == "arg_count") {
219
Vector<Variant> args = d_old["args"];
220
args.resize(p_value);
221
d_new["args"] = args;
222
change_notify_deserved = true;
223
} else if (name.begins_with("args/")) {
224
Vector<Variant> args = d_old["args"];
225
int idx = name.get_slicec('/', 1).to_int();
226
ERR_FAIL_INDEX_V(idx, args.size(), false);
227
228
String what = name.get_slicec('/', 2);
229
if (what == "type") {
230
Variant::Type t = Variant::Type(int(p_value));
231
232
if (t != args[idx].get_type()) {
233
Callable::CallError err;
234
if (Variant::can_convert_strict(args[idx].get_type(), t)) {
235
Variant old = args[idx];
236
Variant *ptrs[1] = { &old };
237
Variant::construct(t, args.write[idx], (const Variant **)ptrs, 1, err);
238
} else {
239
Variant::construct(t, args.write[idx], nullptr, 0, err);
240
}
241
change_notify_deserved = true;
242
d_new["args"] = args;
243
}
244
} else if (what == "value") {
245
Variant value = p_value;
246
if (value.get_type() == Variant::NODE_PATH) {
247
_fix_node_path(value);
248
}
249
250
args.write[idx] = value;
251
d_new["args"] = args;
252
mergeable = true;
253
}
254
}
255
256
if (mergeable) {
257
undo_redo->create_action(TTR("Animation Change Call"), UndoRedo::MERGE_ENDS);
258
} else {
259
undo_redo->create_action(TTR("Animation Change Call"));
260
}
261
262
setting = true;
263
undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, d_new);
264
undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, d_old);
265
undo_redo->add_do_method(this, "_update_obj", animation);
266
undo_redo->add_undo_method(this, "_update_obj", animation);
267
undo_redo->commit_action();
268
269
setting = false;
270
if (change_notify_deserved) {
271
notify_change();
272
}
273
return true;
274
} break;
275
case Animation::TYPE_BEZIER: {
276
if (name == "value") {
277
const Variant &value = p_value;
278
279
setting = true;
280
undo_redo->create_action(TTR("Animation Change Keyframe Value"), UndoRedo::MERGE_ENDS);
281
float prev = animation->bezier_track_get_key_value(track, key);
282
undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_value", track, key, value);
283
undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_value", track, key, prev);
284
undo_redo->add_do_method(this, "_update_obj", animation);
285
undo_redo->add_undo_method(this, "_update_obj", animation);
286
AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
287
if (ape) {
288
undo_redo->add_do_method(ape, "_animation_update_key_frame");
289
undo_redo->add_undo_method(ape, "_animation_update_key_frame");
290
}
291
undo_redo->commit_action();
292
293
setting = false;
294
return true;
295
}
296
297
if (name == "in_handle") {
298
const Variant &value = p_value;
299
300
setting = true;
301
undo_redo->create_action(TTR("Animation Change Keyframe Value"), UndoRedo::MERGE_ENDS);
302
Vector2 prev = animation->bezier_track_get_key_in_handle(track, key);
303
undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_in_handle", track, key, value);
304
undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", track, key, prev);
305
undo_redo->add_do_method(this, "_update_obj", animation);
306
undo_redo->add_undo_method(this, "_update_obj", animation);
307
AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
308
if (ape) {
309
undo_redo->add_do_method(ape, "_animation_update_key_frame");
310
undo_redo->add_undo_method(ape, "_animation_update_key_frame");
311
}
312
undo_redo->commit_action();
313
314
setting = false;
315
return true;
316
}
317
318
if (name == "out_handle") {
319
const Variant &value = p_value;
320
321
setting = true;
322
undo_redo->create_action(TTR("Animation Change Keyframe Value"), UndoRedo::MERGE_ENDS);
323
Vector2 prev = animation->bezier_track_get_key_out_handle(track, key);
324
undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_out_handle", track, key, value);
325
undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", track, key, prev);
326
undo_redo->add_do_method(this, "_update_obj", animation);
327
undo_redo->add_undo_method(this, "_update_obj", animation);
328
AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
329
if (ape) {
330
undo_redo->add_do_method(ape, "_animation_update_key_frame");
331
undo_redo->add_undo_method(ape, "_animation_update_key_frame");
332
}
333
undo_redo->commit_action();
334
335
setting = false;
336
return true;
337
}
338
339
if (name == "handle_mode") {
340
const Variant &value = p_value;
341
342
setting = true;
343
undo_redo->create_action(TTR("Animation Change Keyframe Value"), UndoRedo::MERGE_ENDS, animation.ptr());
344
int prev_mode = animation->bezier_track_get_key_handle_mode(track, key);
345
Vector2 prev_in_handle = animation->bezier_track_get_key_in_handle(track, key);
346
Vector2 prev_out_handle = animation->bezier_track_get_key_out_handle(track, key);
347
undo_redo->add_do_method(editor, "_bezier_track_set_key_handle_mode", animation.ptr(), track, key, value);
348
undo_redo->add_do_method(this, "_update_obj", animation);
349
undo_redo->add_undo_method(editor, "_bezier_track_set_key_handle_mode", animation.ptr(), track, key, prev_mode);
350
undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", track, key, prev_in_handle);
351
undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", track, key, prev_out_handle);
352
undo_redo->add_undo_method(this, "_update_obj", animation);
353
AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
354
if (ape) {
355
undo_redo->add_do_method(ape, "_animation_update_key_frame");
356
undo_redo->add_undo_method(ape, "_animation_update_key_frame");
357
}
358
undo_redo->commit_action();
359
360
setting = false;
361
return true;
362
}
363
} break;
364
case Animation::TYPE_AUDIO: {
365
if (name == "stream") {
366
Ref<AudioStream> stream = p_value;
367
368
setting = true;
369
undo_redo->create_action(TTR("Animation Change Keyframe Value"), UndoRedo::MERGE_ENDS);
370
Ref<Resource> prev = animation->audio_track_get_key_stream(track, key);
371
undo_redo->add_do_method(animation.ptr(), "audio_track_set_key_stream", track, key, stream);
372
undo_redo->add_undo_method(animation.ptr(), "audio_track_set_key_stream", track, key, prev);
373
undo_redo->add_do_method(this, "_update_obj", animation);
374
undo_redo->add_undo_method(this, "_update_obj", animation);
375
undo_redo->commit_action();
376
377
setting = false;
378
notify_change(); // To update limits for `start_offset`/`end_offset` sliders (they depend on the stream length).
379
return true;
380
}
381
382
if (name == "start_offset") {
383
float value = p_value;
384
385
setting = true;
386
undo_redo->create_action(TTR("Animation Change Keyframe Value"), UndoRedo::MERGE_ENDS);
387
float prev = animation->audio_track_get_key_start_offset(track, key);
388
undo_redo->add_do_method(animation.ptr(), "audio_track_set_key_start_offset", track, key, value);
389
undo_redo->add_undo_method(animation.ptr(), "audio_track_set_key_start_offset", track, key, prev);
390
undo_redo->add_do_method(this, "_update_obj", animation);
391
undo_redo->add_undo_method(this, "_update_obj", animation);
392
undo_redo->commit_action();
393
394
setting = false;
395
return true;
396
}
397
398
if (name == "end_offset") {
399
float value = p_value;
400
401
setting = true;
402
undo_redo->create_action(TTR("Animation Change Keyframe Value"), UndoRedo::MERGE_ENDS);
403
float prev = animation->audio_track_get_key_end_offset(track, key);
404
undo_redo->add_do_method(animation.ptr(), "audio_track_set_key_end_offset", track, key, value);
405
undo_redo->add_undo_method(animation.ptr(), "audio_track_set_key_end_offset", track, key, prev);
406
undo_redo->add_do_method(this, "_update_obj", animation);
407
undo_redo->add_undo_method(this, "_update_obj", animation);
408
undo_redo->commit_action();
409
410
setting = false;
411
return true;
412
}
413
} break;
414
case Animation::TYPE_ANIMATION: {
415
if (name == "animation") {
416
StringName anim_name = p_value;
417
418
setting = true;
419
undo_redo->create_action(TTR("Animation Change Keyframe Value"), UndoRedo::MERGE_ENDS);
420
StringName prev = animation->animation_track_get_key_animation(track, key);
421
undo_redo->add_do_method(animation.ptr(), "animation_track_set_key_animation", track, key, anim_name);
422
undo_redo->add_undo_method(animation.ptr(), "animation_track_set_key_animation", track, key, prev);
423
undo_redo->add_do_method(this, "_update_obj", animation);
424
undo_redo->add_undo_method(this, "_update_obj", animation);
425
undo_redo->commit_action();
426
427
setting = false;
428
return true;
429
}
430
} break;
431
}
432
433
return false;
434
}
435
436
bool AnimationTrackKeyEdit::_get(const StringName &p_name, Variant &r_ret) const {
437
int key = animation->track_find_key(track, key_ofs, Animation::FIND_MODE_APPROX);
438
ERR_FAIL_COND_V(key == -1, false);
439
440
String name = p_name;
441
if (name == "easing") {
442
r_ret = animation->track_get_key_transition(track, key);
443
return true;
444
}
445
446
switch (animation->track_get_type(track)) {
447
case Animation::TYPE_POSITION_3D:
448
case Animation::TYPE_ROTATION_3D:
449
case Animation::TYPE_SCALE_3D: {
450
if (name == "position" || name == "rotation" || name == "scale") {
451
r_ret = animation->track_get_key_value(track, key);
452
return true;
453
}
454
} break;
455
case Animation::TYPE_BLEND_SHAPE:
456
case Animation::TYPE_VALUE: {
457
if (name == "value") {
458
r_ret = animation->track_get_key_value(track, key);
459
return true;
460
}
461
462
} break;
463
case Animation::TYPE_METHOD: {
464
Dictionary d = animation->track_get_key_value(track, key);
465
466
if (name == "name") {
467
ERR_FAIL_COND_V(!d.has("method"), false);
468
r_ret = d["method"];
469
return true;
470
}
471
472
ERR_FAIL_COND_V(!d.has("args"), false);
473
474
Vector<Variant> args = d["args"];
475
476
if (name == "arg_count") {
477
r_ret = args.size();
478
return true;
479
}
480
481
if (name.begins_with("args/")) {
482
int idx = name.get_slicec('/', 1).to_int();
483
ERR_FAIL_INDEX_V(idx, args.size(), false);
484
485
String what = name.get_slicec('/', 2);
486
if (what == "type") {
487
r_ret = args[idx].get_type();
488
return true;
489
}
490
491
if (what == "value") {
492
r_ret = args[idx];
493
return true;
494
}
495
}
496
497
} break;
498
case Animation::TYPE_BEZIER: {
499
if (name == "value") {
500
r_ret = animation->bezier_track_get_key_value(track, key);
501
return true;
502
}
503
504
if (name == "in_handle") {
505
r_ret = animation->bezier_track_get_key_in_handle(track, key);
506
return true;
507
}
508
509
if (name == "out_handle") {
510
r_ret = animation->bezier_track_get_key_out_handle(track, key);
511
return true;
512
}
513
514
if (name == "handle_mode") {
515
r_ret = animation->bezier_track_get_key_handle_mode(track, key);
516
return true;
517
}
518
519
} break;
520
case Animation::TYPE_AUDIO: {
521
if (name == "stream") {
522
r_ret = animation->audio_track_get_key_stream(track, key);
523
return true;
524
}
525
526
if (name == "start_offset") {
527
r_ret = animation->audio_track_get_key_start_offset(track, key);
528
return true;
529
}
530
531
if (name == "end_offset") {
532
r_ret = animation->audio_track_get_key_end_offset(track, key);
533
return true;
534
}
535
536
} break;
537
case Animation::TYPE_ANIMATION: {
538
if (name == "animation") {
539
r_ret = animation->animation_track_get_key_animation(track, key);
540
return true;
541
}
542
543
} break;
544
}
545
546
return false;
547
}
548
549
void AnimationTrackKeyEdit::_get_property_list(List<PropertyInfo> *p_list) const {
550
if (animation.is_null()) {
551
return;
552
}
553
554
ERR_FAIL_INDEX(track, animation->get_track_count());
555
int key = animation->track_find_key(track, key_ofs, Animation::FIND_MODE_APPROX);
556
ERR_FAIL_COND(key == -1);
557
558
switch (animation->track_get_type(track)) {
559
case Animation::TYPE_POSITION_3D: {
560
p_list->push_back(PropertyInfo(Variant::VECTOR3, PNAME("position")));
561
} break;
562
case Animation::TYPE_ROTATION_3D: {
563
p_list->push_back(PropertyInfo(Variant::QUATERNION, PNAME("rotation")));
564
} break;
565
case Animation::TYPE_SCALE_3D: {
566
p_list->push_back(PropertyInfo(Variant::VECTOR3, PNAME("scale")));
567
} break;
568
case Animation::TYPE_BLEND_SHAPE: {
569
p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("value")));
570
} break;
571
case Animation::TYPE_VALUE: {
572
Variant v = animation->track_get_key_value(track, key);
573
574
if (hint.type != Variant::NIL) {
575
PropertyInfo pi = hint;
576
pi.name = PNAME("value");
577
p_list->push_back(pi);
578
} else {
579
PropertyHint val_hint = PROPERTY_HINT_NONE;
580
String val_hint_string;
581
582
if (v.get_type() == Variant::OBJECT) {
583
// Could actually check the object property if exists..? Yes I will!
584
Ref<Resource> res = v;
585
if (res.is_valid()) {
586
val_hint = PROPERTY_HINT_RESOURCE_TYPE;
587
val_hint_string = res->get_class();
588
}
589
}
590
591
if (v.get_type() != Variant::NIL) {
592
p_list->push_back(PropertyInfo(v.get_type(), PNAME("value"), val_hint, val_hint_string));
593
}
594
}
595
596
} break;
597
case Animation::TYPE_METHOD: {
598
p_list->push_back(PropertyInfo(Variant::STRING_NAME, PNAME("name")));
599
p_list->push_back(PropertyInfo(Variant::INT, PNAME("arg_count"), PROPERTY_HINT_RANGE, "0,32,1,or_greater"));
600
601
Dictionary d = animation->track_get_key_value(track, key);
602
ERR_FAIL_COND(!d.has("args"));
603
Vector<Variant> args = d["args"];
604
String vtypes;
605
for (int i = 0; i < Variant::VARIANT_MAX; i++) {
606
if (i > 0) {
607
vtypes += ",";
608
}
609
vtypes += Variant::get_type_name(Variant::Type(i));
610
}
611
612
for (int i = 0; i < args.size(); i++) {
613
p_list->push_back(PropertyInfo(Variant::INT, vformat("%s/%d/%s", PNAME("args"), i, PNAME("type")), PROPERTY_HINT_ENUM, vtypes));
614
if (args[i].get_type() != Variant::NIL) {
615
p_list->push_back(PropertyInfo(args[i].get_type(), vformat("%s/%d/%s", PNAME("args"), i, PNAME("value"))));
616
}
617
}
618
619
} break;
620
case Animation::TYPE_BEZIER: {
621
Animation::HandleMode hm = animation->bezier_track_get_key_handle_mode(track, key);
622
p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("value")));
623
if (hm == Animation::HANDLE_MODE_LINEAR) {
624
p_list->push_back(PropertyInfo(Variant::VECTOR2, PNAME("in_handle"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY));
625
p_list->push_back(PropertyInfo(Variant::VECTOR2, PNAME("out_handle"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY));
626
} else {
627
p_list->push_back(PropertyInfo(Variant::VECTOR2, PNAME("in_handle")));
628
p_list->push_back(PropertyInfo(Variant::VECTOR2, PNAME("out_handle")));
629
}
630
p_list->push_back(PropertyInfo(Variant::INT, PNAME("handle_mode"), PROPERTY_HINT_ENUM, "Free,Linear,Balanced,Mirrored"));
631
632
} break;
633
case Animation::TYPE_AUDIO: {
634
p_list->push_back(PropertyInfo(Variant::OBJECT, PNAME("stream"), PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"));
635
Ref<AudioStream> audio_stream = animation->audio_track_get_key_stream(track, key);
636
String hint_string = vformat("0,%.4f,0.0001,or_greater", audio_stream.is_valid() ? audio_stream->get_length() : 3600.0);
637
p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("start_offset"), PROPERTY_HINT_RANGE, hint_string));
638
p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("end_offset"), PROPERTY_HINT_RANGE, hint_string));
639
640
} break;
641
case Animation::TYPE_ANIMATION: {
642
String animations;
643
644
if (root_path) {
645
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(root_path->get_node_or_null(animation->track_get_path(track)));
646
if (ap) {
647
List<StringName> anims;
648
ap->get_animation_list(&anims);
649
for (const StringName &E : anims) {
650
if (!animations.is_empty()) {
651
animations += ",";
652
}
653
654
animations += String(E);
655
}
656
}
657
}
658
659
if (!animations.is_empty()) {
660
animations += ",";
661
}
662
animations += "[stop]";
663
664
p_list->push_back(PropertyInfo(Variant::STRING_NAME, PNAME("animation"), PROPERTY_HINT_ENUM, animations));
665
666
} break;
667
}
668
669
if (animation->track_get_type(track) == Animation::TYPE_VALUE) {
670
p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("easing"), PROPERTY_HINT_EXP_EASING));
671
}
672
}
673
674
void AnimationTrackKeyEdit::notify_change() {
675
notify_property_list_changed();
676
}
677
678
Node *AnimationTrackKeyEdit::get_root_path() {
679
return root_path;
680
}
681
682
void AnimationTrackKeyEdit::set_use_fps(bool p_enable) {
683
use_fps = p_enable;
684
notify_property_list_changed();
685
}
686
687
void AnimationMultiTrackKeyEdit::_bind_methods() {
688
ClassDB::bind_method(D_METHOD("_update_obj"), &AnimationMultiTrackKeyEdit::_update_obj);
689
ClassDB::bind_method(D_METHOD("_key_ofs_changed"), &AnimationMultiTrackKeyEdit::_key_ofs_changed);
690
ClassDB::bind_method(D_METHOD("_hide_script_from_inspector"), &AnimationMultiTrackKeyEdit::_hide_script_from_inspector);
691
ClassDB::bind_method(D_METHOD("_hide_metadata_from_inspector"), &AnimationMultiTrackKeyEdit::_hide_metadata_from_inspector);
692
ClassDB::bind_method(D_METHOD("get_root_path"), &AnimationMultiTrackKeyEdit::get_root_path);
693
ClassDB::bind_method(D_METHOD("_dont_undo_redo"), &AnimationMultiTrackKeyEdit::_dont_undo_redo);
694
ClassDB::bind_method(D_METHOD("_is_read_only"), &AnimationMultiTrackKeyEdit::_is_read_only);
695
}
696
697
void AnimationMultiTrackKeyEdit::_fix_node_path(Variant &value, NodePath &base) {
698
NodePath np = value;
699
700
if (np == NodePath()) {
701
return;
702
}
703
704
Node *root = EditorNode::get_singleton()->get_tree()->get_root();
705
706
Node *np_node = root->get_node_or_null(np);
707
ERR_FAIL_NULL(np_node);
708
709
Node *edited_node = root->get_node_or_null(base);
710
ERR_FAIL_NULL(edited_node);
711
712
value = edited_node->get_path_to(np_node);
713
}
714
715
void AnimationMultiTrackKeyEdit::_update_obj(const Ref<Animation> &p_anim) {
716
if (setting || animation != p_anim) {
717
return;
718
}
719
720
notify_change();
721
}
722
723
void AnimationMultiTrackKeyEdit::_key_ofs_changed(const Ref<Animation> &p_anim, float from, float to) {
724
if (animation != p_anim) {
725
return;
726
}
727
728
for (const KeyValue<int, List<float>> &E : key_ofs_map) {
729
int key = 0;
730
for (const float &key_ofs : E.value) {
731
if (from != key_ofs) {
732
key++;
733
continue;
734
}
735
736
int track = E.key;
737
key_ofs_map[track].get(key) = to;
738
739
if (setting) {
740
return;
741
}
742
743
notify_change();
744
745
return;
746
}
747
}
748
}
749
750
bool AnimationMultiTrackKeyEdit::_set(const StringName &p_name, const Variant &p_value) {
751
bool update_obj = false;
752
bool change_notify_deserved = false;
753
for (const KeyValue<int, List<float>> &E : key_ofs_map) {
754
int track = E.key;
755
for (const float &key_ofs : E.value) {
756
int key = animation->track_find_key(track, key_ofs, Animation::FIND_MODE_APPROX);
757
ERR_FAIL_COND_V(key == -1, false);
758
759
String name = p_name;
760
if (name == "easing") {
761
float val = p_value;
762
float prev_val = animation->track_get_key_transition(track, key);
763
764
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
765
if (!setting) {
766
setting = true;
767
undo_redo->create_action(TTR("Animation Multi Change Transition"), UndoRedo::MERGE_ENDS);
768
}
769
undo_redo->add_do_method(animation.ptr(), "track_set_key_transition", track, key, val);
770
undo_redo->add_undo_method(animation.ptr(), "track_set_key_transition", track, key, prev_val);
771
update_obj = true;
772
}
773
774
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
775
switch (animation->track_get_type(track)) {
776
case Animation::TYPE_POSITION_3D:
777
case Animation::TYPE_ROTATION_3D:
778
case Animation::TYPE_SCALE_3D: {
779
Variant old = animation->track_get_key_value(track, key);
780
if (!setting) {
781
String action_name;
782
switch (animation->track_get_type(track)) {
783
case Animation::TYPE_POSITION_3D:
784
action_name = TTR("Animation Multi Change Position3D");
785
break;
786
case Animation::TYPE_ROTATION_3D:
787
action_name = TTR("Animation Multi Change Rotation3D");
788
break;
789
case Animation::TYPE_SCALE_3D:
790
action_name = TTR("Animation Multi Change Scale3D");
791
break;
792
default: {
793
}
794
}
795
796
setting = true;
797
undo_redo->create_action(action_name);
798
}
799
undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, p_value);
800
undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, old);
801
update_obj = true;
802
} break;
803
case Animation::TYPE_BLEND_SHAPE:
804
case Animation::TYPE_VALUE: {
805
if (name == "value") {
806
Variant value = p_value;
807
808
if (value.get_type() == Variant::NODE_PATH) {
809
_fix_node_path(value, base_map[track]);
810
}
811
812
if (!setting) {
813
setting = true;
814
undo_redo->create_action(TTR("Animation Multi Change Keyframe Value"), UndoRedo::MERGE_ENDS);
815
}
816
Variant prev = animation->track_get_key_value(track, key);
817
undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, value);
818
undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, prev);
819
update_obj = true;
820
}
821
} break;
822
case Animation::TYPE_METHOD: {
823
Dictionary d_old = animation->track_get_key_value(track, key);
824
Dictionary d_new = d_old.duplicate();
825
826
bool mergeable = false;
827
828
if (name == "name") {
829
d_new["method"] = p_value;
830
} else if (name == "arg_count") {
831
Vector<Variant> args = d_old["args"];
832
args.resize(p_value);
833
d_new["args"] = args;
834
change_notify_deserved = true;
835
} else if (name.begins_with("args/")) {
836
Vector<Variant> args = d_old["args"];
837
int idx = name.get_slicec('/', 1).to_int();
838
ERR_FAIL_INDEX_V(idx, args.size(), false);
839
840
String what = name.get_slicec('/', 2);
841
if (what == "type") {
842
Variant::Type t = Variant::Type(int(p_value));
843
844
if (t != args[idx].get_type()) {
845
Callable::CallError err;
846
if (Variant::can_convert_strict(args[idx].get_type(), t)) {
847
Variant old = args[idx];
848
Variant *ptrs[1] = { &old };
849
Variant::construct(t, args.write[idx], (const Variant **)ptrs, 1, err);
850
} else {
851
Variant::construct(t, args.write[idx], nullptr, 0, err);
852
}
853
change_notify_deserved = true;
854
d_new["args"] = args;
855
}
856
} else if (what == "value") {
857
Variant value = p_value;
858
if (value.get_type() == Variant::NODE_PATH) {
859
_fix_node_path(value, base_map[track]);
860
}
861
862
args.write[idx] = value;
863
d_new["args"] = args;
864
mergeable = true;
865
}
866
}
867
868
Variant prev = animation->track_get_key_value(track, key);
869
870
if (!setting) {
871
if (mergeable) {
872
undo_redo->create_action(TTR("Animation Multi Change Call"), UndoRedo::MERGE_ENDS);
873
} else {
874
undo_redo->create_action(TTR("Animation Multi Change Call"));
875
}
876
877
setting = true;
878
}
879
880
undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, d_new);
881
undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, d_old);
882
update_obj = true;
883
} break;
884
case Animation::TYPE_BEZIER: {
885
if (name == "value") {
886
const Variant &value = p_value;
887
888
if (!setting) {
889
setting = true;
890
undo_redo->create_action(TTR("Animation Multi Change Keyframe Value"), UndoRedo::MERGE_ENDS);
891
}
892
float prev = animation->bezier_track_get_key_value(track, key);
893
undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_value", track, key, value);
894
undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_value", track, key, prev);
895
update_obj = true;
896
} else if (name == "in_handle") {
897
const Variant &value = p_value;
898
899
if (!setting) {
900
setting = true;
901
undo_redo->create_action(TTR("Animation Multi Change Keyframe Value"), UndoRedo::MERGE_ENDS);
902
}
903
Vector2 prev = animation->bezier_track_get_key_in_handle(track, key);
904
undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_in_handle", track, key, value);
905
undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", track, key, prev);
906
update_obj = true;
907
} else if (name == "out_handle") {
908
const Variant &value = p_value;
909
910
if (!setting) {
911
setting = true;
912
undo_redo->create_action(TTR("Animation Multi Change Keyframe Value"), UndoRedo::MERGE_ENDS);
913
}
914
Vector2 prev = animation->bezier_track_get_key_out_handle(track, key);
915
undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_out_handle", track, key, value);
916
undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", track, key, prev);
917
update_obj = true;
918
} else if (name == "handle_mode") {
919
const Variant &value = p_value;
920
921
if (!setting) {
922
setting = true;
923
undo_redo->create_action(TTR("Animation Multi Change Keyframe Value"), UndoRedo::MERGE_ENDS, animation.ptr());
924
}
925
int prev_mode = animation->bezier_track_get_key_handle_mode(track, key);
926
Vector2 prev_in_handle = animation->bezier_track_get_key_in_handle(track, key);
927
Vector2 prev_out_handle = animation->bezier_track_get_key_out_handle(track, key);
928
undo_redo->add_do_method(editor, "_bezier_track_set_key_handle_mode", animation.ptr(), track, key, value);
929
undo_redo->add_undo_method(editor, "_bezier_track_set_key_handle_mode", animation.ptr(), track, key, prev_mode);
930
undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", track, key, prev_in_handle);
931
undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", track, key, prev_out_handle);
932
update_obj = true;
933
}
934
} break;
935
case Animation::TYPE_AUDIO: {
936
if (name == "stream") {
937
Ref<AudioStream> stream = p_value;
938
939
if (!setting) {
940
setting = true;
941
undo_redo->create_action(TTR("Animation Multi Change Keyframe Value"), UndoRedo::MERGE_ENDS);
942
}
943
Ref<Resource> prev = animation->audio_track_get_key_stream(track, key);
944
undo_redo->add_do_method(animation.ptr(), "audio_track_set_key_stream", track, key, stream);
945
undo_redo->add_undo_method(animation.ptr(), "audio_track_set_key_stream", track, key, prev);
946
update_obj = true;
947
} else if (name == "start_offset") {
948
float value = p_value;
949
950
if (!setting) {
951
setting = true;
952
undo_redo->create_action(TTR("Animation Multi Change Keyframe Value"), UndoRedo::MERGE_ENDS);
953
}
954
float prev = animation->audio_track_get_key_start_offset(track, key);
955
undo_redo->add_do_method(animation.ptr(), "audio_track_set_key_start_offset", track, key, value);
956
undo_redo->add_undo_method(animation.ptr(), "audio_track_set_key_start_offset", track, key, prev);
957
update_obj = true;
958
} else if (name == "end_offset") {
959
float value = p_value;
960
961
if (!setting) {
962
setting = true;
963
undo_redo->create_action(TTR("Animation Multi Change Keyframe Value"), UndoRedo::MERGE_ENDS);
964
}
965
float prev = animation->audio_track_get_key_end_offset(track, key);
966
undo_redo->add_do_method(animation.ptr(), "audio_track_set_key_end_offset", track, key, value);
967
undo_redo->add_undo_method(animation.ptr(), "audio_track_set_key_end_offset", track, key, prev);
968
update_obj = true;
969
}
970
} break;
971
case Animation::TYPE_ANIMATION: {
972
if (name == "animation") {
973
StringName anim_name = p_value;
974
975
if (!setting) {
976
setting = true;
977
undo_redo->create_action(TTR("Animation Multi Change Keyframe Value"), UndoRedo::MERGE_ENDS);
978
}
979
StringName prev = animation->animation_track_get_key_animation(track, key);
980
undo_redo->add_do_method(animation.ptr(), "animation_track_set_key_animation", track, key, anim_name);
981
undo_redo->add_undo_method(animation.ptr(), "animation_track_set_key_animation", track, key, prev);
982
update_obj = true;
983
}
984
} break;
985
}
986
}
987
}
988
989
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
990
if (setting) {
991
if (update_obj) {
992
undo_redo->add_do_method(this, "_update_obj", animation);
993
undo_redo->add_undo_method(this, "_update_obj", animation);
994
}
995
996
undo_redo->commit_action();
997
setting = false;
998
999
if (change_notify_deserved) {
1000
notify_change();
1001
}
1002
1003
return true;
1004
}
1005
1006
return false;
1007
}
1008
1009
bool AnimationMultiTrackKeyEdit::_get(const StringName &p_name, Variant &r_ret) const {
1010
for (const KeyValue<int, List<float>> &E : key_ofs_map) {
1011
int track = E.key;
1012
for (const float &key_ofs : E.value) {
1013
int key = animation->track_find_key(track, key_ofs, Animation::FIND_MODE_APPROX);
1014
ERR_CONTINUE(key == -1);
1015
1016
String name = p_name;
1017
if (name == "easing") {
1018
r_ret = animation->track_get_key_transition(track, key);
1019
return true;
1020
}
1021
1022
switch (animation->track_get_type(track)) {
1023
case Animation::TYPE_POSITION_3D:
1024
case Animation::TYPE_ROTATION_3D:
1025
case Animation::TYPE_SCALE_3D: {
1026
if (name == "position" || name == "rotation" || name == "scale") {
1027
r_ret = animation->track_get_key_value(track, key);
1028
return true;
1029
}
1030
1031
} break;
1032
case Animation::TYPE_BLEND_SHAPE:
1033
case Animation::TYPE_VALUE: {
1034
if (name == "value") {
1035
r_ret = animation->track_get_key_value(track, key);
1036
return true;
1037
}
1038
1039
} break;
1040
case Animation::TYPE_METHOD: {
1041
Dictionary d = animation->track_get_key_value(track, key);
1042
1043
if (name == "name") {
1044
ERR_FAIL_COND_V(!d.has("method"), false);
1045
r_ret = d["method"];
1046
return true;
1047
}
1048
1049
ERR_FAIL_COND_V(!d.has("args"), false);
1050
1051
Vector<Variant> args = d["args"];
1052
1053
if (name == "arg_count") {
1054
r_ret = args.size();
1055
return true;
1056
}
1057
1058
if (name.begins_with("args/")) {
1059
int idx = name.get_slicec('/', 1).to_int();
1060
ERR_FAIL_INDEX_V(idx, args.size(), false);
1061
1062
String what = name.get_slicec('/', 2);
1063
if (what == "type") {
1064
r_ret = args[idx].get_type();
1065
return true;
1066
}
1067
1068
if (what == "value") {
1069
r_ret = args[idx];
1070
return true;
1071
}
1072
}
1073
1074
} break;
1075
case Animation::TYPE_BEZIER: {
1076
if (name == "value") {
1077
r_ret = animation->bezier_track_get_key_value(track, key);
1078
return true;
1079
}
1080
1081
if (name == "in_handle") {
1082
r_ret = animation->bezier_track_get_key_in_handle(track, key);
1083
return true;
1084
}
1085
1086
if (name == "out_handle") {
1087
r_ret = animation->bezier_track_get_key_out_handle(track, key);
1088
return true;
1089
}
1090
1091
if (name == "handle_mode") {
1092
r_ret = animation->bezier_track_get_key_handle_mode(track, key);
1093
return true;
1094
}
1095
1096
} break;
1097
case Animation::TYPE_AUDIO: {
1098
if (name == "stream") {
1099
r_ret = animation->audio_track_get_key_stream(track, key);
1100
return true;
1101
}
1102
1103
if (name == "start_offset") {
1104
r_ret = animation->audio_track_get_key_start_offset(track, key);
1105
return true;
1106
}
1107
1108
if (name == "end_offset") {
1109
r_ret = animation->audio_track_get_key_end_offset(track, key);
1110
return true;
1111
}
1112
1113
} break;
1114
case Animation::TYPE_ANIMATION: {
1115
if (name == "animation") {
1116
r_ret = animation->animation_track_get_key_animation(track, key);
1117
return true;
1118
}
1119
1120
} break;
1121
}
1122
}
1123
}
1124
1125
return false;
1126
}
1127
1128
void AnimationMultiTrackKeyEdit::_get_property_list(List<PropertyInfo> *p_list) const {
1129
if (animation.is_null()) {
1130
return;
1131
}
1132
1133
int first_track = -1;
1134
float first_key = -1.0;
1135
1136
bool same_track_type = true;
1137
bool same_key_type = true;
1138
for (const KeyValue<int, List<float>> &E : key_ofs_map) {
1139
int track = E.key;
1140
ERR_FAIL_INDEX(track, animation->get_track_count());
1141
1142
if (first_track < 0) {
1143
first_track = track;
1144
}
1145
1146
if (same_track_type) {
1147
if (animation->track_get_type(first_track) != animation->track_get_type(track)) {
1148
same_track_type = false;
1149
same_key_type = false;
1150
}
1151
1152
for (const float &F : E.value) {
1153
int key = animation->track_find_key(track, F, Animation::FIND_MODE_APPROX);
1154
ERR_FAIL_COND(key == -1);
1155
if (first_key < 0) {
1156
first_key = key;
1157
}
1158
1159
if (animation->track_get_key_value(first_track, first_key).get_type() != animation->track_get_key_value(track, key).get_type()) {
1160
same_key_type = false;
1161
}
1162
}
1163
}
1164
}
1165
1166
if (same_track_type) {
1167
switch (animation->track_get_type(first_track)) {
1168
case Animation::TYPE_POSITION_3D: {
1169
p_list->push_back(PropertyInfo(Variant::VECTOR3, "position"));
1170
} break;
1171
case Animation::TYPE_ROTATION_3D: {
1172
p_list->push_back(PropertyInfo(Variant::QUATERNION, "rotation"));
1173
} break;
1174
case Animation::TYPE_SCALE_3D: {
1175
p_list->push_back(PropertyInfo(Variant::VECTOR3, "scale"));
1176
} break;
1177
case Animation::TYPE_BLEND_SHAPE: {
1178
p_list->push_back(PropertyInfo(Variant::FLOAT, "value"));
1179
} break;
1180
case Animation::TYPE_VALUE: {
1181
if (same_key_type) {
1182
Variant v = animation->track_get_key_value(first_track, first_key);
1183
1184
if (hint.type != Variant::NIL) {
1185
PropertyInfo pi = hint;
1186
pi.name = "value";
1187
p_list->push_back(pi);
1188
} else {
1189
PropertyHint val_hint = PROPERTY_HINT_NONE;
1190
String val_hint_string;
1191
1192
if (v.get_type() == Variant::OBJECT) {
1193
// Could actually check the object property if exists..? Yes I will!
1194
Ref<Resource> res = v;
1195
if (res.is_valid()) {
1196
val_hint = PROPERTY_HINT_RESOURCE_TYPE;
1197
val_hint_string = res->get_class();
1198
}
1199
}
1200
1201
if (v.get_type() != Variant::NIL) {
1202
p_list->push_back(PropertyInfo(v.get_type(), "value", val_hint, val_hint_string));
1203
}
1204
}
1205
}
1206
1207
p_list->push_back(PropertyInfo(Variant::FLOAT, "easing", PROPERTY_HINT_EXP_EASING));
1208
} break;
1209
case Animation::TYPE_METHOD: {
1210
p_list->push_back(PropertyInfo(Variant::STRING_NAME, "name"));
1211
1212
p_list->push_back(PropertyInfo(Variant::INT, "arg_count", PROPERTY_HINT_RANGE, "0,32,1,or_greater"));
1213
1214
Dictionary d = animation->track_get_key_value(first_track, first_key);
1215
ERR_FAIL_COND(!d.has("args"));
1216
Vector<Variant> args = d["args"];
1217
String vtypes;
1218
for (int i = 0; i < Variant::VARIANT_MAX; i++) {
1219
if (i > 0) {
1220
vtypes += ",";
1221
}
1222
vtypes += Variant::get_type_name(Variant::Type(i));
1223
}
1224
1225
for (int i = 0; i < args.size(); i++) {
1226
p_list->push_back(PropertyInfo(Variant::INT, "args/" + itos(i) + "/type", PROPERTY_HINT_ENUM, vtypes));
1227
if (args[i].get_type() != Variant::NIL) {
1228
p_list->push_back(PropertyInfo(args[i].get_type(), "args/" + itos(i) + "/value"));
1229
}
1230
}
1231
} break;
1232
case Animation::TYPE_BEZIER: {
1233
p_list->push_back(PropertyInfo(Variant::FLOAT, "value"));
1234
p_list->push_back(PropertyInfo(Variant::VECTOR2, "in_handle"));
1235
p_list->push_back(PropertyInfo(Variant::VECTOR2, "out_handle"));
1236
p_list->push_back(PropertyInfo(Variant::INT, "handle_mode", PROPERTY_HINT_ENUM, "Free,Linear,Balanced,Mirrored"));
1237
} break;
1238
case Animation::TYPE_AUDIO: {
1239
p_list->push_back(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"));
1240
p_list->push_back(PropertyInfo(Variant::FLOAT, "start_offset", PROPERTY_HINT_RANGE, "0,3600,0.0001,or_greater"));
1241
p_list->push_back(PropertyInfo(Variant::FLOAT, "end_offset", PROPERTY_HINT_RANGE, "0,3600,0.0001,or_greater"));
1242
} break;
1243
case Animation::TYPE_ANIMATION: {
1244
if (key_ofs_map.size() > 1) {
1245
break;
1246
}
1247
1248
String animations;
1249
1250
if (root_path) {
1251
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(root_path->get_node_or_null(animation->track_get_path(first_track)));
1252
if (ap) {
1253
List<StringName> anims;
1254
ap->get_animation_list(&anims);
1255
for (const StringName &anim : anims) {
1256
if (!animations.is_empty()) {
1257
animations += ",";
1258
}
1259
1260
animations += String(anim);
1261
}
1262
}
1263
}
1264
1265
if (!animations.is_empty()) {
1266
animations += ",";
1267
}
1268
animations += "[stop]";
1269
1270
p_list->push_back(PropertyInfo(Variant::STRING_NAME, "animation", PROPERTY_HINT_ENUM, animations));
1271
} break;
1272
}
1273
}
1274
}
1275
1276
void AnimationMultiTrackKeyEdit::notify_change() {
1277
notify_property_list_changed();
1278
}
1279
1280
Node *AnimationMultiTrackKeyEdit::get_root_path() {
1281
return root_path;
1282
}
1283
1284
void AnimationMultiTrackKeyEdit::set_use_fps(bool p_enable) {
1285
use_fps = p_enable;
1286
notify_property_list_changed();
1287
}
1288
1289
void AnimationTimelineEdit::_zoom_changed(double) {
1290
double zoom_pivot = 0; // Point on timeline to stay fixed.
1291
double zoom_pivot_delta = 0; // Delta seconds from left-most point on timeline to zoom pivot.
1292
1293
int timeline_width_pixels = get_size().width - get_buttons_width() - get_name_limit();
1294
double timeline_width_seconds = timeline_width_pixels / last_zoom_scale; // Length (in seconds) of visible part of timeline before zoom.
1295
double updated_timeline_width_seconds = timeline_width_pixels / get_zoom_scale(); // Length after zoom.
1296
double updated_timeline_half_width = updated_timeline_width_seconds / 2.0;
1297
bool zooming = updated_timeline_width_seconds < timeline_width_seconds;
1298
1299
double timeline_left = get_value();
1300
double timeline_right = timeline_left + timeline_width_seconds;
1301
double timeline_center = timeline_left + timeline_width_seconds / 2.0;
1302
1303
if (zoom_callback_occurred) { // Zooming with scroll wheel will focus on the position of the mouse.
1304
double zoom_scroll_origin_norm = (zoom_scroll_origin.x - get_name_limit()) / timeline_width_pixels;
1305
zoom_scroll_origin_norm = MAX(zoom_scroll_origin_norm, 0);
1306
zoom_pivot = timeline_left + timeline_width_seconds * zoom_scroll_origin_norm;
1307
zoom_pivot_delta = updated_timeline_width_seconds * zoom_scroll_origin_norm;
1308
zoom_callback_occurred = false;
1309
} else { // Zooming with slider will depend on the current play position.
1310
// If the play position is not in range, or exactly in the center, zoom in on the center.
1311
if (get_play_position() < timeline_left || get_play_position() > timeline_left + timeline_width_seconds || get_play_position() == timeline_center) {
1312
zoom_pivot = timeline_center;
1313
zoom_pivot_delta = updated_timeline_half_width;
1314
}
1315
// Zoom from right if play position is right of center,
1316
// and shrink from right if play position is left of center.
1317
else if ((get_play_position() > timeline_center) == zooming) {
1318
// If play position crosses to other side of center, center it.
1319
bool center_passed = (get_play_position() < timeline_right - updated_timeline_half_width) == zooming;
1320
zoom_pivot = center_passed ? get_play_position() : timeline_right;
1321
double center_offset = CMP_EPSILON * (zooming ? 1 : -1); // Small offset to prevent crossover.
1322
zoom_pivot_delta = center_passed ? updated_timeline_half_width + center_offset : updated_timeline_width_seconds;
1323
}
1324
// Zoom from left if play position is left of center,
1325
// and shrink from left if play position is right of center.
1326
else if ((get_play_position() <= timeline_center) == zooming) {
1327
// If play position crosses to other side of center, center it.
1328
bool center_passed = (get_play_position() > timeline_left + updated_timeline_half_width) == zooming;
1329
zoom_pivot = center_passed ? get_play_position() : timeline_left;
1330
double center_offset = CMP_EPSILON * (zooming ? -1 : 1); // Small offset to prevent crossover.
1331
zoom_pivot_delta = center_passed ? updated_timeline_half_width + center_offset : 0;
1332
}
1333
}
1334
1335
double hscroll_pos = zoom_pivot - zoom_pivot_delta;
1336
hscroll_pos = CLAMP(hscroll_pos, hscroll->get_min(), hscroll->get_max());
1337
1338
hscroll->set_value(hscroll_pos);
1339
hscroll_on_zoom_buffer = hscroll_pos; // In case of page update.
1340
last_zoom_scale = get_zoom_scale();
1341
1342
queue_redraw();
1343
play_position->queue_redraw();
1344
emit_signal(SNAME("zoom_changed"));
1345
}
1346
1347
float AnimationTimelineEdit::get_zoom_scale() const {
1348
return _get_zoom_scale(zoom->get_value());
1349
}
1350
1351
float AnimationTimelineEdit::_get_zoom_scale(double p_zoom_value) const {
1352
float zv = zoom->get_max() - p_zoom_value;
1353
if (zv < 1) {
1354
zv = 1.0 - zv;
1355
return Math::pow(1.0f + zv, 8.0f) * 100;
1356
} else {
1357
return 1.0 / Math::pow(zv, 8.0f) * 100;
1358
}
1359
}
1360
1361
void AnimationTimelineEdit::_anim_length_changed(double p_new_len) {
1362
if (editing) {
1363
return;
1364
}
1365
1366
p_new_len = MAX(SECOND_DECIMAL, p_new_len);
1367
if (use_fps && animation->get_step() > 0) {
1368
p_new_len *= animation->get_step();
1369
}
1370
1371
editing = true;
1372
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
1373
undo_redo->create_action(TTR("Change Animation Length"), UndoRedo::MERGE_ENDS);
1374
undo_redo->add_do_method(animation.ptr(), "set_length", p_new_len);
1375
undo_redo->add_undo_method(animation.ptr(), "set_length", animation->get_length());
1376
undo_redo->commit_action();
1377
editing = false;
1378
queue_redraw();
1379
1380
emit_signal(SNAME("length_changed"), p_new_len);
1381
}
1382
1383
void AnimationTimelineEdit::_anim_loop_pressed() {
1384
if (!read_only) {
1385
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
1386
undo_redo->create_action(TTR("Change Animation Loop"));
1387
switch (animation->get_loop_mode()) {
1388
case Animation::LOOP_NONE: {
1389
undo_redo->add_do_method(animation.ptr(), "set_loop_mode", Animation::LOOP_LINEAR);
1390
} break;
1391
case Animation::LOOP_LINEAR: {
1392
undo_redo->add_do_method(animation.ptr(), "set_loop_mode", Animation::LOOP_PINGPONG);
1393
} break;
1394
case Animation::LOOP_PINGPONG: {
1395
undo_redo->add_do_method(animation.ptr(), "set_loop_mode", Animation::LOOP_NONE);
1396
} break;
1397
default:
1398
break;
1399
}
1400
undo_redo->add_do_method(this, "update_values");
1401
undo_redo->add_undo_method(animation.ptr(), "set_loop_mode", animation->get_loop_mode());
1402
undo_redo->add_undo_method(this, "update_values");
1403
undo_redo->commit_action();
1404
} else {
1405
String base = animation->get_path();
1406
int srpos = base.find("::");
1407
if (srpos != -1) {
1408
base = animation->get_path().substr(0, srpos);
1409
}
1410
1411
if (FileAccess::exists(base + ".import")) {
1412
if (ResourceLoader::get_resource_type(base) == "PackedScene") {
1413
EditorNode::get_singleton()->show_warning(TTR("Can't change loop mode on animation instanced from an imported scene.\n\nTo change this animation's loop mode, navigate to the scene's Advanced Import settings and select the animation.\nYou can then change the loop mode from the inspector menu."));
1414
} else {
1415
EditorNode::get_singleton()->show_warning(TTR("Can't change loop mode on animation instanced from an imported resource."));
1416
}
1417
} else {
1418
if (ResourceLoader::get_resource_type(base) == "PackedScene") {
1419
EditorNode::get_singleton()->show_warning(TTR("Can't change loop mode on animation embedded in another scene.\n\nYou must open this scene and change the animation's loop mode from there."));
1420
} else {
1421
EditorNode::get_singleton()->show_warning(TTR("Can't change loop mode on animation embedded in another resource."));
1422
}
1423
}
1424
1425
update_values();
1426
}
1427
}
1428
1429
int AnimationTimelineEdit::get_buttons_width() const {
1430
const Ref<Texture2D> interp_mode = get_editor_theme_icon(SNAME("TrackContinuous"));
1431
const Ref<Texture2D> interp_type = get_editor_theme_icon(SNAME("InterpRaw"));
1432
const Ref<Texture2D> loop_type = get_editor_theme_icon(SNAME("InterpWrapClamp"));
1433
const Ref<Texture2D> remove_icon = get_editor_theme_icon(SNAME("Remove"));
1434
const Ref<Texture2D> down_icon = get_theme_icon(SNAME("select_arrow"), SNAME("Tree"));
1435
1436
const int h_separation = get_theme_constant(SNAME("h_separation"), SNAME("AnimationTrackEdit"));
1437
1438
int total_w = interp_mode->get_width() + interp_type->get_width() + loop_type->get_width() + remove_icon->get_width();
1439
total_w += (down_icon->get_width() + h_separation) * 4;
1440
1441
return total_w;
1442
}
1443
1444
int AnimationTimelineEdit::get_name_limit() const {
1445
Ref<Texture2D> hsize_icon = get_editor_theme_icon(SNAME("Hsize"));
1446
1447
int filter_track_width = filter_track->is_visible() ? filter_track->get_custom_minimum_size().width : 0;
1448
int limit = MAX(name_limit, add_track->get_minimum_size().width + hsize_icon->get_width() + filter_track_width + 16 * EDSCALE);
1449
1450
limit = MIN(limit, get_size().width - get_buttons_width() - 1);
1451
1452
return limit;
1453
}
1454
1455
void AnimationTimelineEdit::_notification(int p_what) {
1456
switch (p_what) {
1457
case NOTIFICATION_THEME_CHANGED: {
1458
add_track->set_button_icon(get_editor_theme_icon(SNAME("Add")));
1459
loop->set_button_icon(get_editor_theme_icon(SNAME("Loop")));
1460
time_icon->set_texture(get_editor_theme_icon(SNAME("Time")));
1461
filter_track->set_right_icon(get_editor_theme_icon(SNAME("Search")));
1462
1463
add_track->get_popup()->clear();
1464
add_track->get_popup()->add_icon_item(get_editor_theme_icon(SNAME("KeyValue")), TTR("Property Track..."));
1465
add_track->get_popup()->add_icon_item(get_editor_theme_icon(SNAME("KeyXPosition")), TTR("3D Position Track..."));
1466
add_track->get_popup()->add_icon_item(get_editor_theme_icon(SNAME("KeyXRotation")), TTR("3D Rotation Track..."));
1467
add_track->get_popup()->add_icon_item(get_editor_theme_icon(SNAME("KeyXScale")), TTR("3D Scale Track..."));
1468
add_track->get_popup()->add_icon_item(get_editor_theme_icon(SNAME("KeyBlendShape")), TTR("Blend Shape Track..."));
1469
add_track->get_popup()->add_icon_item(get_editor_theme_icon(SNAME("KeyCall")), TTR("Call Method Track..."));
1470
add_track->get_popup()->add_icon_item(get_editor_theme_icon(SNAME("KeyBezier")), TTR("Bezier Curve Track..."));
1471
add_track->get_popup()->add_icon_item(get_editor_theme_icon(SNAME("KeyAudio")), TTR("Audio Playback Track..."));
1472
add_track->get_popup()->add_icon_item(get_editor_theme_icon(SNAME("KeyAnimation")), TTR("Animation Playback Track..."));
1473
} break;
1474
1475
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
1476
if (!EditorSettings::get_singleton()->check_changed_settings_in_group("editors/panning")) {
1477
break;
1478
}
1479
[[fallthrough]];
1480
}
1481
case NOTIFICATION_ENTER_TREE: {
1482
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")));
1483
panner->setup_warped_panning(get_viewport(), EDITOR_GET("editors/panning/warped_mouse_panning"));
1484
} break;
1485
1486
case NOTIFICATION_RESIZED: {
1487
len_hb->set_position(Vector2(get_size().width - get_buttons_width(), 0));
1488
len_hb->set_size(Size2(get_buttons_width(), get_size().height));
1489
int hsize_icon_width = get_editor_theme_icon(SNAME("Hsize"))->get_width();
1490
add_track_hb->set_size(Size2(name_limit - ((hsize_icon_width + 16) * EDSCALE), 0));
1491
} break;
1492
1493
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
1494
RID ae = get_accessibility_element();
1495
ERR_FAIL_COND(ae.is_null());
1496
1497
//TODO
1498
DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_STATIC_TEXT);
1499
DisplayServer::get_singleton()->accessibility_update_set_value(ae, TTR(vformat("The %s is not accessible at this time.", "Animation timeline editor")));
1500
} break;
1501
1502
case NOTIFICATION_DRAW: {
1503
int key_range = get_size().width - get_buttons_width() - get_name_limit();
1504
1505
if (animation.is_null()) {
1506
return;
1507
}
1508
1509
const Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label"));
1510
const int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label"));
1511
1512
const Ref<StyleBox> &stylebox_time_unavailable = get_theme_stylebox(SNAME("time_unavailable"), SNAME("AnimationTimelineEdit"));
1513
const Ref<StyleBox> &stylebox_time_available = get_theme_stylebox(SNAME("time_available"), SNAME("AnimationTimelineEdit"));
1514
1515
const Color v_line_primary_color = get_theme_color(SNAME("v_line_primary_color"), SNAME("AnimationTimelineEdit"));
1516
const Color v_line_secondary_color = get_theme_color(SNAME("v_line_secondary_color"), SNAME("AnimationTimelineEdit"));
1517
const Color h_line_color = get_theme_color(SNAME("h_line_color"), SNAME("AnimationTimelineEdit"));
1518
const Color font_primary_color = get_theme_color(SNAME("font_primary_color"), SNAME("AnimationTimelineEdit"));
1519
const Color font_secondary_color = get_theme_color(SNAME("font_secondary_color"), SNAME("AnimationTimelineEdit"));
1520
1521
const int v_line_primary_margin = get_theme_constant(SNAME("v_line_primary_margin"), SNAME("AnimationTimelineEdit"));
1522
const int v_line_secondary_margin = get_theme_constant(SNAME("v_line_secondary_margin"), SNAME("AnimationTimelineEdit"));
1523
const int v_line_primary_width = get_theme_constant(SNAME("v_line_primary_width"), SNAME("AnimationTimelineEdit"));
1524
const int v_line_secondary_width = get_theme_constant(SNAME("v_line_secondary_width"), SNAME("AnimationTimelineEdit"));
1525
const int text_primary_margin = get_theme_constant(SNAME("text_primary_margin"), SNAME("AnimationTimelineEdit"));
1526
const int text_secondary_margin = get_theme_constant(SNAME("text_secondary_margin"), SNAME("AnimationTimelineEdit"));
1527
1528
int zoomw = key_range;
1529
float scale = get_zoom_scale();
1530
int h = get_size().height;
1531
1532
float l = animation->get_length();
1533
if (l <= 0) {
1534
l = SECOND_DECIMAL; // Avoid crashor.
1535
}
1536
1537
Ref<Texture2D> hsize_icon = get_editor_theme_icon(SNAME("Hsize"));
1538
hsize_rect = Rect2(get_name_limit() - hsize_icon->get_width() - 8 * EDSCALE, (get_size().height - hsize_icon->get_height()) / 2, hsize_icon->get_width(), hsize_icon->get_height());
1539
draw_texture(hsize_icon, hsize_rect.position);
1540
1541
{
1542
float time_min = 0;
1543
float time_max = animation->get_length();
1544
for (int i = 0; i < animation->get_track_count(); i++) {
1545
if (animation->track_get_key_count(i) > 0) {
1546
float beg = animation->track_get_key_time(i, 0);
1547
1548
if (beg < time_min) {
1549
time_min = beg;
1550
}
1551
1552
float end = animation->track_get_key_time(i, animation->track_get_key_count(i) - 1);
1553
1554
if (end > time_max) {
1555
time_max = end;
1556
}
1557
}
1558
}
1559
1560
PackedStringArray markers = animation->get_marker_names();
1561
if (markers.size() > 0) {
1562
float min_marker = animation->get_marker_time(markers[0]);
1563
float max_marker = animation->get_marker_time(markers[markers.size() - 1]);
1564
if (min_marker < time_min) {
1565
time_min = min_marker;
1566
}
1567
if (max_marker > time_max) {
1568
time_max = max_marker;
1569
}
1570
}
1571
1572
float extra = (zoomw / scale) * 0.5;
1573
1574
time_max += extra;
1575
set_min(time_min);
1576
set_max(time_max);
1577
1578
if (zoomw / scale < (time_max - time_min)) {
1579
hscroll->show();
1580
1581
} else {
1582
hscroll->hide();
1583
}
1584
}
1585
1586
set_page(zoomw / scale);
1587
1588
if (hscroll->is_visible() && hscroll_on_zoom_buffer >= 0) {
1589
hscroll->set_value(hscroll_on_zoom_buffer);
1590
hscroll_on_zoom_buffer = -1.0;
1591
}
1592
1593
int end_px = (l - get_value()) * scale;
1594
int begin_px = -get_value() * scale;
1595
1596
{
1597
draw_style_box(stylebox_time_unavailable, Rect2(Point2(get_name_limit(), 0), Point2(zoomw - 1, h)));
1598
1599
if (begin_px < zoomw && end_px > 0) {
1600
if (begin_px < 0) {
1601
begin_px = 0;
1602
}
1603
if (end_px > zoomw) {
1604
end_px = zoomw;
1605
}
1606
1607
draw_style_box(stylebox_time_available, Rect2(Point2(get_name_limit() + begin_px, 0), Point2(end_px - begin_px, h)));
1608
}
1609
}
1610
#define SC_ADJ 100
1611
int dec = 1;
1612
int step = 1;
1613
int decimals = 2;
1614
bool step_found = false;
1615
1616
const float period_width = font->get_char_size('.', font_size).width;
1617
float max_digit_width = font->get_char_size('0', font_size).width;
1618
for (int i = 1; i <= 9; i++) {
1619
const float digit_width = font->get_char_size('0' + i, font_size).width;
1620
max_digit_width = MAX(digit_width, max_digit_width);
1621
}
1622
const int max_sc = int(Math::ceil(zoomw / scale));
1623
const int max_sc_width = String::num(max_sc).length() * Math::ceil(max_digit_width);
1624
1625
const int min_margin = MAX(text_secondary_margin, text_primary_margin);
1626
1627
while (!step_found) {
1628
int min = max_sc_width;
1629
if (decimals > 0) {
1630
min += Math::ceil(period_width + max_digit_width * decimals);
1631
}
1632
1633
min += (min_margin * 2);
1634
1635
static const int _multp[3] = { 1, 2, 5 };
1636
for (int i = 0; i < 3; i++) {
1637
step = (_multp[i] * dec);
1638
if (step * scale / SC_ADJ > min) {
1639
step_found = true;
1640
break;
1641
}
1642
}
1643
if (step_found) {
1644
break;
1645
}
1646
dec *= 10;
1647
decimals--;
1648
if (decimals < 0) {
1649
decimals = 0;
1650
}
1651
}
1652
1653
if (use_fps) {
1654
float step_size = animation->get_step();
1655
if (step_size > 0) {
1656
int prev_frame_ofs = -10000000;
1657
1658
for (int i = 0; i < zoomw; i++) {
1659
float pos = get_value() + double(i) / scale;
1660
float prev = get_value() + (double(i) - 1.0) / scale;
1661
1662
int frame = pos / step_size;
1663
int prev_frame = prev / step_size;
1664
1665
bool sub = Math::floor(prev) == Math::floor(pos);
1666
1667
if (frame != prev_frame && i >= prev_frame_ofs) {
1668
int line_margin = sub ? v_line_secondary_margin : v_line_primary_margin;
1669
int line_width = sub ? v_line_secondary_width : v_line_primary_width;
1670
Color line_color = sub ? v_line_secondary_color : v_line_primary_color;
1671
1672
draw_line(Point2(get_name_limit() + i, 0 + line_margin), Point2(get_name_limit() + i, h - line_margin), line_color, line_width);
1673
1674
int text_margin = sub ? text_secondary_margin : text_primary_margin;
1675
1676
draw_string(font, Point2(get_name_limit() + i + text_margin, (h - font->get_height(font_size)) / 2 + font->get_ascent(font_size)).floor(), itos(frame), HORIZONTAL_ALIGNMENT_LEFT, zoomw - i, font_size, sub ? font_secondary_color : font_primary_color);
1677
1678
prev_frame_ofs = i + font->get_string_size(itos(frame), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).x + text_margin;
1679
}
1680
}
1681
}
1682
1683
} else {
1684
for (int i = 0; i < zoomw; i++) {
1685
float pos = get_value() + double(i) / scale;
1686
float prev = get_value() + (double(i) - 1.0) / scale;
1687
1688
int sc = int(Math::floor(pos * SC_ADJ));
1689
int prev_sc = int(Math::floor(prev * SC_ADJ));
1690
1691
if ((sc / step) != (prev_sc / step) || (prev_sc < 0 && sc >= 0)) {
1692
int scd = sc < 0 ? prev_sc : sc;
1693
bool sub = (((scd - (scd % step)) % (dec * 10)) != 0);
1694
1695
int line_margin = sub ? v_line_secondary_margin : v_line_primary_margin;
1696
int line_width = sub ? v_line_secondary_width : v_line_primary_width;
1697
Color line_color = sub ? v_line_secondary_color : v_line_primary_color;
1698
1699
draw_line(Point2(get_name_limit() + i, 0 + line_margin), Point2(get_name_limit() + i, h - line_margin), line_color, line_width);
1700
1701
int text_margin = sub ? text_secondary_margin : text_primary_margin;
1702
1703
draw_string(font, Point2(get_name_limit() + i + text_margin, (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 ? font_secondary_color : font_primary_color);
1704
}
1705
}
1706
}
1707
1708
draw_line(Vector2(0, get_size().height), get_size(), h_line_color, Math::round(EDSCALE));
1709
update_values();
1710
} break;
1711
}
1712
}
1713
1714
void AnimationTimelineEdit::set_animation(const Ref<Animation> &p_animation, bool p_read_only) {
1715
animation = p_animation;
1716
read_only = p_read_only;
1717
1718
length->set_read_only(read_only);
1719
1720
if (animation.is_valid()) {
1721
len_hb->show();
1722
filter_track->show();
1723
if (read_only) {
1724
add_track->hide();
1725
} else {
1726
add_track->show();
1727
}
1728
play_position->show();
1729
} else {
1730
len_hb->hide();
1731
filter_track->hide();
1732
add_track->hide();
1733
play_position->hide();
1734
}
1735
queue_redraw();
1736
}
1737
1738
Size2 AnimationTimelineEdit::get_minimum_size() const {
1739
Size2 ms = filter_track->get_minimum_size();
1740
const Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label"));
1741
const int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label"));
1742
ms.height = MAX(ms.height, font->get_height(font_size));
1743
ms.width = get_buttons_width() + add_track->get_minimum_size().width + get_editor_theme_icon(SNAME("Hsize"))->get_width() + 2 + 8 * EDSCALE;
1744
return ms;
1745
}
1746
1747
void AnimationTimelineEdit::set_zoom(Range *p_zoom) {
1748
zoom = p_zoom;
1749
zoom->connect(SceneStringName(value_changed), callable_mp(this, &AnimationTimelineEdit::_zoom_changed));
1750
}
1751
1752
void AnimationTimelineEdit::auto_fit() {
1753
if (animation.is_null()) {
1754
return;
1755
}
1756
1757
float anim_end = animation->get_length();
1758
float anim_start = 0;
1759
1760
// Search for keyframe outside animation boundaries to include keyframes before animation start and after animation length.
1761
int track_count = animation->get_track_count();
1762
for (int track = 0; track < track_count; ++track) {
1763
for (int i = 0; i < animation->track_get_key_count(track); i++) {
1764
float key_time = animation->track_get_key_time(track, i);
1765
if (key_time > anim_end) {
1766
anim_end = key_time;
1767
}
1768
if (key_time < anim_start) {
1769
anim_start = key_time;
1770
}
1771
}
1772
}
1773
1774
float anim_length = anim_end - anim_start;
1775
int timeline_width_pixels = get_size().width - get_buttons_width() - get_name_limit();
1776
1777
// I want a little buffer at the end... (5% looks nice and we should keep some space for the bezier handles)
1778
timeline_width_pixels *= 0.95;
1779
1780
// The technique is to reuse the _get_zoom_scale function directly to be sure that the auto_fit is always calculated
1781
// the same way as the zoom slider. It's a little bit more calculation then doing the inverse of get_zoom_scale but
1782
// it's really easier to understand and should always be accurate.
1783
float new_zoom = zoom->get_max();
1784
while (true) {
1785
double test_zoom_scale = _get_zoom_scale(new_zoom);
1786
1787
if (anim_length * test_zoom_scale <= timeline_width_pixels) {
1788
// It fits...
1789
break;
1790
}
1791
1792
new_zoom -= zoom->get_step();
1793
1794
if (new_zoom <= zoom->get_min()) {
1795
new_zoom = zoom->get_min();
1796
break;
1797
}
1798
}
1799
1800
// Horizontal scroll to get_min which should include keyframes that are before the animation start.
1801
hscroll->set_value(hscroll->get_min());
1802
// Set the zoom value... the signal value_changed will be emitted and the timeline will be refreshed correctly!
1803
zoom->set_value(new_zoom);
1804
// The new zoom value must be applied correctly so the scrollbar are updated before we move the scrollbar to
1805
// the beginning of the animation, hence the call deferred.
1806
callable_mp(this, &AnimationTimelineEdit::_scroll_to_start).call_deferred();
1807
}
1808
1809
void AnimationTimelineEdit::_scroll_to_start() {
1810
// Horizontal scroll to get_min which should include keyframes that are before the animation start.
1811
hscroll->set_value(hscroll->get_min());
1812
}
1813
1814
void AnimationTimelineEdit::set_track_edit(AnimationTrackEdit *p_track_edit) {
1815
track_edit = p_track_edit;
1816
}
1817
1818
void AnimationTimelineEdit::set_play_position(float p_pos) {
1819
play_position_pos = p_pos;
1820
play_position->queue_redraw();
1821
}
1822
1823
float AnimationTimelineEdit::get_play_position() const {
1824
return play_position_pos;
1825
}
1826
1827
void AnimationTimelineEdit::update_play_position() {
1828
play_position->queue_redraw();
1829
}
1830
1831
void AnimationTimelineEdit::update_values() {
1832
if (animation.is_null() || editing) {
1833
return;
1834
}
1835
1836
editing = true;
1837
if (use_fps && animation->get_step() > 0.0) {
1838
length->set_value(animation->get_length() / animation->get_step());
1839
length->set_step(FPS_DECIMAL);
1840
length->set_tooltip_text(TTR("Animation length (frames)"));
1841
time_icon->set_tooltip_text(TTR("Animation length (frames)"));
1842
if (track_edit) {
1843
track_edit->editor->_update_key_edit();
1844
track_edit->editor->marker_edit->_update_key_edit();
1845
}
1846
} else {
1847
length->set_value(animation->get_length());
1848
length->set_step(SECOND_DECIMAL);
1849
length->set_tooltip_text(TTR("Animation length (seconds)"));
1850
time_icon->set_tooltip_text(TTR("Animation length (seconds)"));
1851
}
1852
1853
switch (animation->get_loop_mode()) {
1854
case Animation::LOOP_NONE: {
1855
loop->set_button_icon(get_editor_theme_icon(SNAME("Loop")));
1856
loop->set_pressed(false);
1857
} break;
1858
case Animation::LOOP_LINEAR: {
1859
loop->set_button_icon(get_editor_theme_icon(SNAME("Loop")));
1860
loop->set_pressed(true);
1861
} break;
1862
case Animation::LOOP_PINGPONG: {
1863
loop->set_button_icon(get_editor_theme_icon(SNAME("PingPongLoop")));
1864
loop->set_pressed(true);
1865
} break;
1866
default:
1867
break;
1868
}
1869
1870
editing = false;
1871
}
1872
1873
void AnimationTimelineEdit::_play_position_draw() {
1874
if (animation.is_null() || play_position_pos < 0) {
1875
return;
1876
}
1877
1878
float scale = get_zoom_scale();
1879
int h = play_position->get_size().height;
1880
1881
int px = (-get_value() + play_position_pos) * scale + get_name_limit();
1882
1883
if (px >= get_name_limit() && px < (play_position->get_size().width - get_buttons_width())) {
1884
Color color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
1885
play_position->draw_line(Point2(px, 0), Point2(px, h), color, Math::round(2 * EDSCALE));
1886
play_position->draw_texture(
1887
get_editor_theme_icon(SNAME("TimelineIndicator")),
1888
Point2(px - get_editor_theme_icon(SNAME("TimelineIndicator"))->get_width() * 0.5, 0),
1889
color);
1890
}
1891
}
1892
1893
void AnimationTimelineEdit::gui_input(const Ref<InputEvent> &p_event) {
1894
ERR_FAIL_COND(p_event.is_null());
1895
1896
if (panner->gui_input(p_event, get_global_rect())) {
1897
accept_event();
1898
return;
1899
}
1900
1901
const Ref<InputEventMouseButton> mb = p_event;
1902
1903
if (mb.is_valid() && mb->is_pressed() && mb->is_alt_pressed() && mb->get_button_index() == MouseButton::WHEEL_UP) {
1904
if (track_edit) {
1905
track_edit->get_editor()->goto_prev_step(true);
1906
}
1907
accept_event();
1908
}
1909
1910
if (mb.is_valid() && mb->is_pressed() && mb->is_alt_pressed() && mb->get_button_index() == MouseButton::WHEEL_DOWN) {
1911
if (track_edit) {
1912
track_edit->get_editor()->goto_next_step(true);
1913
}
1914
accept_event();
1915
}
1916
1917
if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT && hsize_rect.has_point(mb->get_position())) {
1918
dragging_hsize = true;
1919
dragging_hsize_from = mb->get_position().x;
1920
dragging_hsize_at = name_limit;
1921
}
1922
1923
if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT && dragging_hsize) {
1924
dragging_hsize = false;
1925
}
1926
if (mb.is_valid() && mb->get_position().x > get_name_limit() && mb->get_position().x < (get_size().width - get_buttons_width())) {
1927
if (!panner->is_panning() && mb->get_button_index() == MouseButton::LEFT) {
1928
int x = mb->get_position().x - get_name_limit();
1929
1930
float ofs = x / get_zoom_scale() + get_value();
1931
emit_signal(SNAME("timeline_changed"), ofs, mb->is_alt_pressed());
1932
dragging_timeline = true;
1933
}
1934
}
1935
1936
if (dragging_timeline && mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && !mb->is_pressed()) {
1937
dragging_timeline = false;
1938
}
1939
1940
Ref<InputEventMouseMotion> mm = p_event;
1941
1942
if (mm.is_valid()) {
1943
if (dragging_hsize) {
1944
int ofs = mm->get_position().x - dragging_hsize_from;
1945
name_limit = dragging_hsize_at + ofs;
1946
// Make sure name_limit is clamped to the range that UI allows.
1947
name_limit = get_name_limit();
1948
int hsize_icon_width = get_editor_theme_icon(SNAME("Hsize"))->get_width();
1949
add_track_hb->set_size(Size2(name_limit - ((hsize_icon_width + 16) * EDSCALE), 0));
1950
queue_redraw();
1951
emit_signal(SNAME("name_limit_changed"));
1952
play_position->queue_redraw();
1953
}
1954
if (dragging_timeline) {
1955
int x = mm->get_position().x - get_name_limit();
1956
float ofs = x / get_zoom_scale() + get_value();
1957
emit_signal(SNAME("timeline_changed"), ofs, mm->is_alt_pressed());
1958
}
1959
}
1960
}
1961
1962
Control::CursorShape AnimationTimelineEdit::get_cursor_shape(const Point2 &p_pos) const {
1963
if (dragging_hsize || hsize_rect.has_point(p_pos)) {
1964
// Indicate that the track name column's width can be adjusted
1965
return Control::CURSOR_HSIZE;
1966
} else {
1967
return get_default_cursor_shape();
1968
}
1969
}
1970
1971
void AnimationTimelineEdit::_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event) {
1972
set_value(get_value() - p_scroll_vec.x / get_zoom_scale());
1973
}
1974
1975
void AnimationTimelineEdit::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event) {
1976
double current_zoom_value = get_zoom()->get_value();
1977
zoom_scroll_origin = p_origin;
1978
zoom_callback_occurred = true;
1979
get_zoom()->set_value(MAX(0.01, current_zoom_value - (1.0 - p_zoom_factor)));
1980
}
1981
1982
void AnimationTimelineEdit::set_use_fps(bool p_use_fps) {
1983
use_fps = p_use_fps;
1984
queue_redraw();
1985
}
1986
1987
bool AnimationTimelineEdit::is_using_fps() const {
1988
return use_fps;
1989
}
1990
1991
void AnimationTimelineEdit::set_hscroll(HScrollBar *p_hscroll) {
1992
hscroll = p_hscroll;
1993
}
1994
1995
void AnimationTimelineEdit::_track_added(int p_track) {
1996
emit_signal(SNAME("track_added"), p_track);
1997
}
1998
1999
void AnimationTimelineEdit::_bind_methods() {
2000
ADD_SIGNAL(MethodInfo("zoom_changed"));
2001
ADD_SIGNAL(MethodInfo("name_limit_changed"));
2002
ADD_SIGNAL(MethodInfo("timeline_changed", PropertyInfo(Variant::FLOAT, "position"), PropertyInfo(Variant::BOOL, "timeline_only")));
2003
ADD_SIGNAL(MethodInfo("track_added", PropertyInfo(Variant::INT, "track")));
2004
ADD_SIGNAL(MethodInfo("length_changed", PropertyInfo(Variant::FLOAT, "size")));
2005
ADD_SIGNAL(MethodInfo("filter_changed"));
2006
2007
ClassDB::bind_method(D_METHOD("update_values"), &AnimationTimelineEdit::update_values);
2008
}
2009
2010
AnimationTimelineEdit::AnimationTimelineEdit() {
2011
name_limit = 150 * EDSCALE;
2012
2013
play_position = memnew(Control);
2014
play_position->set_mouse_filter(MOUSE_FILTER_PASS);
2015
add_child(play_position);
2016
play_position->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
2017
play_position->connect(SceneStringName(draw), callable_mp(this, &AnimationTimelineEdit::_play_position_draw));
2018
2019
add_track_hb = memnew(HBoxContainer);
2020
add_child(add_track_hb);
2021
2022
add_track = memnew(MenuButton);
2023
add_track->set_tooltip_text(TTR("Select a new track by type to add to this animation."));
2024
add_track->set_position(Vector2(0, 0));
2025
add_track_hb->add_child(add_track);
2026
filter_track = memnew(LineEdit);
2027
filter_track->set_h_size_flags(SIZE_EXPAND_FILL);
2028
filter_track->set_custom_minimum_size(Vector2(120 * EDSCALE, 0));
2029
filter_track->set_placeholder(TTR("Filter Tracks"));
2030
filter_track->set_tooltip_text(TTR("Filter tracks by entering part of their node name or property."));
2031
filter_track->connect(SceneStringName(text_changed), callable_mp((AnimationTrackEditor *)this, &AnimationTrackEditor::_on_filter_updated));
2032
filter_track->set_clear_button_enabled(true);
2033
filter_track->hide();
2034
add_track_hb->add_child(filter_track);
2035
2036
len_hb = memnew(HBoxContainer);
2037
2038
Control *expander = memnew(Control);
2039
expander->set_h_size_flags(SIZE_EXPAND_FILL);
2040
expander->set_mouse_filter(MOUSE_FILTER_IGNORE);
2041
len_hb->add_child(expander);
2042
2043
time_icon = memnew(TextureRect);
2044
time_icon->set_v_size_flags(SIZE_SHRINK_CENTER);
2045
time_icon->set_tooltip_text(TTR("Animation length (seconds)"));
2046
len_hb->add_child(time_icon);
2047
2048
length = memnew(EditorSpinSlider);
2049
length->set_min(SECOND_DECIMAL);
2050
length->set_max(36000);
2051
length->set_step(SECOND_DECIMAL);
2052
length->set_allow_greater(true);
2053
length->set_custom_minimum_size(Vector2(70 * EDSCALE, 0));
2054
length->set_hide_slider(true);
2055
length->set_tooltip_text(TTR("Animation length (seconds)"));
2056
length->set_accessibility_name(TTRC("Animation length (seconds)"));
2057
length->connect(SceneStringName(value_changed), callable_mp(this, &AnimationTimelineEdit::_anim_length_changed));
2058
len_hb->add_child(length);
2059
2060
loop = memnew(Button);
2061
loop->set_flat(true);
2062
loop->set_tooltip_text(TTR("Animation Looping"));
2063
loop->connect(SceneStringName(pressed), callable_mp(this, &AnimationTimelineEdit::_anim_loop_pressed));
2064
loop->set_toggle_mode(true);
2065
len_hb->add_child(loop);
2066
add_child(len_hb);
2067
2068
add_track->hide();
2069
add_track->get_popup()->connect("index_pressed", callable_mp(this, &AnimationTimelineEdit::_track_added));
2070
len_hb->hide();
2071
2072
panner.instantiate();
2073
panner->set_scroll_zoom_factor(SCROLL_ZOOM_FACTOR_IN);
2074
panner->set_callbacks(callable_mp(this, &AnimationTimelineEdit::_pan_callback), callable_mp(this, &AnimationTimelineEdit::_zoom_callback));
2075
panner->set_pan_axis(ViewPanner::PAN_AXIS_HORIZONTAL);
2076
2077
set_layout_direction(Control::LAYOUT_DIRECTION_LTR);
2078
}
2079
2080
////////////////////////////////////
2081
2082
void AnimationTrackEdit::_notification(int p_what) {
2083
switch (p_what) {
2084
case NOTIFICATION_THEME_CHANGED: {
2085
if (animation.is_null()) {
2086
return;
2087
}
2088
ERR_FAIL_INDEX(track, animation->get_track_count());
2089
2090
type_icon = _get_key_type_icon();
2091
selected_icon = get_editor_theme_icon(SNAME("KeySelected"));
2092
} break;
2093
2094
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
2095
RID ae = get_accessibility_element();
2096
ERR_FAIL_COND(ae.is_null());
2097
2098
//TODO
2099
DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_STATIC_TEXT);
2100
DisplayServer::get_singleton()->accessibility_update_set_value(ae, TTR(vformat("The %s is not accessible at this time.", "Animation track editor")));
2101
} break;
2102
2103
case NOTIFICATION_DRAW: {
2104
if (animation.is_null()) {
2105
return;
2106
}
2107
ERR_FAIL_INDEX(track, animation->get_track_count());
2108
2109
int limit = timeline->get_name_limit();
2110
2111
const Ref<StyleBox> &stylebox_odd = get_theme_stylebox(SNAME("odd"), SNAME("AnimationTrackEdit"));
2112
const Ref<StyleBox> &stylebox_focus = get_theme_stylebox(SNAME("focus"), SNAME("AnimationTrackEdit"));
2113
const Ref<StyleBox> &stylebox_hover = get_theme_stylebox(SceneStringName(hover), SNAME("AnimationTrackEdit"));
2114
2115
const Color h_line_color = get_theme_color(SNAME("h_line_color"), SNAME("AnimationTrackEdit"));
2116
const int h_separation = get_theme_constant(SNAME("h_separation"), SNAME("AnimationTrackEdit"));
2117
const int outer_margin = get_theme_constant(SNAME("outer_margin"), SNAME("AnimationTrackEdit"));
2118
2119
if (track % 2 == 1) {
2120
// Draw a background over odd lines to make long lists of tracks easier to read.
2121
draw_style_box(stylebox_odd, Rect2(Point2(1 * EDSCALE, 0), get_size() - Size2(1 * EDSCALE, 0)));
2122
}
2123
2124
if (hovered) {
2125
// Draw hover feedback.
2126
draw_style_box(stylebox_hover, Rect2(Point2(1 * EDSCALE, 0), get_size() - Size2(1 * EDSCALE, 0)));
2127
}
2128
2129
if (has_focus()) {
2130
// Offside so the horizontal sides aren't cutoff.
2131
draw_style_box(stylebox_focus, Rect2(Point2(1 * EDSCALE, 0), get_size() - Size2(1 * EDSCALE, 0)));
2132
}
2133
2134
const Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label"));
2135
const int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label"));
2136
const Color color = get_theme_color(SceneStringName(font_color), SNAME("Label"));
2137
2138
const Color dc = get_theme_color(SNAME("font_disabled_color"), EditorStringName(Editor));
2139
2140
// Names and icons.
2141
2142
{
2143
Ref<Texture2D> check = animation->track_is_enabled(track) ? get_theme_icon(SNAME("checked"), SNAME("CheckBox")) : get_theme_icon(SNAME("unchecked"), SNAME("CheckBox"));
2144
2145
int ofs = in_group ? outer_margin : 0;
2146
2147
check_rect = Rect2(Point2(ofs, (get_size().height - check->get_height()) / 2).round(), check->get_size());
2148
draw_texture(check, check_rect.position);
2149
ofs += check->get_width() + h_separation;
2150
2151
Ref<Texture2D> key_type_icon = _get_key_type_icon();
2152
draw_texture(key_type_icon, Point2(ofs, (get_size().height - key_type_icon->get_height()) / 2).round());
2153
ofs += key_type_icon->get_width() + h_separation;
2154
2155
NodePath anim_path = animation->track_get_path(track);
2156
Node *node = nullptr;
2157
if (root) {
2158
node = root->get_node_or_null(anim_path);
2159
}
2160
2161
String text;
2162
Color text_color = color;
2163
if (node && EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) {
2164
text_color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
2165
}
2166
2167
if (in_group) {
2168
if (animation->track_get_type(track) == Animation::TYPE_METHOD) {
2169
text = TTR("Functions:");
2170
} else if (animation->track_get_type(track) == Animation::TYPE_AUDIO) {
2171
text = TTR("Audio Clips:");
2172
} else if (animation->track_get_type(track) == Animation::TYPE_ANIMATION) {
2173
text = TTR("Animation Clips:");
2174
} else {
2175
text += anim_path.get_concatenated_subnames();
2176
}
2177
text_color.a *= 0.7;
2178
} else if (node) {
2179
Ref<Texture2D> icon = EditorNode::get_singleton()->get_object_icon(node, "Node");
2180
const Vector2 icon_size = Vector2(1, 1) * get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor));
2181
2182
icon_rect = Rect2(Point2(ofs, (get_size().height - check->get_height()) / 2).round(), icon->get_size());
2183
draw_texture_rect(icon, icon_rect);
2184
icon_cache = icon;
2185
2186
text = String() + node->get_name() + ":" + anim_path.get_concatenated_subnames();
2187
ofs += h_separation;
2188
ofs += icon_size.x;
2189
2190
} else {
2191
icon_cache = key_type_icon;
2192
2193
text = String(anim_path);
2194
}
2195
2196
path_cache = text;
2197
2198
path_rect = Rect2(ofs, 0, limit - ofs - h_separation, get_size().height);
2199
2200
Vector2 string_pos = Point2(ofs, (get_size().height - font->get_height(font_size)) / 2 + font->get_ascent(font_size));
2201
string_pos = string_pos.floor();
2202
draw_string(font, string_pos, text, HORIZONTAL_ALIGNMENT_LEFT, limit - ofs - h_separation, font_size, text_color);
2203
2204
draw_line(Point2(limit, 0), Point2(limit, get_size().height), h_line_color, Math::round(EDSCALE));
2205
}
2206
2207
// Marker sections.
2208
2209
{
2210
float scale = timeline->get_zoom_scale();
2211
int limit_end = get_size().width - timeline->get_buttons_width();
2212
2213
PackedStringArray section = editor->get_selected_section();
2214
if (section.size() == 2) {
2215
StringName start_marker = section[0];
2216
StringName end_marker = section[1];
2217
double start_time = animation->get_marker_time(start_marker);
2218
double end_time = animation->get_marker_time(end_marker);
2219
2220
// When AnimationPlayer is playing, don't move the preview rect, so it still indicates the playback section.
2221
AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();
2222
if (editor->is_marker_moving_selection() && !(player && player->is_playing())) {
2223
start_time += editor->get_marker_moving_selection_offset();
2224
end_time += editor->get_marker_moving_selection_offset();
2225
}
2226
2227
if (start_time < animation->get_length() && end_time >= 0) {
2228
float start_ofs = MAX(0, start_time) - timeline->get_value();
2229
float end_ofs = MIN(animation->get_length(), end_time) - timeline->get_value();
2230
start_ofs = start_ofs * scale + limit;
2231
end_ofs = end_ofs * scale + limit;
2232
start_ofs = MAX(start_ofs, limit);
2233
end_ofs = MIN(end_ofs, limit_end);
2234
Rect2 rect;
2235
rect.set_position(Vector2(start_ofs, 0));
2236
rect.set_size(Vector2(end_ofs - start_ofs, get_size().height));
2237
2238
draw_rect(rect, Color(1, 0.1, 0.1, 0.2));
2239
}
2240
}
2241
}
2242
2243
// Marker overlays.
2244
2245
{
2246
float scale = timeline->get_zoom_scale();
2247
PackedStringArray markers = animation->get_marker_names();
2248
for (const StringName marker : markers) {
2249
double time = animation->get_marker_time(marker);
2250
if (editor->is_marker_selected(marker) && editor->is_marker_moving_selection()) {
2251
time += editor->get_marker_moving_selection_offset();
2252
}
2253
if (time >= 0) {
2254
float offset = time - timeline->get_value();
2255
offset = offset * scale + limit;
2256
Color marker_color = animation->get_marker_color(marker);
2257
marker_color.a = 0.2;
2258
draw_line(Point2(offset, 0), Point2(offset, get_size().height), marker_color, Math::round(EDSCALE));
2259
}
2260
}
2261
}
2262
2263
// Keyframes.
2264
2265
draw_bg(limit, get_size().width - timeline->get_buttons_width() - outer_margin);
2266
2267
{
2268
float scale = timeline->get_zoom_scale();
2269
int limit_end = get_size().width - timeline->get_buttons_width() - outer_margin;
2270
2271
for (int i = 0; i < animation->track_get_key_count(track); i++) {
2272
float offset = animation->track_get_key_time(track, i) - timeline->get_value();
2273
if (editor->is_key_selected(track, i) && editor->is_moving_selection()) {
2274
offset = offset + editor->get_moving_selection_offset();
2275
}
2276
offset = offset * scale + limit;
2277
if (i < animation->track_get_key_count(track) - 1) {
2278
float offset_n = animation->track_get_key_time(track, i + 1) - timeline->get_value();
2279
if (editor->is_key_selected(track, i + 1) && editor->is_moving_selection()) {
2280
offset_n = offset_n + editor->get_moving_selection_offset();
2281
}
2282
offset_n = offset_n * scale + limit;
2283
float offset_last = limit_end;
2284
if (i < animation->track_get_key_count(track) - 2) {
2285
offset_last = animation->track_get_key_time(track, i + 2) - timeline->get_value();
2286
if (editor->is_key_selected(track, i + 2) && editor->is_moving_selection()) {
2287
offset_last = offset_last + editor->get_moving_selection_offset();
2288
}
2289
offset_last = offset_last * scale + limit;
2290
}
2291
int limit_string = (editor->is_key_selected(track, i + 1) && editor->is_moving_selection()) ? int(offset_last) : int(offset_n);
2292
if (editor->is_key_selected(track, i) && editor->is_moving_selection()) {
2293
limit_string = int(MAX(limit_end, offset_last));
2294
}
2295
draw_key_link(i, scale, int(offset), int(offset_n), limit, limit_end);
2296
draw_key(i, scale, int(offset), editor->is_key_selected(track, i), limit, limit_string);
2297
continue;
2298
}
2299
2300
draw_key(i, scale, int(offset), editor->is_key_selected(track, i), limit, limit_end);
2301
}
2302
}
2303
2304
draw_fg(limit, get_size().width - timeline->get_buttons_width() - outer_margin);
2305
2306
// Buttons.
2307
2308
{
2309
Ref<Texture2D> wrap_icon[2] = {
2310
get_editor_theme_icon(SNAME("InterpWrapClamp")),
2311
get_editor_theme_icon(SNAME("InterpWrapLoop")),
2312
};
2313
Ref<Texture2D> interp_icon[5] = {
2314
get_editor_theme_icon(SNAME("InterpRaw")),
2315
get_editor_theme_icon(SNAME("InterpLinear")),
2316
get_editor_theme_icon(SNAME("InterpCubic")),
2317
get_editor_theme_icon(SNAME("InterpLinearAngle")),
2318
get_editor_theme_icon(SNAME("InterpCubicAngle")),
2319
};
2320
Ref<Texture2D> cont_icon[3] = {
2321
get_editor_theme_icon(SNAME("TrackContinuous")),
2322
get_editor_theme_icon(SNAME("TrackDiscrete")),
2323
get_editor_theme_icon(SNAME("TrackCapture"))
2324
};
2325
Ref<Texture2D> blend_icon[2] = {
2326
get_editor_theme_icon(SNAME("UseBlendEnable")),
2327
get_editor_theme_icon(SNAME("UseBlendDisable")),
2328
};
2329
2330
int ofs = get_size().width - timeline->get_buttons_width() - outer_margin;
2331
2332
const Ref<Texture2D> down_icon = get_theme_icon(SNAME("select_arrow"), SNAME("Tree"));
2333
2334
draw_line(Point2(ofs, 0), Point2(ofs, get_size().height), h_line_color, Math::round(EDSCALE));
2335
2336
ofs += h_separation;
2337
{
2338
// Callmode.
2339
2340
Animation::UpdateMode update_mode;
2341
2342
if (animation->track_get_type(track) == Animation::TYPE_VALUE) {
2343
update_mode = animation->value_track_get_update_mode(track);
2344
} else {
2345
update_mode = Animation::UPDATE_CONTINUOUS;
2346
}
2347
2348
Ref<Texture2D> update_icon = cont_icon[update_mode];
2349
2350
update_mode_rect.position.x = ofs;
2351
update_mode_rect.position.y = Math::round((get_size().height - update_icon->get_height()) / 2);
2352
update_mode_rect.size = update_icon->get_size();
2353
2354
if (!animation->track_is_compressed(track) && animation->track_get_type(track) == Animation::TYPE_VALUE) {
2355
draw_texture(update_icon, update_mode_rect.position);
2356
}
2357
if (animation->track_get_type(track) == Animation::TYPE_AUDIO) {
2358
Ref<Texture2D> use_blend_icon = blend_icon[animation->audio_track_is_use_blend(track) ? 0 : 1];
2359
Vector2 use_blend_icon_pos = update_mode_rect.position + (update_mode_rect.size - use_blend_icon->get_size()) / 2;
2360
draw_texture(use_blend_icon, use_blend_icon_pos);
2361
}
2362
// Make it easier to click.
2363
update_mode_rect.position.y = 0;
2364
update_mode_rect.size.y = get_size().height;
2365
2366
ofs += update_icon->get_width() + h_separation / 2;
2367
update_mode_rect.size.x += h_separation / 2;
2368
2369
if (!read_only) {
2370
if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_AUDIO) {
2371
draw_texture(down_icon, Vector2(ofs, (get_size().height - down_icon->get_height()) / 2).round());
2372
update_mode_rect.size.x += down_icon->get_width();
2373
} else if (animation->track_get_type(track) == Animation::TYPE_BEZIER) {
2374
update_mode_rect.size.x += down_icon->get_width();
2375
update_mode_rect = Rect2();
2376
} else {
2377
update_mode_rect = Rect2();
2378
}
2379
} else {
2380
update_mode_rect = Rect2();
2381
}
2382
2383
ofs += down_icon->get_width();
2384
draw_line(Point2(ofs + h_separation * 0.5, 0), Point2(ofs + h_separation * 0.5, get_size().height), h_line_color, Math::round(EDSCALE));
2385
ofs += h_separation;
2386
}
2387
2388
{
2389
// Interp.
2390
2391
Animation::InterpolationType interp_mode = animation->track_get_interpolation_type(track);
2392
2393
Ref<Texture2D> icon = interp_icon[interp_mode];
2394
2395
interp_mode_rect.position.x = ofs;
2396
interp_mode_rect.position.y = Math::round((get_size().height - icon->get_height()) / 2);
2397
interp_mode_rect.size = icon->get_size();
2398
2399
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)) {
2400
draw_texture(icon, interp_mode_rect.position);
2401
}
2402
// Make it easier to click.
2403
interp_mode_rect.position.y = 0;
2404
interp_mode_rect.size.y = get_size().height;
2405
2406
ofs += icon->get_width() + h_separation / 2;
2407
interp_mode_rect.size.x += h_separation / 2;
2408
2409
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)) {
2410
draw_texture(down_icon, Vector2(ofs, (get_size().height - down_icon->get_height()) / 2).round());
2411
interp_mode_rect.size.x += down_icon->get_width();
2412
} else {
2413
interp_mode_rect = Rect2();
2414
}
2415
2416
ofs += down_icon->get_width();
2417
draw_line(Point2(ofs + h_separation * 0.5, 0), Point2(ofs + h_separation * 0.5, get_size().height), h_line_color, Math::round(EDSCALE));
2418
ofs += h_separation;
2419
}
2420
2421
{
2422
// Loop.
2423
2424
bool loop_wrap = animation->track_get_interpolation_loop_wrap(track);
2425
2426
Ref<Texture2D> icon = wrap_icon[loop_wrap ? 1 : 0];
2427
2428
loop_wrap_rect.position.x = ofs;
2429
loop_wrap_rect.position.y = Math::round((get_size().height - icon->get_height()) / 2);
2430
loop_wrap_rect.size = icon->get_size();
2431
2432
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)) {
2433
draw_texture(icon, loop_wrap_rect.position);
2434
}
2435
2436
loop_wrap_rect.position.y = 0;
2437
loop_wrap_rect.size.y = get_size().height;
2438
2439
ofs += icon->get_width() + h_separation / 2;
2440
loop_wrap_rect.size.x += h_separation / 2;
2441
2442
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)) {
2443
draw_texture(down_icon, Vector2(ofs, (get_size().height - down_icon->get_height()) / 2).round());
2444
loop_wrap_rect.size.x += down_icon->get_width();
2445
} else {
2446
loop_wrap_rect = Rect2();
2447
}
2448
2449
ofs += down_icon->get_width();
2450
draw_line(Point2(ofs + h_separation * 0.5, 0), Point2(ofs + h_separation * 0.5, get_size().height), h_line_color, Math::round(EDSCALE));
2451
ofs += h_separation;
2452
}
2453
2454
{
2455
// Erase.
2456
2457
Ref<Texture2D> icon = get_editor_theme_icon(animation->track_is_compressed(track) ? SNAME("Lock") : SNAME("Remove"));
2458
2459
remove_rect.position.x = ofs + ((get_size().width - ofs) - icon->get_width()) - outer_margin;
2460
remove_rect.position.y = Math::round((get_size().height - icon->get_height()) / 2);
2461
remove_rect.size = icon->get_size();
2462
2463
if (read_only) {
2464
draw_texture(icon, remove_rect.position, dc);
2465
} else {
2466
draw_texture(icon, remove_rect.position);
2467
}
2468
}
2469
}
2470
2471
if (in_group) {
2472
draw_line(Vector2(timeline->get_name_limit(), get_size().height), get_size(), h_line_color, Math::round(EDSCALE));
2473
} else {
2474
draw_line(Vector2(0, get_size().height), get_size(), h_line_color, Math::round(EDSCALE));
2475
}
2476
2477
if (dropping_at != 0) {
2478
Color drop_color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
2479
if (dropping_at < 0) {
2480
draw_line(Vector2(0, 0), Vector2(get_size().width, 0), drop_color, Math::round(EDSCALE));
2481
} else {
2482
draw_line(Vector2(0, get_size().height), get_size(), drop_color, Math::round(EDSCALE));
2483
}
2484
}
2485
} break;
2486
2487
case NOTIFICATION_MOUSE_ENTER:
2488
hovered = true;
2489
queue_redraw();
2490
break;
2491
case NOTIFICATION_MOUSE_EXIT:
2492
hovered = false;
2493
// When the mouse cursor exits the track, we're no longer hovering any keyframe.
2494
hovering_key_idx = -1;
2495
queue_redraw();
2496
[[fallthrough]];
2497
case NOTIFICATION_DRAG_END: {
2498
cancel_drop();
2499
} break;
2500
}
2501
}
2502
2503
int AnimationTrackEdit::get_key_height() const {
2504
if (animation.is_null()) {
2505
return 0;
2506
}
2507
2508
return type_icon->get_height();
2509
}
2510
2511
Rect2 AnimationTrackEdit::get_key_rect(int p_index, float p_pixels_sec) {
2512
if (animation.is_null()) {
2513
return Rect2();
2514
}
2515
Rect2 rect = Rect2(-type_icon->get_width() / 2, 0, type_icon->get_width(), get_size().height);
2516
2517
// Make it a big easier to click.
2518
rect.position.x -= rect.size.x * 0.5;
2519
rect.size.x *= 2;
2520
return rect;
2521
}
2522
2523
bool AnimationTrackEdit::is_key_selectable_by_distance() const {
2524
return true;
2525
}
2526
2527
void 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) {
2528
if (p_next_x < p_clip_left) {
2529
return;
2530
}
2531
if (p_x > p_clip_right) {
2532
return;
2533
}
2534
2535
Variant current = animation->track_get_key_value(get_track(), p_index);
2536
Variant next = animation->track_get_key_value(get_track(), p_index + 1);
2537
if (current != next || animation->track_get_type(get_track()) == Animation::TrackType::TYPE_METHOD) {
2538
return;
2539
}
2540
2541
Color color = get_theme_color(SceneStringName(font_color), SNAME("Label"));
2542
color.a = 0.5;
2543
2544
int from_x = MAX(p_x, p_clip_left);
2545
int to_x = MIN(p_next_x, p_clip_right);
2546
2547
draw_line(Point2(from_x + 1, get_size().height / 2), Point2(to_x, get_size().height / 2), color, Math::round(2 * EDSCALE));
2548
}
2549
2550
void AnimationTrackEdit::draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) {
2551
if (animation.is_null()) {
2552
return;
2553
}
2554
2555
if (p_x < p_clip_left || p_x > p_clip_right) {
2556
return;
2557
}
2558
2559
Ref<Texture2D> icon_to_draw = p_selected ? selected_icon : type_icon;
2560
2561
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))) {
2562
// Use a different icon for keys with non-linear easing.
2563
icon_to_draw = get_editor_theme_icon(p_selected ? SNAME("KeyEasedSelected") : SNAME("KeyValueEased"));
2564
}
2565
2566
// Override type icon for invalid value keys, unless selected.
2567
if (!p_selected && animation->track_get_type(track) == Animation::TYPE_VALUE) {
2568
const Variant &v = animation->track_get_key_value(track, p_index);
2569
Variant::Type valid_type = Variant::NIL;
2570
if (!_is_value_key_valid(v, valid_type)) {
2571
icon_to_draw = get_editor_theme_icon(SNAME("KeyInvalid"));
2572
}
2573
}
2574
2575
Vector2 ofs(p_x - icon_to_draw->get_width() / 2, (get_size().height - icon_to_draw->get_height()) / 2);
2576
2577
if (animation->track_get_type(track) == Animation::TYPE_METHOD) {
2578
const Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label"));
2579
const int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label"));
2580
Color color = get_theme_color(SceneStringName(font_color), SNAME("Label"));
2581
color.a = 0.5;
2582
2583
Dictionary d = animation->track_get_key_value(track, p_index);
2584
String text;
2585
2586
if (d.has("method")) {
2587
text += String(d["method"]);
2588
}
2589
text += "(";
2590
Vector<Variant> args;
2591
if (d.has("args")) {
2592
args = d["args"];
2593
}
2594
for (int i = 0; i < args.size(); i++) {
2595
if (i > 0) {
2596
text += ", ";
2597
}
2598
text += args[i].get_construct_string();
2599
}
2600
text += ")";
2601
2602
int limit = ((p_selected && editor->is_moving_selection()) || editor->is_function_name_pressed()) ? 0 : MAX(0, p_clip_right - p_x - icon_to_draw->get_width() * 2);
2603
2604
if (limit > 0) {
2605
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);
2606
}
2607
}
2608
2609
// Use a different color for the currently hovered key.
2610
// The color multiplier is chosen to work with both dark and light editor themes,
2611
// and on both unselected and selected key icons.
2612
draw_texture(
2613
icon_to_draw,
2614
ofs,
2615
p_index == hovering_key_idx ? get_theme_color(SNAME("folder_icon_color"), SNAME("FileDialog")) : Color(1, 1, 1));
2616
}
2617
2618
// Helper.
2619
void AnimationTrackEdit::draw_rect_clipped(const Rect2 &p_rect, const Color &p_color, bool p_filled) {
2620
int clip_left = timeline->get_name_limit();
2621
int clip_right = get_size().width - timeline->get_buttons_width();
2622
2623
if (p_rect.position.x > clip_right) {
2624
return;
2625
}
2626
if (p_rect.position.x + p_rect.size.x < clip_left) {
2627
return;
2628
}
2629
Rect2 clip = Rect2(clip_left, 0, clip_right - clip_left, get_size().height);
2630
draw_rect(clip.intersection(p_rect), p_color, p_filled);
2631
}
2632
2633
void AnimationTrackEdit::draw_bg(int p_clip_left, int p_clip_right) {
2634
}
2635
2636
void AnimationTrackEdit::draw_fg(int p_clip_left, int p_clip_right) {
2637
}
2638
2639
void AnimationTrackEdit::draw_texture_region_clipped(const Ref<Texture2D> &p_texture, const Rect2 &p_rect, const Rect2 &p_region) {
2640
int clip_left = timeline->get_name_limit();
2641
int clip_right = get_size().width - timeline->get_buttons_width();
2642
2643
// Clip left and right.
2644
if (clip_left > p_rect.position.x + p_rect.size.x) {
2645
return;
2646
}
2647
if (clip_right < p_rect.position.x) {
2648
return;
2649
}
2650
2651
Rect2 rect = p_rect;
2652
Rect2 region = p_region;
2653
2654
if (clip_left > rect.position.x) {
2655
int rect_pixels = (clip_left - rect.position.x);
2656
int region_pixels = rect_pixels * region.size.x / rect.size.x;
2657
2658
rect.position.x += rect_pixels;
2659
rect.size.x -= rect_pixels;
2660
2661
region.position.x += region_pixels;
2662
region.size.x -= region_pixels;
2663
}
2664
2665
if (clip_right < rect.position.x + rect.size.x) {
2666
int rect_pixels = rect.position.x + rect.size.x - clip_right;
2667
int region_pixels = rect_pixels * region.size.x / rect.size.x;
2668
2669
rect.size.x -= rect_pixels;
2670
region.size.x -= region_pixels;
2671
}
2672
2673
draw_texture_rect_region(p_texture, rect, region);
2674
}
2675
2676
int AnimationTrackEdit::get_track() const {
2677
return track;
2678
}
2679
2680
Ref<Animation> AnimationTrackEdit::get_animation() const {
2681
return animation;
2682
}
2683
2684
void AnimationTrackEdit::set_animation_and_track(const Ref<Animation> &p_animation, int p_track, bool p_read_only) {
2685
animation = p_animation;
2686
read_only = p_read_only;
2687
2688
track = p_track;
2689
queue_redraw();
2690
2691
ERR_FAIL_INDEX(track, animation->get_track_count());
2692
2693
node_path = animation->track_get_path(p_track);
2694
type_icon = _get_key_type_icon();
2695
selected_icon = get_editor_theme_icon(SNAME("KeySelected"));
2696
}
2697
2698
NodePath AnimationTrackEdit::get_path() const {
2699
return node_path;
2700
}
2701
2702
Size2 AnimationTrackEdit::get_minimum_size() const {
2703
Ref<Texture2D> texture = get_editor_theme_icon(SNAME("Object"));
2704
const Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label"));
2705
const int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label"));
2706
const int separation = get_theme_constant(SNAME("v_separation"), SNAME("ItemList"));
2707
2708
int max_h = MAX(texture->get_height(), font->get_height(font_size));
2709
max_h = MAX(max_h, get_key_height());
2710
2711
return Vector2(1, max_h + separation);
2712
}
2713
2714
void AnimationTrackEdit::set_timeline(AnimationTimelineEdit *p_timeline) {
2715
timeline = p_timeline;
2716
timeline->set_track_edit(this);
2717
timeline->connect("zoom_changed", callable_mp(this, &AnimationTrackEdit::_zoom_changed));
2718
timeline->connect("name_limit_changed", callable_mp(this, &AnimationTrackEdit::_zoom_changed));
2719
}
2720
2721
void AnimationTrackEdit::set_editor(AnimationTrackEditor *p_editor) {
2722
editor = p_editor;
2723
}
2724
2725
void AnimationTrackEdit::_play_position_draw() {
2726
if (animation.is_null() || play_position_pos < 0) {
2727
return;
2728
}
2729
2730
float scale = timeline->get_zoom_scale();
2731
int h = get_size().height;
2732
2733
int px = (-timeline->get_value() + play_position_pos) * scale + timeline->get_name_limit();
2734
2735
if (px >= timeline->get_name_limit() && px < (get_size().width - timeline->get_buttons_width())) {
2736
Color color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
2737
play_position->draw_line(Point2(px, 0), Point2(px, h), color, Math::round(2 * EDSCALE));
2738
}
2739
}
2740
2741
void AnimationTrackEdit::set_play_position(float p_pos) {
2742
play_position_pos = p_pos;
2743
play_position->queue_redraw();
2744
}
2745
2746
void AnimationTrackEdit::update_play_position() {
2747
play_position->queue_redraw();
2748
}
2749
2750
void AnimationTrackEdit::set_root(Node *p_root) {
2751
root = p_root;
2752
}
2753
2754
void AnimationTrackEdit::_zoom_changed() {
2755
queue_redraw();
2756
play_position->queue_redraw();
2757
}
2758
2759
void AnimationTrackEdit::_path_submitted(const String &p_text) {
2760
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
2761
undo_redo->create_action(TTR("Change Track Path"));
2762
undo_redo->add_do_method(animation.ptr(), "track_set_path", track, p_text);
2763
undo_redo->add_undo_method(animation.ptr(), "track_set_path", track, animation->track_get_path(track));
2764
undo_redo->commit_action();
2765
path_popup->hide();
2766
}
2767
2768
bool AnimationTrackEdit::_is_value_key_valid(const Variant &p_key_value, Variant::Type &r_valid_type) const {
2769
if (root == nullptr || !root->has_node_and_resource(animation->track_get_path(track))) {
2770
return false;
2771
}
2772
Ref<Resource> res;
2773
Vector<StringName> leftover_path;
2774
Node *node = root->get_node_and_resource(animation->track_get_path(track), res, leftover_path);
2775
2776
Object *obj = nullptr;
2777
if (res.is_valid()) {
2778
obj = res.ptr();
2779
} else if (node) {
2780
obj = node;
2781
}
2782
2783
bool prop_exists = false;
2784
if (obj) {
2785
r_valid_type = obj->get_static_property_type_indexed(leftover_path, &prop_exists);
2786
}
2787
2788
return (!prop_exists || Variant::can_convert(p_key_value.get_type(), r_valid_type));
2789
}
2790
2791
Ref<Texture2D> AnimationTrackEdit::_get_key_type_icon() const {
2792
const Ref<Texture2D> type_icons[9] = {
2793
get_editor_theme_icon(SNAME("KeyValue")),
2794
get_editor_theme_icon(SNAME("KeyTrackPosition")),
2795
get_editor_theme_icon(SNAME("KeyTrackRotation")),
2796
get_editor_theme_icon(SNAME("KeyTrackScale")),
2797
get_editor_theme_icon(SNAME("KeyTrackBlendShape")),
2798
get_editor_theme_icon(SNAME("KeyCall")),
2799
get_editor_theme_icon(SNAME("KeyBezier")),
2800
get_editor_theme_icon(SNAME("KeyAudio")),
2801
get_editor_theme_icon(SNAME("KeyAnimation"))
2802
};
2803
return type_icons[animation->track_get_type(track)];
2804
}
2805
2806
Control::CursorShape AnimationTrackEdit::get_cursor_shape(const Point2 &p_pos) const {
2807
if (command_or_control_pressed && animation->track_get_type(track) == Animation::TYPE_METHOD && hovering_key_idx != -1) {
2808
return Control::CURSOR_POINTING_HAND;
2809
}
2810
return get_default_cursor_shape();
2811
}
2812
2813
String AnimationTrackEdit::get_tooltip(const Point2 &p_pos) const {
2814
if (check_rect.has_point(p_pos)) {
2815
return TTR("Toggle this track on/off.");
2816
}
2817
2818
if (icon_rect.has_point(p_pos)) {
2819
return TTR("Select node in scene.");
2820
}
2821
2822
// Don't overlap track keys if they start at 0.
2823
if (path_rect.has_point(p_pos + Size2(type_icon->get_width(), 0))) {
2824
return String(animation->track_get_path(track));
2825
}
2826
2827
if (update_mode_rect.has_point(p_pos)) {
2828
if (animation->track_get_type(track) == Animation::TYPE_AUDIO) {
2829
return TTR("Use Blend");
2830
} else {
2831
return TTR("Update Mode (How this property is set)");
2832
}
2833
}
2834
2835
if (interp_mode_rect.has_point(p_pos)) {
2836
return TTR("Interpolation Mode");
2837
}
2838
2839
if (loop_wrap_rect.has_point(p_pos)) {
2840
return TTR("Loop Wrap Mode (Interpolate end with beginning on loop)");
2841
}
2842
2843
if (remove_rect.has_point(p_pos)) {
2844
return TTR("Remove this track.");
2845
}
2846
2847
int limit = timeline->get_name_limit();
2848
int limit_end = get_size().width - timeline->get_buttons_width();
2849
// Left Border including space occupied by keyframes on t=0.
2850
int limit_start_hitbox = limit - type_icon->get_width();
2851
2852
if (p_pos.x >= limit_start_hitbox && p_pos.x <= limit_end) {
2853
int key_idx = -1;
2854
float key_distance = 1e20;
2855
2856
// Select should happen in the opposite order of drawing for more accurate overlap select.
2857
for (int i = animation->track_get_key_count(track) - 1; i >= 0; i--) {
2858
Rect2 rect = const_cast<AnimationTrackEdit *>(this)->get_key_rect(i, timeline->get_zoom_scale());
2859
float offset = animation->track_get_key_time(track, i) - timeline->get_value();
2860
offset = offset * timeline->get_zoom_scale() + limit;
2861
rect.position.x += offset;
2862
2863
if (rect.has_point(p_pos)) {
2864
if (const_cast<AnimationTrackEdit *>(this)->is_key_selectable_by_distance()) {
2865
float distance = Math::abs(offset - p_pos.x);
2866
if (key_idx == -1 || distance < key_distance) {
2867
key_idx = i;
2868
key_distance = distance;
2869
}
2870
} else {
2871
// First one does it.
2872
break;
2873
}
2874
}
2875
}
2876
2877
if (key_idx != -1) {
2878
String text = TTR("Time (s):") + " " + TS->format_number(rtos(Math::snapped(animation->track_get_key_time(track, key_idx), SECOND_DECIMAL))) + "\n";
2879
switch (animation->track_get_type(track)) {
2880
case Animation::TYPE_POSITION_3D: {
2881
Vector3 t = animation->track_get_key_value(track, key_idx);
2882
text += TTR("Position:") + " " + String(t) + "\n";
2883
} break;
2884
case Animation::TYPE_ROTATION_3D: {
2885
Quaternion t = animation->track_get_key_value(track, key_idx);
2886
text += TTR("Rotation:") + " " + String(t) + "\n";
2887
} break;
2888
case Animation::TYPE_SCALE_3D: {
2889
Vector3 t = animation->track_get_key_value(track, key_idx);
2890
text += TTR("Scale:") + " " + String(t) + "\n";
2891
} break;
2892
case Animation::TYPE_BLEND_SHAPE: {
2893
float t = animation->track_get_key_value(track, key_idx);
2894
text += TTR("Blend Shape:") + " " + itos(t) + "\n";
2895
} break;
2896
case Animation::TYPE_VALUE: {
2897
const Variant &v = animation->track_get_key_value(track, key_idx);
2898
text += TTR("Type:") + " " + Variant::get_type_name(v.get_type()) + "\n";
2899
Variant::Type valid_type = Variant::NIL;
2900
text += TTR("Value:") + " " + String(v);
2901
if (!_is_value_key_valid(v, valid_type)) {
2902
text += " " + vformat(TTR("(Invalid, expected type: %s)"), Variant::get_type_name(valid_type));
2903
}
2904
text += "\n" + TTR("Easing:") + " " + rtos(animation->track_get_key_transition(track, key_idx));
2905
2906
} break;
2907
case Animation::TYPE_METHOD: {
2908
Dictionary d = animation->track_get_key_value(track, key_idx);
2909
if (d.has("method")) {
2910
text += String(d["method"]);
2911
}
2912
text += "(";
2913
Vector<Variant> args;
2914
if (d.has("args")) {
2915
args = d["args"];
2916
}
2917
for (int i = 0; i < args.size(); i++) {
2918
if (i > 0) {
2919
text += ", ";
2920
}
2921
text += args[i].get_construct_string();
2922
}
2923
text += ")\n";
2924
2925
} break;
2926
case Animation::TYPE_BEZIER: {
2927
float h = animation->bezier_track_get_key_value(track, key_idx);
2928
text += TTR("Value:") + " " + rtos(h) + "\n";
2929
Vector2 ih = animation->bezier_track_get_key_in_handle(track, key_idx);
2930
text += TTR("In-Handle:") + " " + String(ih) + "\n";
2931
Vector2 oh = animation->bezier_track_get_key_out_handle(track, key_idx);
2932
text += TTR("Out-Handle:") + " " + String(oh) + "\n";
2933
int hm = animation->bezier_track_get_key_handle_mode(track, key_idx);
2934
switch (hm) {
2935
case Animation::HANDLE_MODE_FREE: {
2936
text += TTR("Handle mode: Free\n");
2937
} break;
2938
case Animation::HANDLE_MODE_LINEAR: {
2939
text += TTR("Handle mode: Linear\n");
2940
} break;
2941
case Animation::HANDLE_MODE_BALANCED: {
2942
text += TTR("Handle mode: Balanced\n");
2943
} break;
2944
case Animation::HANDLE_MODE_MIRRORED: {
2945
text += TTR("Handle mode: Mirrored\n");
2946
} break;
2947
}
2948
} break;
2949
case Animation::TYPE_AUDIO: {
2950
String stream_name = "null";
2951
Ref<Resource> stream = animation->audio_track_get_key_stream(track, key_idx);
2952
if (stream.is_valid()) {
2953
if (stream->get_path().is_resource_file()) {
2954
stream_name = stream->get_path().get_file();
2955
} else if (!stream->get_name().is_empty()) {
2956
stream_name = stream->get_name();
2957
} else {
2958
stream_name = stream->get_class();
2959
}
2960
}
2961
2962
text += TTR("Stream:") + " " + stream_name + "\n";
2963
float so = animation->audio_track_get_key_start_offset(track, key_idx);
2964
text += TTR("Start (s):") + " " + rtos(so) + "\n";
2965
float eo = animation->audio_track_get_key_end_offset(track, key_idx);
2966
text += TTR("End (s):") + " " + rtos(eo) + "\n";
2967
} break;
2968
case Animation::TYPE_ANIMATION: {
2969
String name = animation->animation_track_get_key_animation(track, key_idx);
2970
text += TTR("Animation Clip:") + " " + name + "\n";
2971
} break;
2972
}
2973
return text;
2974
}
2975
}
2976
2977
return Control::get_tooltip(p_pos);
2978
}
2979
2980
void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
2981
ERR_FAIL_COND(p_event.is_null());
2982
2983
if (p_event->is_pressed()) {
2984
if (ED_IS_SHORTCUT("animation_editor/duplicate_selected_keys", p_event)) {
2985
if (!read_only) {
2986
emit_signal(SNAME("duplicate_request"), -1.0, false);
2987
}
2988
accept_event();
2989
}
2990
if (ED_IS_SHORTCUT("animation_editor/cut_selected_keys", p_event)) {
2991
if (!read_only) {
2992
emit_signal(SNAME("cut_request"));
2993
}
2994
accept_event();
2995
}
2996
if (ED_IS_SHORTCUT("animation_editor/copy_selected_keys", p_event)) {
2997
if (!read_only) {
2998
emit_signal(SNAME("copy_request"));
2999
}
3000
accept_event();
3001
}
3002
3003
if (ED_IS_SHORTCUT("animation_editor/paste_keys", p_event)) {
3004
if (!read_only) {
3005
emit_signal(SNAME("paste_request"), -1.0, false);
3006
}
3007
accept_event();
3008
}
3009
3010
if (ED_IS_SHORTCUT("animation_editor/delete_selection", p_event)) {
3011
if (!read_only) {
3012
emit_signal(SNAME("delete_request"));
3013
}
3014
accept_event();
3015
}
3016
}
3017
3018
Ref<InputEventMouseButton> mb = p_event;
3019
if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
3020
Point2 pos = mb->get_position();
3021
bool no_mod_key_pressed = !mb->is_alt_pressed() && !mb->is_shift_pressed() && !mb->is_command_or_control_pressed();
3022
if (mb->is_double_click() && !moving_selection && no_mod_key_pressed) {
3023
int x = pos.x - timeline->get_name_limit();
3024
float ofs = x / timeline->get_zoom_scale() + timeline->get_value();
3025
emit_signal(SNAME("timeline_changed"), ofs, false);
3026
}
3027
3028
if (!read_only) {
3029
if (check_rect.has_point(pos)) {
3030
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
3031
undo_redo->create_action(TTR("Toggle Track Enabled"));
3032
undo_redo->add_do_method(animation.ptr(), "track_set_enabled", track, !animation->track_is_enabled(track));
3033
undo_redo->add_undo_method(animation.ptr(), "track_set_enabled", track, animation->track_is_enabled(track));
3034
undo_redo->commit_action();
3035
queue_redraw();
3036
accept_event();
3037
}
3038
3039
if (icon_rect.has_point(pos)) {
3040
EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection();
3041
editor_selection->clear();
3042
Node *n = root->get_node_or_null(node_path);
3043
if (n) {
3044
editor_selection->add_node(n);
3045
}
3046
}
3047
3048
// Don't overlap track keys if they start at 0.
3049
if (path_rect.has_point(pos + Size2(type_icon->get_width(), 0))) {
3050
clicking_on_name = true;
3051
accept_event();
3052
}
3053
3054
if (update_mode_rect.has_point(pos)) {
3055
if (!menu) {
3056
menu = memnew(PopupMenu);
3057
add_child(menu);
3058
menu->connect(SceneStringName(id_pressed), callable_mp(this, &AnimationTrackEdit::_menu_selected));
3059
}
3060
menu->clear();
3061
if (animation->track_get_type(track) == Animation::TYPE_AUDIO) {
3062
menu->add_icon_item(get_editor_theme_icon(SNAME("UseBlendEnable")), TTR("Use Blend"), MENU_USE_BLEND_ENABLED);
3063
menu->add_icon_item(get_editor_theme_icon(SNAME("UseBlendDisable")), TTR("Don't Use Blend"), MENU_USE_BLEND_DISABLED);
3064
} else {
3065
menu->add_icon_item(get_editor_theme_icon(SNAME("TrackContinuous")), TTR("Continuous"), MENU_CALL_MODE_CONTINUOUS);
3066
menu->add_icon_item(get_editor_theme_icon(SNAME("TrackDiscrete")), TTR("Discrete"), MENU_CALL_MODE_DISCRETE);
3067
menu->add_icon_item(get_editor_theme_icon(SNAME("TrackCapture")), TTR("Capture"), MENU_CALL_MODE_CAPTURE);
3068
}
3069
menu->reset_size();
3070
3071
moving_selection_attempt = false;
3072
moving_selection = false;
3073
3074
Vector2 popup_pos = get_screen_position() + update_mode_rect.position + Vector2(0, update_mode_rect.size.height);
3075
menu->set_position(popup_pos);
3076
menu->popup();
3077
accept_event();
3078
}
3079
3080
if (interp_mode_rect.has_point(pos)) {
3081
if (!menu) {
3082
menu = memnew(PopupMenu);
3083
add_child(menu);
3084
menu->connect(SceneStringName(id_pressed), callable_mp(this, &AnimationTrackEdit::_menu_selected));
3085
}
3086
menu->clear();
3087
menu->add_icon_item(get_editor_theme_icon(SNAME("InterpRaw")), TTR("Nearest"), MENU_INTERPOLATION_NEAREST);
3088
menu->add_icon_item(get_editor_theme_icon(SNAME("InterpLinear")), TTR("Linear"), MENU_INTERPOLATION_LINEAR);
3089
menu->add_icon_item(get_editor_theme_icon(SNAME("InterpCubic")), TTR("Cubic"), MENU_INTERPOLATION_CUBIC);
3090
// Check whether it is angle property.
3091
AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
3092
if (ape) {
3093
AnimationPlayer *ap = ape->get_player();
3094
if (ap) {
3095
NodePath npath = animation->track_get_path(track);
3096
Node *a_ap_root_node = ap->get_node_or_null(ap->get_root_node());
3097
Node *nd = nullptr;
3098
// We must test that we have a valid a_ap_root_node before trying to access its content to init the nd Node.
3099
if (a_ap_root_node) {
3100
nd = a_ap_root_node->get_node_or_null(NodePath(npath.get_concatenated_names()));
3101
}
3102
if (nd) {
3103
StringName prop = npath.get_concatenated_subnames();
3104
PropertyInfo prop_info;
3105
ClassDB::get_property_info(nd->get_class(), prop, &prop_info);
3106
#ifdef DISABLE_DEPRECATED
3107
bool is_angle = prop_info.type == Variant::FLOAT && prop_info.hint_string.contains("radians_as_degrees");
3108
#else
3109
bool is_angle = prop_info.type == Variant::FLOAT && prop_info.hint_string.contains("radians");
3110
#endif // DISABLE_DEPRECATED
3111
if (is_angle) {
3112
menu->add_icon_item(get_editor_theme_icon(SNAME("InterpLinearAngle")), TTR("Linear Angle"), MENU_INTERPOLATION_LINEAR_ANGLE);
3113
menu->add_icon_item(get_editor_theme_icon(SNAME("InterpCubicAngle")), TTR("Cubic Angle"), MENU_INTERPOLATION_CUBIC_ANGLE);
3114
}
3115
}
3116
}
3117
}
3118
menu->reset_size();
3119
3120
moving_selection_attempt = false;
3121
moving_selection = false;
3122
3123
Vector2 popup_pos = get_screen_position() + interp_mode_rect.position + Vector2(0, interp_mode_rect.size.height);
3124
menu->set_position(popup_pos);
3125
menu->popup();
3126
accept_event();
3127
}
3128
3129
if (loop_wrap_rect.has_point(pos)) {
3130
if (!menu) {
3131
menu = memnew(PopupMenu);
3132
add_child(menu);
3133
menu->connect(SceneStringName(id_pressed), callable_mp(this, &AnimationTrackEdit::_menu_selected));
3134
}
3135
menu->clear();
3136
menu->add_icon_item(get_editor_theme_icon(SNAME("InterpWrapClamp")), TTR("Clamp Loop Interp"), MENU_LOOP_CLAMP);
3137
menu->add_icon_item(get_editor_theme_icon(SNAME("InterpWrapLoop")), TTR("Wrap Loop Interp"), MENU_LOOP_WRAP);
3138
menu->reset_size();
3139
3140
moving_selection_attempt = false;
3141
moving_selection = false;
3142
3143
Vector2 popup_pos = get_screen_position() + loop_wrap_rect.position + Vector2(0, loop_wrap_rect.size.height);
3144
menu->set_position(popup_pos);
3145
menu->popup();
3146
accept_event();
3147
}
3148
3149
if (remove_rect.has_point(pos)) {
3150
emit_signal(SNAME("remove_request"), track);
3151
accept_event();
3152
return;
3153
}
3154
}
3155
3156
if (mb->is_command_or_control_pressed() && _lookup_key(hovering_key_idx)) {
3157
accept_event();
3158
return;
3159
}
3160
3161
if (_try_select_at_ui_pos(pos, mb->is_command_or_control_pressed() || mb->is_shift_pressed(), true)) {
3162
accept_event();
3163
}
3164
}
3165
3166
if (!moving_selection && mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) {
3167
Point2 pos = mb->get_position();
3168
if (pos.x >= timeline->get_name_limit() && pos.x <= get_size().width - timeline->get_buttons_width()) {
3169
// Can do something with menu too! show insert key.
3170
float offset = (pos.x - timeline->get_name_limit()) / timeline->get_zoom_scale();
3171
if (!read_only) {
3172
if (!menu) {
3173
menu = memnew(PopupMenu);
3174
add_child(menu);
3175
menu->connect(SceneStringName(id_pressed), callable_mp(this, &AnimationTrackEdit::_menu_selected));
3176
}
3177
3178
bool selected = _try_select_at_ui_pos(pos, mb->is_command_or_control_pressed() || mb->is_shift_pressed(), false);
3179
3180
menu->clear();
3181
if (animation->track_get_type(track) == Animation::TYPE_METHOD) {
3182
if (hovering_key_idx != -1) {
3183
lookup_key_idx = hovering_key_idx;
3184
menu->add_icon_item(get_editor_theme_icon(SNAME("Help")), vformat("%s (%s)", TTR("Go to Definition"), animation->method_track_get_name(track, lookup_key_idx)), MENU_KEY_LOOKUP);
3185
menu->add_separator();
3186
}
3187
}
3188
menu->add_icon_item(get_editor_theme_icon(SNAME("Key")), TTR("Insert Key..."), MENU_KEY_INSERT);
3189
if (selected || editor->is_selection_active()) {
3190
menu->add_separator();
3191
menu->add_icon_item(get_editor_theme_icon(SNAME("Duplicate")), TTR("Duplicate Key(s)"), MENU_KEY_DUPLICATE);
3192
menu->add_icon_item(get_editor_theme_icon(SNAME("ActionCut")), TTR("Cut Key(s)"), MENU_KEY_CUT);
3193
menu->add_icon_item(get_editor_theme_icon(SNAME("ActionCopy")), TTR("Copy Key(s)"), MENU_KEY_COPY);
3194
}
3195
if (editor->is_key_clipboard_active()) {
3196
menu->add_icon_item(get_editor_theme_icon(SNAME("ActionPaste")), TTR("Paste Key(s)"), MENU_KEY_PASTE);
3197
}
3198
if (selected || editor->is_selection_active()) {
3199
AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();
3200
if ((!player->has_animation(SceneStringName(RESET)) || animation != player->get_animation(SceneStringName(RESET))) && editor->can_add_reset_key()) {
3201
menu->add_icon_item(get_editor_theme_icon(SNAME("Reload")), TTR("Add RESET Value(s)"), MENU_KEY_ADD_RESET);
3202
}
3203
3204
menu->add_separator();
3205
menu->add_icon_item(get_editor_theme_icon(SNAME("Remove")), TTR("Delete Key(s)"), MENU_KEY_DELETE);
3206
}
3207
menu->reset_size();
3208
3209
moving_selection_attempt = false;
3210
moving_selection = false;
3211
3212
menu->set_position(get_screen_position() + get_local_mouse_position());
3213
menu->popup();
3214
3215
insert_at_pos = offset + timeline->get_value();
3216
accept_event();
3217
}
3218
}
3219
}
3220
3221
if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT && clicking_on_name) {
3222
if (!path) {
3223
path_popup = memnew(Popup);
3224
path_popup->set_wrap_controls(true);
3225
add_child(path_popup);
3226
path = memnew(LineEdit);
3227
path_popup->add_child(path);
3228
path->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
3229
path->connect(SceneStringName(text_submitted), callable_mp(this, &AnimationTrackEdit::_path_submitted));
3230
}
3231
3232
path->set_text(String(animation->track_get_path(track)));
3233
const Vector2 theme_ofs = path->get_theme_stylebox(CoreStringName(normal), SNAME("LineEdit"))->get_offset();
3234
3235
moving_selection_attempt = false;
3236
moving_selection = false;
3237
3238
path_popup->set_position(get_screen_position() + path_rect.position - theme_ofs);
3239
path_popup->set_size(path_rect.size);
3240
path_popup->popup();
3241
path->grab_focus();
3242
path->set_caret_column(path->get_text().length());
3243
clicking_on_name = false;
3244
}
3245
3246
if (mb.is_valid() && moving_selection_attempt) {
3247
if (!mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
3248
moving_selection_attempt = false;
3249
if (moving_selection && moving_selection_effective) {
3250
if (std::abs(editor->get_moving_selection_offset()) > CMP_EPSILON) {
3251
emit_signal(SNAME("move_selection_commit"));
3252
}
3253
} else if (select_single_attempt != -1) {
3254
emit_signal(SNAME("select_key"), select_single_attempt, true);
3255
}
3256
moving_selection = false;
3257
select_single_attempt = -1;
3258
}
3259
3260
if (moving_selection && mb->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) {
3261
moving_selection_attempt = false;
3262
moving_selection = false;
3263
emit_signal(SNAME("move_selection_cancel"));
3264
}
3265
}
3266
3267
Ref<InputEventMouseMotion> mm = p_event;
3268
if (mm.is_valid()) {
3269
const int previous_hovering_key_idx = hovering_key_idx;
3270
3271
command_or_control_pressed = mm->is_command_or_control_pressed();
3272
3273
// Hovering compressed keyframes for editing is not possible.
3274
if (!animation->track_is_compressed(track)) {
3275
const float scale = timeline->get_zoom_scale();
3276
const int limit = timeline->get_name_limit();
3277
const int limit_end = get_size().width - timeline->get_buttons_width();
3278
// Left Border including space occupied by keyframes on t=0.
3279
const int limit_start_hitbox = limit - type_icon->get_width();
3280
const Point2 pos = mm->get_position();
3281
3282
if (pos.x >= limit_start_hitbox && pos.x <= limit_end) {
3283
// Use the same logic as key selection to ensure that hovering accurately represents
3284
// which key will be selected when clicking.
3285
int key_idx = -1;
3286
float key_distance = 1e20;
3287
3288
hovering_key_idx = -1;
3289
3290
// Hovering should happen in the opposite order of drawing for more accurate overlap hovering.
3291
for (int i = animation->track_get_key_count(track) - 1; i >= 0; i--) {
3292
Rect2 rect = get_key_rect(i, scale);
3293
float offset = animation->track_get_key_time(track, i) - timeline->get_value();
3294
offset = offset * scale + limit;
3295
rect.position.x += offset;
3296
3297
if (rect.has_point(pos)) {
3298
if (is_key_selectable_by_distance()) {
3299
const float distance = Math::abs(offset - pos.x);
3300
if (key_idx == -1 || distance < key_distance) {
3301
key_idx = i;
3302
key_distance = distance;
3303
hovering_key_idx = i;
3304
}
3305
} else {
3306
// First one does it.
3307
hovering_key_idx = i;
3308
break;
3309
}
3310
}
3311
}
3312
3313
if (hovering_key_idx != previous_hovering_key_idx) {
3314
// Required to draw keyframe hover feedback on the correct keyframe.
3315
queue_redraw();
3316
}
3317
}
3318
}
3319
}
3320
3321
if (mm.is_valid() && mm->get_button_mask().has_flag(MouseButtonMask::LEFT) && moving_selection_attempt) {
3322
if (!moving_selection) {
3323
moving_selection = true;
3324
emit_signal(SNAME("move_selection_begin"));
3325
}
3326
3327
float moving_begin_time = ((moving_selection_mouse_begin_x - timeline->get_name_limit()) / timeline->get_zoom_scale()) + timeline->get_value();
3328
float new_time = ((mm->get_position().x - timeline->get_name_limit()) / timeline->get_zoom_scale()) + timeline->get_value();
3329
float delta = new_time - moving_begin_time;
3330
float snapped_time = editor->snap_time(moving_selection_pivot + delta);
3331
3332
float offset = 0.0;
3333
if (std::abs(editor->get_moving_selection_offset()) > CMP_EPSILON || (snapped_time > moving_selection_pivot && delta > CMP_EPSILON) || (snapped_time < moving_selection_pivot && delta < -CMP_EPSILON)) {
3334
offset = snapped_time - moving_selection_pivot;
3335
moving_selection_effective = true;
3336
}
3337
3338
emit_signal(SNAME("move_selection"), offset);
3339
}
3340
}
3341
3342
bool AnimationTrackEdit::_try_select_at_ui_pos(const Point2 &p_pos, bool p_aggregate, bool p_deselectable) {
3343
if (!animation->track_is_compressed(track)) { // Selecting compressed keyframes for editing is not possible.
3344
float scale = timeline->get_zoom_scale();
3345
int limit = timeline->get_name_limit();
3346
int limit_end = get_size().width - timeline->get_buttons_width();
3347
// Left Border including space occupied by keyframes on t=0.
3348
int limit_start_hitbox = limit - type_icon->get_width();
3349
3350
if (p_pos.x >= limit_start_hitbox && p_pos.x <= limit_end) {
3351
int key_idx = -1;
3352
float key_distance = 1e20;
3353
3354
// Select should happen in the opposite order of drawing for more accurate overlap select.
3355
for (int i = animation->track_get_key_count(track) - 1; i >= 0; i--) {
3356
Rect2 rect = get_key_rect(i, scale);
3357
float offset = animation->track_get_key_time(track, i) - timeline->get_value();
3358
offset = offset * scale + limit;
3359
rect.position.x += offset;
3360
3361
if (rect.has_point(p_pos)) {
3362
if (is_key_selectable_by_distance()) {
3363
float distance = Math::abs(offset - p_pos.x);
3364
if (key_idx == -1 || distance < key_distance) {
3365
key_idx = i;
3366
key_distance = distance;
3367
}
3368
} else {
3369
// First one does it.
3370
key_idx = i;
3371
break;
3372
}
3373
}
3374
}
3375
3376
if (key_idx != -1) {
3377
if (p_aggregate) {
3378
if (editor->is_key_selected(track, key_idx)) {
3379
if (p_deselectable) {
3380
emit_signal(SNAME("deselect_key"), key_idx);
3381
moving_selection_pivot = 0.0f;
3382
moving_selection_mouse_begin_x = 0.0f;
3383
}
3384
} else {
3385
emit_signal(SNAME("select_key"), key_idx, false);
3386
moving_selection_attempt = true;
3387
moving_selection_effective = false;
3388
select_single_attempt = -1;
3389
moving_selection_pivot = animation->track_get_key_time(track, key_idx);
3390
moving_selection_mouse_begin_x = p_pos.x;
3391
}
3392
} else {
3393
if (!editor->is_key_selected(track, key_idx)) {
3394
emit_signal(SNAME("select_key"), key_idx, true);
3395
select_single_attempt = -1;
3396
} else {
3397
select_single_attempt = key_idx;
3398
}
3399
3400
moving_selection_attempt = true;
3401
moving_selection_effective = false;
3402
moving_selection_pivot = animation->track_get_key_time(track, key_idx);
3403
moving_selection_mouse_begin_x = p_pos.x;
3404
}
3405
3406
if (read_only) {
3407
moving_selection_attempt = false;
3408
moving_selection_pivot = 0.0f;
3409
moving_selection_mouse_begin_x = 0.0f;
3410
}
3411
return true;
3412
}
3413
}
3414
}
3415
return false;
3416
}
3417
3418
bool AnimationTrackEdit::_lookup_key(int p_key_idx) const {
3419
if (p_key_idx < 0 || p_key_idx >= animation->track_get_key_count(track)) {
3420
return false;
3421
}
3422
3423
if (animation->track_get_type(track) == Animation::TYPE_METHOD) {
3424
Node *target = root->get_node_or_null(animation->track_get_path(track));
3425
if (target) {
3426
StringName method = animation->method_track_get_name(track, p_key_idx);
3427
// First, check every script in the inheritance chain.
3428
bool found_in_script = false;
3429
Ref<Script> target_script_ref = target->get_script();
3430
Script *target_script = target_script_ref.ptr();
3431
while (target_script) {
3432
if (target_script->has_method(method)) {
3433
found_in_script = true;
3434
// Tell ScriptEditor to show the method's line.
3435
ScriptEditor::get_singleton()->script_goto_method(target_script, animation->method_track_get_name(track, p_key_idx));
3436
break;
3437
}
3438
target_script = target_script->get_base_script().ptr();
3439
}
3440
3441
if (!found_in_script) {
3442
// Not found in script, so it must be a native method.
3443
if (ClassDB::has_method(target->get_class_name(), method)) {
3444
// Show help page instead.
3445
ScriptEditor::get_singleton()->goto_help(vformat("class_method:%s:%s", target->get_class_name(), method));
3446
} else {
3447
// Still not found, which means the target doesn't have this method. Warn the user.
3448
WARN_PRINT_ED(TTR(vformat("Failed to lookup method: \"%s\"", method)));
3449
}
3450
}
3451
return true;
3452
}
3453
}
3454
return false;
3455
}
3456
3457
Variant AnimationTrackEdit::get_drag_data(const Point2 &p_point) {
3458
if (!clicking_on_name || (get_editor()->is_sorting_alphabetically() && !get_editor()->is_grouping_tracks())) {
3459
return Variant();
3460
}
3461
3462
Dictionary drag_data;
3463
drag_data["type"] = "animation_track";
3464
String base_path = String(animation->track_get_path(track));
3465
base_path = base_path.get_slicec(':', 0); // Remove sub-path.
3466
drag_data["group"] = base_path;
3467
drag_data["index"] = track;
3468
3469
Button *tb = memnew(Button);
3470
tb->set_flat(true);
3471
tb->set_text(path_cache);
3472
tb->set_button_icon(icon_cache);
3473
tb->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
3474
tb->add_theme_constant_override("icon_max_width", get_theme_constant("class_icon_size", EditorStringName(Editor)));
3475
set_drag_preview(tb);
3476
3477
clicking_on_name = false;
3478
3479
return drag_data;
3480
}
3481
3482
bool AnimationTrackEdit::can_drop_data(const Point2 &p_point, const Variant &p_data) const {
3483
Dictionary d = p_data;
3484
if (!d.has("type")) {
3485
return false;
3486
}
3487
3488
String type = d["type"];
3489
if (type != "animation_track") {
3490
return false;
3491
}
3492
3493
// Don't allow moving tracks outside their groups.
3494
if (get_editor()->is_grouping_tracks()) {
3495
String base_path = String(animation->track_get_path(track));
3496
base_path = base_path.get_slicec(':', 0); // Remove sub-path.
3497
if (d["group"] != base_path) {
3498
return false;
3499
}
3500
}
3501
3502
if (p_point.y < get_size().height / 2) {
3503
dropping_at = -1;
3504
} else {
3505
dropping_at = 1;
3506
}
3507
3508
const_cast<AnimationTrackEdit *>(this)->queue_redraw();
3509
3510
return true;
3511
}
3512
3513
void AnimationTrackEdit::drop_data(const Point2 &p_point, const Variant &p_data) {
3514
Dictionary d = p_data;
3515
if (!d.has("type")) {
3516
return;
3517
}
3518
3519
String type = d["type"];
3520
if (type != "animation_track") {
3521
return;
3522
}
3523
3524
// Don't allow moving tracks outside their groups.
3525
if (get_editor()->is_grouping_tracks()) {
3526
String base_path = String(animation->track_get_path(track));
3527
base_path = base_path.get_slicec(':', 0); // Remove sub-path.
3528
if (d["group"] != base_path) {
3529
return;
3530
}
3531
}
3532
3533
int from_track = d["index"];
3534
3535
if (dropping_at < 0) {
3536
emit_signal(SNAME("dropped"), from_track, track);
3537
} else {
3538
emit_signal(SNAME("dropped"), from_track, track + 1);
3539
}
3540
}
3541
3542
void AnimationTrackEdit::_menu_selected(int p_index) {
3543
switch (p_index) {
3544
case MENU_CALL_MODE_CONTINUOUS:
3545
case MENU_CALL_MODE_DISCRETE:
3546
case MENU_CALL_MODE_CAPTURE: {
3547
Animation::UpdateMode update_mode = Animation::UpdateMode(p_index);
3548
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
3549
undo_redo->create_action(TTR("Change Animation Update Mode"));
3550
undo_redo->add_do_method(animation.ptr(), "value_track_set_update_mode", track, update_mode);
3551
undo_redo->add_undo_method(animation.ptr(), "value_track_set_update_mode", track, animation->value_track_get_update_mode(track));
3552
AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
3553
if (ape) {
3554
undo_redo->add_do_method(ape, "_animation_update_key_frame");
3555
undo_redo->add_undo_method(ape, "_animation_update_key_frame");
3556
}
3557
undo_redo->commit_action();
3558
queue_redraw();
3559
3560
} break;
3561
case MENU_INTERPOLATION_NEAREST:
3562
case MENU_INTERPOLATION_LINEAR:
3563
case MENU_INTERPOLATION_CUBIC:
3564
case MENU_INTERPOLATION_LINEAR_ANGLE:
3565
case MENU_INTERPOLATION_CUBIC_ANGLE: {
3566
Animation::InterpolationType interp_mode = Animation::InterpolationType(p_index - MENU_INTERPOLATION_NEAREST);
3567
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
3568
undo_redo->create_action(TTR("Change Animation Interpolation Mode"));
3569
undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_type", track, interp_mode);
3570
undo_redo->add_undo_method(animation.ptr(), "track_set_interpolation_type", track, animation->track_get_interpolation_type(track));
3571
AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
3572
if (ape) {
3573
undo_redo->add_do_method(ape, "_animation_update_key_frame");
3574
undo_redo->add_undo_method(ape, "_animation_update_key_frame");
3575
}
3576
undo_redo->commit_action();
3577
queue_redraw();
3578
} break;
3579
case MENU_LOOP_WRAP:
3580
case MENU_LOOP_CLAMP: {
3581
bool loop_wrap = p_index == MENU_LOOP_WRAP;
3582
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
3583
undo_redo->create_action(TTR("Change Animation Loop Mode"));
3584
undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_loop_wrap", track, loop_wrap);
3585
undo_redo->add_undo_method(animation.ptr(), "track_set_interpolation_loop_wrap", track, animation->track_get_interpolation_loop_wrap(track));
3586
AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
3587
if (ape) {
3588
undo_redo->add_do_method(ape, "_animation_update_key_frame");
3589
undo_redo->add_undo_method(ape, "_animation_update_key_frame");
3590
}
3591
undo_redo->commit_action();
3592
queue_redraw();
3593
3594
} break;
3595
case MENU_KEY_INSERT: {
3596
emit_signal(SNAME("insert_key"), insert_at_pos);
3597
} break;
3598
case MENU_KEY_DUPLICATE: {
3599
emit_signal(SNAME("duplicate_request"), insert_at_pos, true);
3600
} break;
3601
case MENU_KEY_CUT: {
3602
emit_signal(SNAME("cut_request"));
3603
} break;
3604
case MENU_KEY_COPY: {
3605
emit_signal(SNAME("copy_request"));
3606
} break;
3607
case MENU_KEY_PASTE: {
3608
emit_signal(SNAME("paste_request"), insert_at_pos, true);
3609
} break;
3610
case MENU_KEY_ADD_RESET: {
3611
emit_signal(SNAME("create_reset_request"));
3612
3613
} break;
3614
case MENU_KEY_DELETE: {
3615
emit_signal(SNAME("delete_request"));
3616
3617
} break;
3618
case MENU_KEY_LOOKUP: {
3619
_lookup_key(lookup_key_idx);
3620
} break;
3621
case MENU_USE_BLEND_ENABLED:
3622
case MENU_USE_BLEND_DISABLED: {
3623
bool use_blend = p_index == MENU_USE_BLEND_ENABLED;
3624
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
3625
undo_redo->create_action(TTR("Change Animation Use Blend"));
3626
undo_redo->add_do_method(animation.ptr(), "audio_track_set_use_blend", track, use_blend);
3627
undo_redo->add_undo_method(animation.ptr(), "audio_track_set_use_blend", track, animation->audio_track_is_use_blend(track));
3628
undo_redo->commit_action();
3629
queue_redraw();
3630
} break;
3631
}
3632
}
3633
3634
void AnimationTrackEdit::cancel_drop() {
3635
if (dropping_at != 0) {
3636
dropping_at = 0;
3637
queue_redraw();
3638
}
3639
}
3640
3641
void AnimationTrackEdit::set_in_group(bool p_enable) {
3642
in_group = p_enable;
3643
queue_redraw();
3644
}
3645
3646
void AnimationTrackEdit::append_to_selection(const Rect2 &p_box, bool p_deselection) {
3647
if (animation->track_is_compressed(track)) {
3648
return; // Compressed keyframes can't be edited
3649
}
3650
// Left Border including space occupied by keyframes on t=0.
3651
int limit_start_hitbox = timeline->get_name_limit() - type_icon->get_width();
3652
Rect2 select_rect(limit_start_hitbox, 0, get_size().width - timeline->get_name_limit() - timeline->get_buttons_width(), get_size().height);
3653
select_rect = select_rect.intersection(p_box);
3654
3655
// Select should happen in the opposite order of drawing for more accurate overlap select.
3656
for (int i = animation->track_get_key_count(track) - 1; i >= 0; i--) {
3657
Rect2 rect = const_cast<AnimationTrackEdit *>(this)->get_key_rect(i, timeline->get_zoom_scale());
3658
float offset = animation->track_get_key_time(track, i) - timeline->get_value();
3659
offset = offset * timeline->get_zoom_scale() + timeline->get_name_limit();
3660
rect.position.x += offset;
3661
3662
if (select_rect.intersects(rect)) {
3663
if (p_deselection) {
3664
emit_signal(SNAME("deselect_key"), i);
3665
} else {
3666
emit_signal(SNAME("select_key"), i, false);
3667
}
3668
}
3669
}
3670
}
3671
3672
void AnimationTrackEdit::_bind_methods() {
3673
ADD_SIGNAL(MethodInfo("timeline_changed", PropertyInfo(Variant::FLOAT, "position"), PropertyInfo(Variant::BOOL, "timeline_only")));
3674
ADD_SIGNAL(MethodInfo("remove_request", PropertyInfo(Variant::INT, "track")));
3675
ADD_SIGNAL(MethodInfo("dropped", PropertyInfo(Variant::INT, "from_track"), PropertyInfo(Variant::INT, "to_track")));
3676
ADD_SIGNAL(MethodInfo("insert_key", PropertyInfo(Variant::FLOAT, "offset")));
3677
ADD_SIGNAL(MethodInfo("select_key", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::BOOL, "single")));
3678
ADD_SIGNAL(MethodInfo("deselect_key", PropertyInfo(Variant::INT, "index")));
3679
3680
ADD_SIGNAL(MethodInfo("move_selection_begin"));
3681
ADD_SIGNAL(MethodInfo("move_selection", PropertyInfo(Variant::FLOAT, "offset")));
3682
ADD_SIGNAL(MethodInfo("move_selection_commit"));
3683
ADD_SIGNAL(MethodInfo("move_selection_cancel"));
3684
3685
ADD_SIGNAL(MethodInfo("duplicate_request", PropertyInfo(Variant::FLOAT, "offset"), PropertyInfo(Variant::BOOL, "is_offset_valid")));
3686
ADD_SIGNAL(MethodInfo("create_reset_request"));
3687
ADD_SIGNAL(MethodInfo("copy_request"));
3688
ADD_SIGNAL(MethodInfo("cut_request"));
3689
ADD_SIGNAL(MethodInfo("paste_request", PropertyInfo(Variant::FLOAT, "offset"), PropertyInfo(Variant::BOOL, "is_offset_valid")));
3690
ADD_SIGNAL(MethodInfo("delete_request"));
3691
}
3692
3693
AnimationTrackEdit::AnimationTrackEdit() {
3694
play_position = memnew(Control);
3695
play_position->set_mouse_filter(MOUSE_FILTER_PASS);
3696
add_child(play_position);
3697
play_position->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
3698
play_position->connect(SceneStringName(draw), callable_mp(this, &AnimationTrackEdit::_play_position_draw));
3699
set_focus_mode(FOCUS_CLICK);
3700
set_mouse_filter(MOUSE_FILTER_PASS); // Scroll has to work too for selection.
3701
}
3702
3703
//////////////////////////////////////
3704
3705
AnimationTrackEdit *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) {
3706
if (get_script_instance()) {
3707
return Object::cast_to<AnimationTrackEdit>(get_script_instance()->call("create_value_track_edit", p_object, p_type, p_property, p_hint, p_hint_string, p_usage));
3708
}
3709
return nullptr;
3710
}
3711
3712
AnimationTrackEdit *AnimationTrackEditPlugin::create_audio_track_edit() {
3713
if (get_script_instance()) {
3714
return Object::cast_to<AnimationTrackEdit>(get_script_instance()->call("create_audio_track_edit").operator Object *());
3715
}
3716
return nullptr;
3717
}
3718
3719
AnimationTrackEdit *AnimationTrackEditPlugin::create_animation_track_edit(Object *p_object) {
3720
if (get_script_instance()) {
3721
return Object::cast_to<AnimationTrackEdit>(get_script_instance()->call("create_animation_track_edit", p_object).operator Object *());
3722
}
3723
return nullptr;
3724
}
3725
3726
///////////////////////////////////////
3727
3728
void AnimationTrackEditGroup::_notification(int p_what) {
3729
switch (p_what) {
3730
case NOTIFICATION_THEME_CHANGED: {
3731
icon_size = Vector2(1, 1) * get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor));
3732
} break;
3733
3734
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
3735
RID ae = get_accessibility_element();
3736
ERR_FAIL_COND(ae.is_null());
3737
3738
//TODO
3739
DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_STATIC_TEXT);
3740
DisplayServer::get_singleton()->accessibility_update_set_value(ae, TTR(vformat("The %s is not accessible at this time.", "Animation track group")));
3741
} break;
3742
3743
case NOTIFICATION_DRAW: {
3744
const Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label"));
3745
const int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label"));
3746
Color color = get_theme_color(SceneStringName(font_color), SNAME("Label"));
3747
3748
const Ref<StyleBox> &stylebox_header = get_theme_stylebox(SNAME("header"), SNAME("AnimationTrackEditGroup"));
3749
const int outer_margin = get_theme_constant(SNAME("outer_margin"), SNAME("AnimationTrackEdit"));
3750
3751
float v_margin_offset = stylebox_header->get_content_margin(SIDE_TOP) - stylebox_header->get_content_margin(SIDE_BOTTOM);
3752
3753
const Color h_line_color = get_theme_color(SNAME("h_line_color"), SNAME("AnimationTrackEditGroup"));
3754
const Color v_line_color = get_theme_color(SNAME("v_line_color"), SNAME("AnimationTrackEditGroup"));
3755
const int h_separation = get_theme_constant(SNAME("h_separation"), SNAME("AnimationTrackEditGroup"));
3756
3757
if (root) {
3758
Node *n = root->get_node_or_null(node);
3759
if (n && EditorNode::get_singleton()->get_editor_selection()->is_selected(n)) {
3760
color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
3761
}
3762
}
3763
3764
draw_style_box(stylebox_header, Rect2(Point2(), get_size()));
3765
3766
int limit = timeline->get_name_limit();
3767
3768
// Section preview.
3769
3770
{
3771
float scale = timeline->get_zoom_scale();
3772
int limit_end = get_size().width - timeline->get_buttons_width();
3773
3774
PackedStringArray section = editor->get_selected_section();
3775
if (section.size() == 2) {
3776
StringName start_marker = section[0];
3777
StringName end_marker = section[1];
3778
double start_time = editor->get_current_animation()->get_marker_time(start_marker);
3779
double end_time = editor->get_current_animation()->get_marker_time(end_marker);
3780
3781
// When AnimationPlayer is playing, don't move the preview rect, so it still indicates the playback section.
3782
AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();
3783
if (editor->is_marker_moving_selection() && !(player && player->is_playing())) {
3784
start_time += editor->get_marker_moving_selection_offset();
3785
end_time += editor->get_marker_moving_selection_offset();
3786
}
3787
3788
if (start_time < editor->get_current_animation()->get_length() && end_time >= 0) {
3789
float start_ofs = MAX(0, start_time) - timeline->get_value();
3790
float end_ofs = MIN(editor->get_current_animation()->get_length(), end_time) - timeline->get_value();
3791
start_ofs = start_ofs * scale + limit;
3792
end_ofs = end_ofs * scale + limit;
3793
start_ofs = MAX(start_ofs, limit);
3794
end_ofs = MIN(end_ofs, limit_end);
3795
Rect2 rect;
3796
rect.set_position(Vector2(start_ofs, 0));
3797
rect.set_size(Vector2(end_ofs - start_ofs, get_size().height));
3798
3799
draw_rect(rect, Color(1, 0.1, 0.1, 0.2));
3800
}
3801
}
3802
}
3803
3804
// Marker overlays.
3805
3806
{
3807
float scale = timeline->get_zoom_scale();
3808
PackedStringArray markers = editor->get_current_animation()->get_marker_names();
3809
for (const StringName marker : markers) {
3810
double time = editor->get_current_animation()->get_marker_time(marker);
3811
if (editor->is_marker_selected(marker) && editor->is_marker_moving_selection()) {
3812
time += editor->get_marker_moving_selection_offset();
3813
}
3814
if (time >= 0) {
3815
float offset = time - timeline->get_value();
3816
offset = offset * scale + limit;
3817
Color marker_color = editor->get_current_animation()->get_marker_color(marker);
3818
marker_color.a = 0.2;
3819
draw_line(Point2(offset, 0), Point2(offset, get_size().height), marker_color, Math::round(EDSCALE));
3820
}
3821
}
3822
}
3823
3824
draw_line(Point2(), Point2(get_size().width, 0), h_line_color, Math::round(EDSCALE));
3825
draw_line(Point2(timeline->get_name_limit(), 0), Point2(timeline->get_name_limit(), get_size().height), v_line_color, Math::round(EDSCALE));
3826
draw_line(Point2(get_size().width - timeline->get_buttons_width() - outer_margin, 0), Point2(get_size().width - timeline->get_buttons_width() - outer_margin, get_size().height), v_line_color, Math::round(EDSCALE));
3827
3828
int ofs = stylebox_header->get_margin(SIDE_LEFT);
3829
draw_texture_rect(icon, Rect2(Point2(ofs, (get_size().height - icon_size.y) / 2 + v_margin_offset).round(), icon_size));
3830
ofs += h_separation + icon_size.x;
3831
draw_string(font, Point2(ofs, (get_size().height - font->get_height(font_size)) / 2 + font->get_ascent(font_size) + v_margin_offset).round(), node_name, HORIZONTAL_ALIGNMENT_LEFT, timeline->get_name_limit() - ofs, font_size, color);
3832
3833
int px = (-timeline->get_value() + timeline->get_play_position()) * timeline->get_zoom_scale() + timeline->get_name_limit();
3834
3835
if (px >= timeline->get_name_limit() && px < (get_size().width - timeline->get_buttons_width())) {
3836
const Color accent = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
3837
draw_line(Point2(px, 0), Point2(px, get_size().height), accent, Math::round(2 * EDSCALE));
3838
}
3839
} break;
3840
}
3841
}
3842
3843
void AnimationTrackEditGroup::gui_input(const Ref<InputEvent> &p_event) {
3844
ERR_FAIL_COND(p_event.is_null());
3845
3846
Ref<InputEventMouseButton> mb = p_event;
3847
if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
3848
Point2 pos = mb->get_position();
3849
Rect2 node_name_rect = Rect2(0, 0, timeline->get_name_limit(), get_size().height);
3850
3851
if (node_name_rect.has_point(pos)) {
3852
EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection();
3853
editor_selection->clear();
3854
Node *n = root->get_node_or_null(node);
3855
if (n) {
3856
editor_selection->add_node(n);
3857
}
3858
}
3859
}
3860
}
3861
3862
void AnimationTrackEditGroup::set_type_and_name(const Ref<Texture2D> &p_type, const String &p_name, const NodePath &p_node) {
3863
icon = p_type;
3864
node_name = p_name;
3865
node = p_node;
3866
queue_redraw();
3867
update_minimum_size();
3868
}
3869
3870
Size2 AnimationTrackEditGroup::get_minimum_size() const {
3871
const Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label"));
3872
const int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label"));
3873
const int separation = get_theme_constant(SNAME("v_separation"), SNAME("ItemList"));
3874
3875
const Ref<StyleBox> &header_style = get_theme_stylebox(SNAME("header"), SNAME("AnimationTrackEditGroup"));
3876
const int content_margin = header_style->get_content_margin(SIDE_TOP) + header_style->get_content_margin(SIDE_BOTTOM);
3877
3878
return Vector2(0, MAX(font->get_height(font_size), icon_size.y) + separation + content_margin);
3879
}
3880
3881
String AnimationTrackEditGroup::get_node_name() const {
3882
return node_name;
3883
}
3884
3885
void AnimationTrackEditGroup::set_timeline(AnimationTimelineEdit *p_timeline) {
3886
timeline = p_timeline;
3887
timeline->connect("zoom_changed", callable_mp(this, &AnimationTrackEditGroup::_zoom_changed));
3888
timeline->connect("name_limit_changed", callable_mp(this, &AnimationTrackEditGroup::_zoom_changed));
3889
}
3890
3891
void AnimationTrackEditGroup::set_root(Node *p_root) {
3892
root = p_root;
3893
queue_redraw();
3894
}
3895
3896
void AnimationTrackEditGroup::set_editor(AnimationTrackEditor *p_editor) {
3897
editor = p_editor;
3898
}
3899
3900
void AnimationTrackEditGroup::_zoom_changed() {
3901
queue_redraw();
3902
}
3903
3904
AnimationTrackEditGroup::AnimationTrackEditGroup() {
3905
set_mouse_filter(MOUSE_FILTER_PASS);
3906
}
3907
3908
//////////////////////////////////////
3909
3910
void AnimationTrackEditor::add_track_edit_plugin(const Ref<AnimationTrackEditPlugin> &p_plugin) {
3911
if (track_edit_plugins.has(p_plugin)) {
3912
return;
3913
}
3914
track_edit_plugins.push_back(p_plugin);
3915
}
3916
3917
void AnimationTrackEditor::remove_track_edit_plugin(const Ref<AnimationTrackEditPlugin> &p_plugin) {
3918
track_edit_plugins.erase(p_plugin);
3919
}
3920
3921
void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim, bool p_read_only) {
3922
if (animation != p_anim && _get_track_selected() >= 0) {
3923
track_edits[_get_track_selected()]->release_focus();
3924
}
3925
if (animation.is_valid()) {
3926
animation->disconnect_changed(callable_mp(this, &AnimationTrackEditor::_animation_changed));
3927
_clear_selection();
3928
}
3929
animation = p_anim;
3930
read_only = p_read_only;
3931
timeline->set_animation(p_anim, read_only);
3932
3933
marker_edit->set_animation(p_anim, read_only);
3934
marker_edit->set_play_position(timeline->get_play_position());
3935
3936
_cancel_bezier_edit();
3937
_update_tracks();
3938
3939
if (animation.is_valid()) {
3940
animation->connect_changed(callable_mp(this, &AnimationTrackEditor::_animation_changed));
3941
3942
hscroll->show();
3943
edit->set_disabled(read_only);
3944
step->set_block_signals(true);
3945
3946
_update_step_spinbox();
3947
step->set_block_signals(false);
3948
step->set_read_only(false);
3949
snap_keys->set_disabled(false);
3950
snap_timeline->set_disabled(false);
3951
fps_compat->set_disabled(false);
3952
snap_mode->set_disabled(false);
3953
auto_fit->set_disabled(false);
3954
auto_fit_bezier->set_disabled(false);
3955
3956
imported_anim_warning->hide();
3957
for (int i = 0; i < animation->get_track_count(); i++) {
3958
if (animation->track_is_imported(i)) {
3959
imported_anim_warning->show();
3960
break;
3961
}
3962
}
3963
3964
_check_bezier_exist();
3965
} else {
3966
hscroll->hide();
3967
edit->set_disabled(true);
3968
step->set_block_signals(true);
3969
step->set_value(0);
3970
step->set_block_signals(false);
3971
step->set_read_only(true);
3972
snap_keys->set_disabled(true);
3973
snap_timeline->set_disabled(true);
3974
fps_compat->set_disabled(true);
3975
snap_mode->set_disabled(true);
3976
bezier_edit_icon->set_disabled(true);
3977
auto_fit->set_disabled(true);
3978
auto_fit_bezier->set_disabled(true);
3979
}
3980
}
3981
3982
void AnimationTrackEditor::_check_bezier_exist() {
3983
bool is_exist = false;
3984
for (int i = 0; i < animation->get_track_count(); i++) {
3985
if (animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) {
3986
is_exist = true;
3987
break;
3988
}
3989
}
3990
if (is_exist) {
3991
bezier_edit_icon->set_disabled(false);
3992
} else {
3993
if (bezier_edit->is_visible()) {
3994
_cancel_bezier_edit();
3995
}
3996
bezier_edit_icon->set_disabled(true);
3997
}
3998
}
3999
4000
Ref<Animation> AnimationTrackEditor::get_current_animation() const {
4001
return animation;
4002
}
4003
4004
void AnimationTrackEditor::_root_removed() {
4005
root = nullptr;
4006
}
4007
4008
void AnimationTrackEditor::set_root(Node *p_root) {
4009
if (root) {
4010
root->disconnect(SceneStringName(tree_exiting), callable_mp(this, &AnimationTrackEditor::_root_removed));
4011
}
4012
4013
root = p_root;
4014
4015
if (root) {
4016
root->connect(SceneStringName(tree_exiting), callable_mp(this, &AnimationTrackEditor::_root_removed), CONNECT_ONE_SHOT);
4017
}
4018
4019
_update_tracks();
4020
}
4021
4022
Node *AnimationTrackEditor::get_root() const {
4023
return root;
4024
}
4025
4026
void AnimationTrackEditor::update_keying() {
4027
bool keying_enabled = false;
4028
4029
EditorSelectionHistory *editor_history = EditorNode::get_singleton()->get_editor_selection_history();
4030
if (is_visible_in_tree() && animation.is_valid() && editor_history->get_path_size() > 0) {
4031
Object *obj = ObjectDB::get_instance(editor_history->get_path_object(0));
4032
keying_enabled = Object::cast_to<Node>(obj) != nullptr || Object::cast_to<MultiNodeEdit>(obj) != nullptr;
4033
}
4034
4035
if (keying_enabled == keying) {
4036
return;
4037
}
4038
4039
keying = keying_enabled;
4040
4041
emit_signal(SNAME("keying_changed"));
4042
}
4043
4044
bool AnimationTrackEditor::has_keying() const {
4045
return keying;
4046
}
4047
4048
Dictionary AnimationTrackEditor::get_state() const {
4049
Dictionary state;
4050
state["fps_mode"] = timeline->is_using_fps();
4051
state["fps_compat"] = fps_compat->is_pressed();
4052
state["zoom"] = zoom->get_value();
4053
state["offset"] = timeline->get_value();
4054
state["v_scroll"] = scroll->get_v_scroll_bar()->get_value();
4055
return state;
4056
}
4057
4058
void AnimationTrackEditor::set_state(const Dictionary &p_state) {
4059
if (p_state.has("fps_mode")) {
4060
bool fps_mode = p_state["fps_mode"];
4061
if (fps_mode) {
4062
snap_mode->select(1);
4063
} else {
4064
snap_mode->select(0);
4065
}
4066
_snap_mode_changed(snap_mode->get_selected());
4067
}
4068
4069
if (p_state.has("fps_compat")) {
4070
fps_compat->set_pressed(p_state["fps_compat"]);
4071
}
4072
4073
if (p_state.has("zoom")) {
4074
zoom->set_value(p_state["zoom"]);
4075
}
4076
4077
if (p_state.has("offset")) {
4078
timeline->set_value(p_state["offset"]);
4079
}
4080
4081
if (p_state.has("v_scroll")) {
4082
scroll->get_v_scroll_bar()->set_value(p_state["v_scroll"]);
4083
}
4084
}
4085
4086
void AnimationTrackEditor::clear() {
4087
snap_mode->select(EDITOR_GET("editors/animation/default_fps_mode"));
4088
_snap_mode_changed(snap_mode->get_selected());
4089
fps_compat->set_pressed(EDITOR_GET("editors/animation/default_fps_compatibility"));
4090
zoom->set_value(1.0);
4091
timeline->set_value(0);
4092
scroll->get_v_scroll_bar()->set_value(0);
4093
}
4094
4095
void AnimationTrackEditor::cleanup() {
4096
set_animation(Ref<Animation>(), read_only);
4097
}
4098
4099
void AnimationTrackEditor::_name_limit_changed() {
4100
_redraw_tracks();
4101
}
4102
4103
void AnimationTrackEditor::_timeline_changed(float p_new_pos, bool p_timeline_only) {
4104
emit_signal(SNAME("timeline_changed"), p_new_pos, p_timeline_only, false);
4105
}
4106
4107
void AnimationTrackEditor::_track_remove_request(int p_track) {
4108
_animation_track_remove_request(p_track, animation);
4109
}
4110
4111
void AnimationTrackEditor::_animation_track_remove_request(int p_track, Ref<Animation> p_from_animation) {
4112
if (p_from_animation->track_is_compressed(p_track)) {
4113
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."));
4114
return;
4115
}
4116
int idx = p_track;
4117
if (idx >= 0 && idx < p_from_animation->get_track_count()) {
4118
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
4119
undo_redo->create_action(TTR("Remove Anim Track"), UndoRedo::MERGE_DISABLE, p_from_animation.ptr());
4120
4121
// Remove corresponding reset tracks if they are no longer needed.
4122
AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();
4123
if (player->has_animation(SceneStringName(RESET))) {
4124
Ref<Animation> reset = player->get_animation(SceneStringName(RESET));
4125
if (reset != p_from_animation) {
4126
for (int i = 0; i < reset->get_track_count(); i++) {
4127
if (reset->track_get_path(i) == p_from_animation->track_get_path(p_track)) {
4128
// Check if the reset track isn't used by other animations.
4129
bool used = false;
4130
List<StringName> animation_list;
4131
player->get_animation_list(&animation_list);
4132
4133
for (const StringName &anim_name : animation_list) {
4134
Ref<Animation> anim = player->get_animation(anim_name);
4135
if (anim == p_from_animation || anim == reset) {
4136
continue;
4137
}
4138
4139
for (int j = 0; j < anim->get_track_count(); j++) {
4140
if (anim->track_get_path(j) == reset->track_get_path(i)) {
4141
used = true;
4142
break;
4143
}
4144
}
4145
4146
if (used) {
4147
break;
4148
}
4149
}
4150
4151
if (!used) {
4152
_animation_track_remove_request(i, reset);
4153
}
4154
break;
4155
}
4156
}
4157
}
4158
}
4159
4160
undo_redo->add_do_method(this, "_clear_selection", false);
4161
undo_redo->add_do_method(p_from_animation.ptr(), "remove_track", idx);
4162
undo_redo->add_undo_method(p_from_animation.ptr(), "add_track", p_from_animation->track_get_type(idx), idx);
4163
undo_redo->add_undo_method(p_from_animation.ptr(), "track_set_path", idx, p_from_animation->track_get_path(idx));
4164
4165
// TODO interpolation.
4166
for (int i = 0; i < p_from_animation->track_get_key_count(idx); i++) {
4167
Variant v = p_from_animation->track_get_key_value(idx, i);
4168
float time = p_from_animation->track_get_key_time(idx, i);
4169
float trans = p_from_animation->track_get_key_transition(idx, i);
4170
4171
undo_redo->add_undo_method(p_from_animation.ptr(), "track_insert_key", idx, time, v);
4172
undo_redo->add_undo_method(p_from_animation.ptr(), "track_set_key_transition", idx, i, trans);
4173
}
4174
4175
undo_redo->add_undo_method(p_from_animation.ptr(), "track_set_interpolation_type", idx, p_from_animation->track_get_interpolation_type(idx));
4176
if (p_from_animation->track_get_type(idx) == Animation::TYPE_VALUE) {
4177
undo_redo->add_undo_method(p_from_animation.ptr(), "value_track_set_update_mode", idx, p_from_animation->value_track_get_update_mode(idx));
4178
}
4179
if (animation->track_get_type(idx) == Animation::TYPE_AUDIO) {
4180
undo_redo->add_undo_method(animation.ptr(), "audio_track_set_use_blend", idx, animation->audio_track_is_use_blend(idx));
4181
}
4182
4183
undo_redo->commit_action();
4184
}
4185
}
4186
4187
void AnimationTrackEditor::_track_grab_focus(int p_track) {
4188
// Don't steal focus if not working with the track editor.
4189
if (Object::cast_to<AnimationTrackEdit>(get_viewport()->gui_get_focus_owner())) {
4190
track_edits[p_track]->grab_focus();
4191
}
4192
}
4193
4194
void AnimationTrackEditor::set_anim_pos(float p_pos) {
4195
timeline->set_play_position(p_pos);
4196
marker_edit->set_play_position(p_pos);
4197
for (int i = 0; i < track_edits.size(); i++) {
4198
track_edits[i]->set_play_position(p_pos);
4199
}
4200
_redraw_groups();
4201
bezier_edit->set_play_position(p_pos);
4202
emit_signal(SNAME("timeline_changed"), p_pos, true, true);
4203
}
4204
4205
static bool track_type_is_resettable(Animation::TrackType p_type) {
4206
switch (p_type) {
4207
case Animation::TYPE_VALUE:
4208
[[fallthrough]];
4209
case Animation::TYPE_BLEND_SHAPE:
4210
[[fallthrough]];
4211
case Animation::TYPE_BEZIER:
4212
[[fallthrough]];
4213
case Animation::TYPE_POSITION_3D:
4214
[[fallthrough]];
4215
case Animation::TYPE_ROTATION_3D:
4216
[[fallthrough]];
4217
case Animation::TYPE_SCALE_3D:
4218
return true;
4219
default:
4220
return false;
4221
}
4222
}
4223
4224
void AnimationTrackEditor::make_insert_queue() {
4225
insert_data.clear();
4226
insert_queue = true;
4227
}
4228
4229
void AnimationTrackEditor::commit_insert_queue() {
4230
bool reset_allowed = true;
4231
AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();
4232
if (player->has_animation(SceneStringName(RESET)) && player->get_animation(SceneStringName(RESET)) == animation) {
4233
// Avoid messing with the reset animation itself.
4234
reset_allowed = false;
4235
} else {
4236
bool some_resettable = false;
4237
for (const AnimationTrackEditor::InsertData &E : insert_data) {
4238
if (track_type_is_resettable(E.type)) {
4239
some_resettable = true;
4240
break;
4241
}
4242
}
4243
if (!some_resettable) {
4244
reset_allowed = false;
4245
}
4246
}
4247
4248
// Organize insert data.
4249
int num_tracks = 0;
4250
String last_track_query;
4251
bool all_bezier = true;
4252
for (const AnimationTrackEditor::InsertData &E : insert_data) {
4253
if (E.type != Animation::TYPE_VALUE && E.type != Animation::TYPE_BEZIER) {
4254
all_bezier = false;
4255
}
4256
4257
if (E.track_idx == -1) {
4258
++num_tracks;
4259
last_track_query = E.query;
4260
}
4261
4262
if (E.type != Animation::TYPE_VALUE) {
4263
continue;
4264
}
4265
4266
switch (E.value.get_type()) {
4267
case Variant::INT:
4268
case Variant::FLOAT:
4269
case Variant::VECTOR2:
4270
case Variant::VECTOR3:
4271
case Variant::QUATERNION:
4272
case Variant::PLANE:
4273
case Variant::COLOR: {
4274
// Valid.
4275
} break;
4276
default: {
4277
all_bezier = false;
4278
}
4279
}
4280
}
4281
4282
// Skip the confirmation dialog if the user holds Shift while clicking the key icon.
4283
// If `confirm_insert_track` editor setting is disabled, the behavior is reversed.
4284
bool confirm_insert = EDITOR_GET("editors/animation/confirm_insert_track");
4285
if ((Input::get_singleton()->is_key_pressed(Key::SHIFT) != confirm_insert) && num_tracks > 0) {
4286
String dialog_text;
4287
4288
// Potentially a new key, does not exist.
4289
if (num_tracks == 1) {
4290
// TRANSLATORS: %s will be replaced by a phrase describing the target of track.
4291
dialog_text = vformat(TTR("Create new track for %s and insert key?"), last_track_query);
4292
} else {
4293
dialog_text = vformat(TTR("Create %d new tracks and insert keys?"), num_tracks);
4294
}
4295
4296
if (confirm_insert) {
4297
dialog_text += +"\n\n" + TTR("Hold Shift when clicking the key icon to skip this dialog.");
4298
}
4299
insert_confirm_text->set_text(dialog_text);
4300
4301
insert_confirm_bezier->set_visible(all_bezier);
4302
insert_confirm_reset->set_visible(reset_allowed);
4303
4304
insert_confirm->set_ok_button_text(TTR("Create"));
4305
insert_confirm->popup_centered();
4306
} else {
4307
_insert_track(reset_allowed && EDITOR_GET("editors/animation/default_create_reset_tracks"), all_bezier && EDITOR_GET("editors/animation/default_create_bezier_tracks"));
4308
}
4309
4310
insert_queue = false;
4311
}
4312
4313
void AnimationTrackEditor::_query_insert(const InsertData &p_id) {
4314
if (!insert_queue) {
4315
insert_data.clear();
4316
}
4317
4318
for (const InsertData &E : insert_data) {
4319
// Prevent insertion of multiple tracks.
4320
if (E.path == p_id.path && E.type == p_id.type) {
4321
return; // Already inserted a track this frame.
4322
}
4323
}
4324
4325
insert_data.push_back(p_id);
4326
4327
// Without queue, commit immediately.
4328
if (!insert_queue) {
4329
commit_insert_queue();
4330
}
4331
}
4332
4333
void AnimationTrackEditor::_insert_track(bool p_reset_wanted, bool p_create_beziers) {
4334
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
4335
undo_redo->create_action(TTR("Animation Insert Key"));
4336
4337
Ref<Animation> reset_anim;
4338
if (p_reset_wanted) {
4339
reset_anim = _create_and_get_reset_animation();
4340
}
4341
4342
TrackIndices next_tracks(animation.ptr(), reset_anim.ptr());
4343
bool advance = false;
4344
while (insert_data.size()) {
4345
if (insert_data.front()->get().advance) {
4346
advance = true;
4347
}
4348
next_tracks = _confirm_insert(insert_data.front()->get(), next_tracks, p_reset_wanted, reset_anim, p_create_beziers);
4349
insert_data.pop_front();
4350
}
4351
4352
undo_redo->commit_action();
4353
4354
if (advance) {
4355
_edit_menu_pressed(EDIT_GOTO_NEXT_STEP_TIMELINE_ONLY);
4356
}
4357
}
4358
4359
void AnimationTrackEditor::insert_transform_key(Node3D *p_node, const String &p_sub, const Animation::TrackType p_type, const Variant &p_value) {
4360
ERR_FAIL_NULL(root);
4361
ERR_FAIL_COND_MSG(
4362
(p_type != Animation::TYPE_POSITION_3D && p_type != Animation::TYPE_ROTATION_3D && p_type != Animation::TYPE_SCALE_3D),
4363
"Track type must be Position/Rotation/Scale 3D.");
4364
if (!keying) {
4365
return;
4366
}
4367
if (animation.is_null()) {
4368
return;
4369
}
4370
4371
// Let's build a node path.
4372
String path = String(root->get_path_to(p_node, true));
4373
if (!p_sub.is_empty()) {
4374
path += ":" + p_sub;
4375
}
4376
4377
NodePath np = path;
4378
4379
int track_idx = -1;
4380
4381
for (int i = 0; i < animation->get_track_count(); i++) {
4382
if (animation->track_get_path(i) != np) {
4383
continue;
4384
}
4385
if (animation->track_get_type(i) != p_type) {
4386
continue;
4387
}
4388
track_idx = i;
4389
}
4390
4391
InsertData id;
4392
id.path = np;
4393
// TRANSLATORS: This describes the target of new animation track, will be inserted into another string.
4394
id.query = vformat(TTR("node '%s'"), p_node->get_name());
4395
id.advance = false;
4396
id.track_idx = track_idx;
4397
id.value = p_value;
4398
id.type = p_type;
4399
_query_insert(id);
4400
}
4401
4402
bool AnimationTrackEditor::has_track(Node3D *p_node, const String &p_sub, const Animation::TrackType p_type) {
4403
ERR_FAIL_NULL_V(root, false);
4404
if (!keying) {
4405
return false;
4406
}
4407
if (animation.is_null()) {
4408
return false;
4409
}
4410
4411
// Let's build a node path.
4412
String path = String(root->get_path_to(p_node, true));
4413
if (!p_sub.is_empty()) {
4414
path += ":" + p_sub;
4415
}
4416
4417
int track_id = animation->find_track(path, p_type);
4418
if (track_id >= 0) {
4419
return true;
4420
}
4421
return false;
4422
}
4423
4424
void AnimationTrackEditor::_insert_animation_key(NodePath p_path, const Variant &p_value) {
4425
String path = String(p_path);
4426
4427
// Animation property is a special case, always creates an animation track.
4428
for (int i = 0; i < animation->get_track_count(); i++) {
4429
String np = String(animation->track_get_path(i));
4430
4431
if (path == np && animation->track_get_type(i) == Animation::TYPE_ANIMATION) {
4432
// Exists.
4433
InsertData id;
4434
id.path = path;
4435
id.track_idx = i;
4436
id.value = p_value;
4437
id.type = Animation::TYPE_ANIMATION;
4438
// TRANSLATORS: This describes the target of new animation track, will be inserted into another string.
4439
id.query = TTR("animation");
4440
id.advance = false;
4441
// Dialog insert.
4442
_query_insert(id);
4443
return;
4444
}
4445
}
4446
4447
InsertData id;
4448
id.path = path;
4449
id.track_idx = -1;
4450
id.value = p_value;
4451
id.type = Animation::TYPE_ANIMATION;
4452
id.query = TTR("animation");
4453
id.advance = false;
4454
// Dialog insert.
4455
_query_insert(id);
4456
}
4457
4458
void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_property, bool p_only_if_exists, bool p_advance) {
4459
ERR_FAIL_NULL(root);
4460
4461
// Let's build a node path.
4462
String path = String(root->get_path_to(p_node, true));
4463
4464
// Get the value from the subpath.
4465
Vector<StringName> subpath = NodePath(p_property).get_as_property_path().get_subnames();
4466
Variant value = p_node->get_indexed(subpath);
4467
4468
if (Object::cast_to<AnimationPlayer>(p_node) && p_property == "current_animation") {
4469
if (p_node == AnimationPlayerEditor::get_singleton()->get_player()) {
4470
EditorNode::get_singleton()->show_warning(TTR("AnimationPlayer can't animate itself, only other players."));
4471
return;
4472
}
4473
_insert_animation_key(path, value);
4474
return;
4475
}
4476
4477
EditorSelectionHistory *history = EditorNode::get_singleton()->get_editor_selection_history();
4478
for (int i = 1; i < history->get_path_size(); i++) {
4479
String prop = history->get_path_property(i);
4480
ERR_FAIL_COND(prop.is_empty());
4481
path += ":" + prop;
4482
}
4483
4484
path += ":" + p_property;
4485
4486
NodePath np = path;
4487
4488
// Locate track.
4489
4490
bool inserted = false;
4491
4492
for (int i = 0; i < animation->get_track_count(); i++) {
4493
if (animation->track_get_type(i) == Animation::TYPE_VALUE) {
4494
if (animation->track_get_path(i) != np) {
4495
continue;
4496
}
4497
4498
InsertData id;
4499
id.path = np;
4500
id.track_idx = i;
4501
id.value = value;
4502
id.type = Animation::TYPE_VALUE;
4503
// TRANSLATORS: This describes the target of new animation track, will be inserted into another string.
4504
id.query = vformat(TTR("property '%s'"), p_property);
4505
id.advance = p_advance;
4506
// Dialog insert.
4507
_query_insert(id);
4508
inserted = true;
4509
} else if (animation->track_get_type(i) == Animation::TYPE_BEZIER) {
4510
Variant actual_value;
4511
String track_path = String(animation->track_get_path(i));
4512
if (track_path == String(np)) {
4513
actual_value = value; // All good.
4514
} else {
4515
int sep = track_path.rfind_char(':');
4516
if (sep != -1) {
4517
String base_path = track_path.substr(0, sep);
4518
if (base_path == String(np)) {
4519
String value_name = track_path.substr(sep + 1);
4520
actual_value = value.get(value_name);
4521
} else {
4522
continue;
4523
}
4524
} else {
4525
continue;
4526
}
4527
}
4528
4529
InsertData id;
4530
id.path = animation->track_get_path(i);
4531
id.track_idx = i;
4532
id.value = actual_value;
4533
id.type = Animation::TYPE_BEZIER;
4534
id.query = vformat(TTR("property '%s'"), p_property);
4535
id.advance = p_advance;
4536
// Dialog insert.
4537
_query_insert(id);
4538
inserted = true;
4539
}
4540
}
4541
4542
if (inserted || p_only_if_exists) {
4543
return;
4544
}
4545
InsertData id;
4546
id.path = np;
4547
id.track_idx = -1;
4548
id.value = value;
4549
id.type = Animation::TYPE_VALUE;
4550
id.query = vformat(TTR("property '%s'"), p_property);
4551
id.advance = p_advance;
4552
// Dialog insert.
4553
_query_insert(id);
4554
}
4555
4556
PackedStringArray AnimationTrackEditor::get_selected_section() const {
4557
return marker_edit->get_selected_section();
4558
}
4559
4560
bool AnimationTrackEditor::is_marker_selected(const StringName &p_marker) const {
4561
return marker_edit->is_marker_selected(p_marker);
4562
}
4563
4564
bool AnimationTrackEditor::is_marker_moving_selection() const {
4565
return marker_edit->is_moving_selection();
4566
}
4567
4568
float AnimationTrackEditor::get_marker_moving_selection_offset() const {
4569
return marker_edit->get_moving_selection_offset();
4570
}
4571
4572
void AnimationTrackEditor::insert_value_key(const String &p_property, bool p_advance) {
4573
EditorSelectionHistory *history = EditorNode::get_singleton()->get_editor_selection_history();
4574
4575
ERR_FAIL_NULL(root);
4576
ERR_FAIL_COND(history->get_path_size() == 0);
4577
Object *obj = ObjectDB::get_instance(history->get_path_object(0));
4578
4579
Ref<MultiNodeEdit> multi_node_edit(obj);
4580
if (multi_node_edit.is_valid()) {
4581
Node *edited_scene = EditorNode::get_singleton()->get_edited_scene();
4582
ERR_FAIL_NULL(edited_scene);
4583
4584
make_insert_queue();
4585
4586
for (int i = 0; i < multi_node_edit->get_node_count(); ++i) {
4587
Node *node = edited_scene->get_node(multi_node_edit->get_node(i));
4588
insert_node_value_key(node, p_property, false, p_advance);
4589
}
4590
4591
commit_insert_queue();
4592
} else {
4593
Node *node = Object::cast_to<Node>(obj);
4594
ERR_FAIL_NULL(node);
4595
4596
make_insert_queue();
4597
insert_node_value_key(node, p_property, false, p_advance);
4598
commit_insert_queue();
4599
}
4600
}
4601
4602
Ref<Animation> AnimationTrackEditor::_create_and_get_reset_animation() {
4603
AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();
4604
if (player->has_animation(SceneStringName(RESET))) {
4605
return player->get_animation(SceneStringName(RESET));
4606
} else {
4607
Ref<AnimationLibrary> al;
4608
AnimationMixer *mixer = AnimationPlayerEditor::get_singleton()->fetch_mixer_for_library();
4609
if (mixer) {
4610
if (!mixer->has_animation_library("")) {
4611
al.instantiate();
4612
mixer->add_animation_library("", al);
4613
} else {
4614
al = mixer->get_animation_library("");
4615
}
4616
}
4617
Ref<Animation> reset_anim;
4618
reset_anim.instantiate();
4619
reset_anim->set_length(ANIM_MIN_LENGTH);
4620
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
4621
undo_redo->add_do_method(al.ptr(), "add_animation", SceneStringName(RESET), reset_anim);
4622
undo_redo->add_do_method(AnimationPlayerEditor::get_singleton(), "_animation_player_changed", player);
4623
undo_redo->add_undo_method(al.ptr(), "remove_animation", SceneStringName(RESET));
4624
undo_redo->add_undo_method(AnimationPlayerEditor::get_singleton(), "_animation_player_changed", player);
4625
return reset_anim;
4626
}
4627
}
4628
4629
void AnimationTrackEditor::_confirm_insert_list() {
4630
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
4631
undo_redo->create_action(TTR("Animation Insert Key"));
4632
4633
bool create_reset = insert_confirm_reset->is_visible() && insert_confirm_reset->is_pressed();
4634
Ref<Animation> reset_anim;
4635
if (create_reset) {
4636
reset_anim = _create_and_get_reset_animation();
4637
}
4638
4639
TrackIndices next_tracks(animation.ptr(), reset_anim.ptr());
4640
bool advance = false;
4641
while (insert_data.size()) {
4642
if (insert_data.front()->get().advance) {
4643
advance = true;
4644
}
4645
next_tracks = _confirm_insert(insert_data.front()->get(), next_tracks, create_reset, reset_anim, insert_confirm_bezier->is_pressed());
4646
insert_data.pop_front();
4647
}
4648
4649
undo_redo->commit_action();
4650
4651
if (advance) {
4652
_edit_menu_pressed(EDIT_GOTO_NEXT_STEP_TIMELINE_ONLY);
4653
}
4654
}
4655
4656
PropertyInfo AnimationTrackEditor::_find_hint_for_track(int p_idx, NodePath &r_base_path, Variant *r_current_val) {
4657
r_base_path = NodePath();
4658
ERR_FAIL_COND_V(animation.is_null(), PropertyInfo());
4659
ERR_FAIL_INDEX_V(p_idx, animation->get_track_count(), PropertyInfo());
4660
4661
if (!root) {
4662
return PropertyInfo();
4663
}
4664
4665
NodePath path = animation->track_get_path(p_idx);
4666
4667
if (!root->has_node_and_resource(path)) {
4668
return PropertyInfo();
4669
}
4670
4671
Ref<Resource> res;
4672
Vector<StringName> leftover_path;
4673
Node *node = root->get_node_and_resource(path, res, leftover_path, true);
4674
4675
if (node) {
4676
r_base_path = node->get_path();
4677
}
4678
4679
if (leftover_path.is_empty()) {
4680
if (r_current_val) {
4681
if (res.is_valid()) {
4682
*r_current_val = res;
4683
} else if (node) {
4684
*r_current_val = node;
4685
}
4686
}
4687
return PropertyInfo();
4688
}
4689
4690
Variant property_info_base;
4691
if (res.is_valid()) {
4692
property_info_base = res;
4693
if (r_current_val) {
4694
*r_current_val = res->get_indexed(leftover_path);
4695
}
4696
} else if (node) {
4697
property_info_base = node;
4698
if (r_current_val) {
4699
*r_current_val = node->get_indexed(leftover_path);
4700
}
4701
}
4702
4703
if (property_info_base.is_null()) {
4704
WARN_PRINT(vformat("Could not determine track hint for '%s:%s' because its base property is null.",
4705
String(path.get_concatenated_names()), String(path.get_concatenated_subnames())));
4706
return PropertyInfo();
4707
}
4708
4709
List<PropertyInfo> pinfo;
4710
property_info_base.get_property_list(&pinfo);
4711
4712
for (const PropertyInfo &E : pinfo) {
4713
if (E.name == leftover_path[leftover_path.size() - 1]) {
4714
return E;
4715
}
4716
}
4717
4718
return PropertyInfo();
4719
}
4720
4721
static Vector<String> _get_bezier_subindices_for_type(Variant::Type p_type, bool *r_valid = nullptr) {
4722
Vector<String> subindices;
4723
if (r_valid) {
4724
*r_valid = true;
4725
}
4726
switch (p_type) {
4727
case Variant::INT: {
4728
subindices.push_back("");
4729
} break;
4730
case Variant::FLOAT: {
4731
subindices.push_back("");
4732
} break;
4733
case Variant::VECTOR2: {
4734
subindices.push_back(":x");
4735
subindices.push_back(":y");
4736
} break;
4737
case Variant::VECTOR3: {
4738
subindices.push_back(":x");
4739
subindices.push_back(":y");
4740
subindices.push_back(":z");
4741
} break;
4742
case Variant::QUATERNION: {
4743
subindices.push_back(":x");
4744
subindices.push_back(":y");
4745
subindices.push_back(":z");
4746
subindices.push_back(":w");
4747
} break;
4748
case Variant::COLOR: {
4749
subindices.push_back(":r");
4750
subindices.push_back(":g");
4751
subindices.push_back(":b");
4752
subindices.push_back(":a");
4753
} break;
4754
case Variant::PLANE: {
4755
subindices.push_back(":x");
4756
subindices.push_back(":y");
4757
subindices.push_back(":z");
4758
subindices.push_back(":d");
4759
} break;
4760
case Variant::NIL: {
4761
subindices.push_back(""); // Hack: it is probably float since non-numeric types are filtered in the selection window.
4762
} break;
4763
default: {
4764
if (r_valid) {
4765
*r_valid = false;
4766
}
4767
}
4768
}
4769
4770
return subindices;
4771
}
4772
4773
AnimationTrackEditor::TrackIndices AnimationTrackEditor::_confirm_insert(InsertData p_id, TrackIndices p_next_tracks, bool p_reset_wanted, Ref<Animation> p_reset_anim, bool p_create_beziers) {
4774
bool created = false;
4775
4776
bool create_normal_track = p_id.track_idx < 0;
4777
bool create_reset_track = p_reset_wanted && track_type_is_resettable(p_id.type);
4778
4779
Animation::UpdateMode update_mode = Animation::UPDATE_DISCRETE;
4780
Animation::InterpolationType interp_type = Animation::INTERPOLATION_LINEAR;
4781
bool loop_wrap = true;
4782
if (create_normal_track || create_reset_track) {
4783
if (p_id.type == Animation::TYPE_VALUE || p_id.type == Animation::TYPE_BEZIER) {
4784
_fetch_value_track_options(p_id.path, &update_mode, &interp_type, &loop_wrap);
4785
}
4786
}
4787
4788
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
4789
if (create_normal_track) {
4790
if (p_create_beziers) {
4791
bool valid;
4792
Vector<String> subindices = _get_bezier_subindices_for_type(p_id.value.get_type(), &valid);
4793
if (valid) {
4794
for (int i = 0; i < subindices.size(); i++) {
4795
InsertData id = p_id;
4796
id.type = Animation::TYPE_BEZIER;
4797
id.value = subindices[i].is_empty() ? p_id.value : p_id.value.get(subindices[i].substr(1));
4798
id.path = String(p_id.path) + subindices[i];
4799
p_next_tracks = _confirm_insert(id, p_next_tracks, p_reset_wanted, p_reset_anim, false);
4800
}
4801
4802
return p_next_tracks;
4803
}
4804
}
4805
created = true;
4806
4807
p_id.track_idx = p_next_tracks.normal;
4808
4809
undo_redo->add_do_method(animation.ptr(), "add_track", p_id.type);
4810
undo_redo->add_do_method(animation.ptr(), "track_set_path", p_id.track_idx, p_id.path);
4811
undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_type", p_id.track_idx, interp_type);
4812
undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_loop_wrap", p_id.track_idx, loop_wrap);
4813
if (p_id.type == Animation::TYPE_VALUE) {
4814
undo_redo->add_do_method(animation.ptr(), "value_track_set_update_mode", p_id.track_idx, update_mode);
4815
}
4816
}
4817
4818
float time = p_id.time == FLT_MAX ? timeline->get_play_position() : p_id.time;
4819
Variant value;
4820
4821
switch (p_id.type) {
4822
case Animation::TYPE_POSITION_3D:
4823
case Animation::TYPE_ROTATION_3D:
4824
case Animation::TYPE_SCALE_3D:
4825
case Animation::TYPE_BLEND_SHAPE:
4826
case Animation::TYPE_VALUE:
4827
case Animation::TYPE_AUDIO:
4828
case Animation::TYPE_ANIMATION: {
4829
value = p_id.value;
4830
4831
} break;
4832
case Animation::TYPE_BEZIER: {
4833
int existing = -1;
4834
if (p_id.track_idx < animation->get_track_count()) {
4835
existing = animation->track_find_key(p_id.track_idx, time, Animation::FIND_MODE_APPROX);
4836
}
4837
4838
if (existing != -1) {
4839
Array arr = animation->track_get_key_value(p_id.track_idx, existing);
4840
arr[0] = p_id.value;
4841
value = arr;
4842
} else {
4843
value = animation->make_default_bezier_key(p_id.value);
4844
}
4845
bezier_edit_icon->set_disabled(false);
4846
} break;
4847
default: {
4848
// Other track types shouldn't use this code path.
4849
DEV_ASSERT(false);
4850
}
4851
}
4852
4853
undo_redo->add_do_method(animation.ptr(), "track_insert_key", p_id.track_idx, time, value);
4854
if (!created && p_id.type == Animation::TYPE_BEZIER) {
4855
undo_redo->add_do_method(this, "_bezier_track_set_key_handle_mode_at_time", animation.ptr(), p_id.track_idx, time, (Animation::HandleMode)bezier_key_mode->get_selected_id(), Animation::HANDLE_SET_MODE_AUTO);
4856
}
4857
4858
if (created) {
4859
// Just remove the track.
4860
undo_redo->add_undo_method(this, "_clear_selection", false);
4861
undo_redo->add_undo_method(animation.ptr(), "remove_track", animation->get_track_count());
4862
p_next_tracks.normal++;
4863
} else {
4864
undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_id.track_idx, time);
4865
int existing = animation->track_find_key(p_id.track_idx, time, Animation::FIND_MODE_APPROX);
4866
if (existing != -1) {
4867
Variant v = animation->track_get_key_value(p_id.track_idx, existing);
4868
float trans = animation->track_get_key_transition(p_id.track_idx, existing);
4869
undo_redo->add_undo_method(animation.ptr(), "track_insert_key", p_id.track_idx, time, v, trans);
4870
}
4871
}
4872
4873
if (create_reset_track) {
4874
Animation *reset_anim = p_reset_anim.ptr();
4875
for (int i = 0; i < reset_anim->get_track_count(); i++) {
4876
if (reset_anim->track_get_path(i) == p_id.path) {
4877
create_reset_track = false;
4878
break;
4879
}
4880
}
4881
if (create_reset_track) {
4882
undo_redo->add_do_method(reset_anim, "add_track", p_id.type);
4883
undo_redo->add_do_method(reset_anim, "track_set_path", p_next_tracks.reset, p_id.path);
4884
if (p_id.type == Animation::TYPE_VALUE) {
4885
undo_redo->add_do_method(reset_anim, "value_track_set_update_mode", p_next_tracks.reset, update_mode);
4886
}
4887
undo_redo->add_do_method(reset_anim, "track_set_interpolation_type", p_next_tracks.reset, interp_type);
4888
undo_redo->add_do_method(reset_anim, "track_insert_key", p_next_tracks.reset, 0.0f, value);
4889
undo_redo->add_undo_method(reset_anim, "remove_track", reset_anim->get_track_count());
4890
p_next_tracks.reset++;
4891
}
4892
}
4893
4894
return p_next_tracks;
4895
}
4896
4897
void AnimationTrackEditor::show_select_node_warning(bool p_show) {
4898
info_message->set_visible(p_show);
4899
}
4900
4901
void AnimationTrackEditor::show_dummy_player_warning(bool p_show) {
4902
dummy_player_warning->set_visible(p_show);
4903
}
4904
4905
void AnimationTrackEditor::show_inactive_player_warning(bool p_show) {
4906
inactive_player_warning->set_visible(p_show);
4907
}
4908
4909
bool AnimationTrackEditor::is_key_selected(int p_track, int p_key) const {
4910
SelectedKey sk;
4911
sk.key = p_key;
4912
sk.track = p_track;
4913
4914
return selection.has(sk);
4915
}
4916
4917
bool AnimationTrackEditor::is_selection_active() const {
4918
return selection.size();
4919
}
4920
4921
bool AnimationTrackEditor::is_key_clipboard_active() const {
4922
return key_clipboard.keys.size();
4923
}
4924
4925
bool AnimationTrackEditor::is_snap_timeline_enabled() const {
4926
return snap_timeline->is_pressed() ^ Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL);
4927
}
4928
4929
bool AnimationTrackEditor::is_snap_keys_enabled() const {
4930
return snap_keys->is_pressed() ^ Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL);
4931
}
4932
4933
bool AnimationTrackEditor::is_bezier_editor_active() const {
4934
return bezier_edit->is_visible();
4935
}
4936
4937
bool AnimationTrackEditor::can_add_reset_key() const {
4938
for (const KeyValue<SelectedKey, KeyInfo> &E : selection) {
4939
const Animation::TrackType track_type = animation->track_get_type(E.key.track);
4940
if (track_type != Animation::TYPE_ANIMATION && track_type != Animation::TYPE_AUDIO && track_type != Animation::TYPE_METHOD) {
4941
return true;
4942
}
4943
}
4944
return false;
4945
}
4946
4947
void AnimationTrackEditor::_on_filter_updated(const String &p_filter) {
4948
emit_signal(SNAME("filter_changed"));
4949
}
4950
4951
void AnimationTrackEditor::_update_tracks() {
4952
int selected = _get_track_selected();
4953
4954
while (track_vbox->get_child_count()) {
4955
memdelete(track_vbox->get_child(0));
4956
}
4957
4958
timeline->set_track_edit(nullptr);
4959
4960
track_edits.clear();
4961
groups.clear();
4962
4963
if (animation.is_null()) {
4964
return;
4965
}
4966
4967
bool file_read_only = false;
4968
if (!animation->get_path().is_resource_file()) {
4969
int srpos = animation->get_path().find("::");
4970
if (srpos != -1) {
4971
String base = animation->get_path().substr(0, srpos);
4972
if (ResourceLoader::get_resource_type(base) == "PackedScene") {
4973
if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) {
4974
file_read_only = true;
4975
}
4976
} else {
4977
if (FileAccess::exists(base + ".import")) {
4978
file_read_only = true;
4979
}
4980
}
4981
}
4982
} else {
4983
if (FileAccess::exists(animation->get_path() + ".import")) {
4984
file_read_only = true;
4985
}
4986
}
4987
4988
RBMap<String, VBoxContainer *> group_sort;
4989
LocalVector<VBoxContainer *> group_containers;
4990
4991
bool use_grouping = !view_group->is_pressed();
4992
bool use_filter = selected_filter->is_pressed();
4993
bool use_alphabetic_sorting = alphabetic_sorting->is_pressed();
4994
4995
AnimationTrackEdit *selected_track_edit = nullptr;
4996
4997
for (int i = 0; i < animation->get_track_count(); i++) {
4998
AnimationTrackEdit *track_edit = nullptr;
4999
5000
// Find hint and info for plugin.
5001
5002
if (use_filter) {
5003
NodePath path = animation->track_get_path(i);
5004
5005
if (root) {
5006
Node *node = root->get_node_or_null(path);
5007
if (!node) {
5008
continue; // No node, no filter.
5009
}
5010
if (!EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) {
5011
continue; // Skip track due to not selected.
5012
}
5013
}
5014
}
5015
5016
String filter_text = timeline->filter_track->get_text();
5017
5018
if (!filter_text.is_empty()) {
5019
String target = String(animation->track_get_path(i));
5020
if (!target.containsn(filter_text)) {
5021
continue;
5022
}
5023
}
5024
5025
if (animation->track_get_type(i) == Animation::TYPE_VALUE) {
5026
NodePath path = animation->track_get_path(i);
5027
5028
if (root && root->has_node_and_resource(path)) {
5029
Ref<Resource> res;
5030
NodePath base_path;
5031
Vector<StringName> leftover_path;
5032
Node *node = root->get_node_and_resource(path, res, leftover_path, true);
5033
PropertyInfo pinfo = _find_hint_for_track(i, base_path);
5034
5035
Object *object = node;
5036
if (res.is_valid()) {
5037
object = res.ptr();
5038
}
5039
5040
if (object && !leftover_path.is_empty()) {
5041
if (pinfo.name.is_empty()) {
5042
pinfo.name = leftover_path[leftover_path.size() - 1];
5043
}
5044
5045
for (int j = 0; j < track_edit_plugins.size(); j++) {
5046
track_edit = track_edit_plugins.write[j]->create_value_track_edit(object, pinfo.type, pinfo.name, pinfo.hint, pinfo.hint_string, pinfo.usage);
5047
if (track_edit) {
5048
break;
5049
}
5050
}
5051
}
5052
}
5053
}
5054
if (animation->track_get_type(i) == Animation::TYPE_AUDIO) {
5055
for (int j = 0; j < track_edit_plugins.size(); j++) {
5056
track_edit = track_edit_plugins.write[j]->create_audio_track_edit();
5057
if (track_edit) {
5058
break;
5059
}
5060
}
5061
}
5062
5063
if (animation->track_get_type(i) == Animation::TYPE_ANIMATION) {
5064
NodePath path = animation->track_get_path(i);
5065
5066
Node *node = nullptr;
5067
if (root) {
5068
node = root->get_node_or_null(path);
5069
}
5070
5071
if (node && Object::cast_to<AnimationPlayer>(node)) {
5072
for (int j = 0; j < track_edit_plugins.size(); j++) {
5073
track_edit = track_edit_plugins.write[j]->create_animation_track_edit(node);
5074
if (track_edit) {
5075
break;
5076
}
5077
}
5078
}
5079
}
5080
5081
if (track_edit == nullptr) {
5082
// No valid plugin_found.
5083
track_edit = memnew(AnimationTrackEdit);
5084
}
5085
5086
track_edits.push_back(track_edit);
5087
5088
if (use_grouping) {
5089
String base_path = String(animation->track_get_path(i));
5090
base_path = base_path.get_slicec(':', 0); // Remove sub-path.
5091
5092
if (!group_sort.has(base_path)) {
5093
AnimationTrackEditGroup *g = memnew(AnimationTrackEditGroup);
5094
Ref<Texture2D> icon = get_editor_theme_icon(SNAME("Node"));
5095
String name = base_path;
5096
String tooltip;
5097
if (root) {
5098
Node *n = root->get_node_or_null(base_path);
5099
if (n) {
5100
icon = EditorNode::get_singleton()->get_object_icon(n, "Node");
5101
name = n->get_name();
5102
tooltip = String(root->get_path_to(n));
5103
}
5104
}
5105
5106
g->set_type_and_name(icon, name, animation->track_get_path(i));
5107
g->set_root(root);
5108
g->set_tooltip_text(tooltip);
5109
g->set_timeline(timeline);
5110
g->set_editor(this);
5111
groups.push_back(g);
5112
VBoxContainer *vb = memnew(VBoxContainer);
5113
vb->add_theme_constant_override("separation", 0);
5114
vb->add_child(g);
5115
group_sort[base_path] = vb;
5116
group_containers.push_back(vb);
5117
}
5118
5119
track_edit->set_in_group(true);
5120
group_sort[base_path]->add_child(track_edit);
5121
5122
} else {
5123
track_edit->set_in_group(false);
5124
}
5125
5126
track_edit->set_timeline(timeline);
5127
track_edit->set_root(root);
5128
track_edit->set_animation_and_track(animation, i, file_read_only);
5129
track_edit->set_play_position(timeline->get_play_position());
5130
track_edit->set_editor(this);
5131
5132
if (selected == i) {
5133
selected_track_edit = track_edit;
5134
}
5135
5136
track_edit->connect("timeline_changed", callable_mp(this, &AnimationTrackEditor::_timeline_changed));
5137
track_edit->connect("remove_request", callable_mp(this, &AnimationTrackEditor::_track_remove_request), CONNECT_DEFERRED);
5138
track_edit->connect("dropped", callable_mp(this, &AnimationTrackEditor::_dropped_track), CONNECT_DEFERRED);
5139
track_edit->connect("insert_key", callable_mp(this, &AnimationTrackEditor::_insert_key_from_track).bind(i), CONNECT_DEFERRED);
5140
track_edit->connect("select_key", callable_mp(this, &AnimationTrackEditor::_key_selected).bind(i), CONNECT_DEFERRED);
5141
track_edit->connect("deselect_key", callable_mp(this, &AnimationTrackEditor::_key_deselected).bind(i), CONNECT_DEFERRED);
5142
track_edit->connect("move_selection_begin", callable_mp(this, &AnimationTrackEditor::_move_selection_begin));
5143
track_edit->connect("move_selection", callable_mp(this, &AnimationTrackEditor::_move_selection));
5144
track_edit->connect("move_selection_commit", callable_mp(this, &AnimationTrackEditor::_move_selection_commit));
5145
track_edit->connect("move_selection_cancel", callable_mp(this, &AnimationTrackEditor::_move_selection_cancel));
5146
5147
track_edit->connect("duplicate_request", callable_mp(this, &AnimationTrackEditor::_anim_duplicate_keys).bind(i), CONNECT_DEFERRED);
5148
track_edit->connect("cut_request", callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed).bind(EDIT_CUT_KEYS), CONNECT_DEFERRED);
5149
track_edit->connect("copy_request", callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed).bind(EDIT_COPY_KEYS), CONNECT_DEFERRED);
5150
track_edit->connect("paste_request", callable_mp(this, &AnimationTrackEditor::_anim_paste_keys).bind(i), CONNECT_DEFERRED);
5151
track_edit->connect("create_reset_request", callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed).bind(EDIT_ADD_RESET_KEY), CONNECT_DEFERRED);
5152
track_edit->connect("delete_request", callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed).bind(EDIT_DELETE_SELECTION), CONNECT_DEFERRED);
5153
}
5154
5155
if (use_grouping) {
5156
if (use_alphabetic_sorting) {
5157
struct GroupAlphaCompare {
5158
bool operator()(const VBoxContainer *p_lhs, const VBoxContainer *p_rhs) const {
5159
String lhs_node_name = Object::cast_to<AnimationTrackEditGroup>(p_lhs->get_child(0))->get_node_name();
5160
String rhs_node_name = Object::cast_to<AnimationTrackEditGroup>(p_rhs->get_child(0))->get_node_name();
5161
return lhs_node_name < rhs_node_name;
5162
}
5163
};
5164
5165
group_containers.sort_custom<GroupAlphaCompare>();
5166
}
5167
5168
for (VBoxContainer *vb : group_containers) {
5169
track_vbox->add_child(vb);
5170
}
5171
5172
} else {
5173
if (use_alphabetic_sorting) {
5174
struct TrackAlphaCompare {
5175
bool operator()(const AnimationTrackEdit *p_lhs, const AnimationTrackEdit *p_rhs) const {
5176
String lhs_leaf = (String)p_lhs->get_path().slice(-p_lhs->get_path().get_subname_count() - 1);
5177
String rhs_leaf = (String)p_rhs->get_path().slice(-p_rhs->get_path().get_subname_count() - 1);
5178
return lhs_leaf < rhs_leaf;
5179
}
5180
};
5181
5182
track_edits.sort_custom<TrackAlphaCompare>();
5183
}
5184
5185
for (AnimationTrackEdit *track_edit : track_edits) {
5186
track_vbox->add_child(track_edit);
5187
}
5188
}
5189
5190
if (selected_track_edit != nullptr) {
5191
selected_track_edit->grab_focus();
5192
}
5193
}
5194
5195
void AnimationTrackEditor::_redraw_tracks() {
5196
for (int i = 0; i < track_edits.size(); i++) {
5197
track_edits[i]->queue_redraw();
5198
}
5199
}
5200
5201
void AnimationTrackEditor::_redraw_groups() {
5202
for (int i = 0; i < groups.size(); i++) {
5203
groups[i]->queue_redraw();
5204
}
5205
}
5206
5207
void AnimationTrackEditor::_animation_changed() {
5208
if (animation_changing_awaiting_update) {
5209
return; // All will be updated, don't bother with anything.
5210
}
5211
5212
_check_bezier_exist();
5213
5214
if (key_edit) {
5215
if (key_edit->setting) {
5216
// If editing a key, just redraw the edited track, makes refresh less costly.
5217
if (key_edit->track < track_edits.size()) {
5218
if (animation->track_get_type(key_edit->track) == Animation::TYPE_BEZIER) {
5219
bezier_edit->queue_redraw();
5220
} else {
5221
track_edits[key_edit->track]->queue_redraw();
5222
}
5223
}
5224
return;
5225
} else {
5226
_update_key_edit();
5227
}
5228
}
5229
5230
animation_changing_awaiting_update = true;
5231
callable_mp(this, &AnimationTrackEditor::_animation_update).call_deferred();
5232
}
5233
5234
void AnimationTrackEditor::_snap_mode_changed(int p_mode) {
5235
bool use_fps = p_mode == 1;
5236
timeline->set_use_fps(use_fps);
5237
if (key_edit) {
5238
key_edit->set_use_fps(use_fps);
5239
}
5240
marker_edit->set_use_fps(use_fps);
5241
// To ensure that the conversion results are consistent between serialization and load, the value is snapped with 0.0625 to be a rational number when FPS mode is used.
5242
step->set_step(use_fps ? FPS_DECIMAL : SECOND_DECIMAL);
5243
if (use_fps) {
5244
fps_compat->hide();
5245
} else {
5246
fps_compat->show();
5247
}
5248
_update_step_spinbox();
5249
}
5250
5251
void AnimationTrackEditor::_update_step_spinbox() {
5252
if (animation.is_null()) {
5253
return;
5254
}
5255
step->set_block_signals(true);
5256
5257
if (timeline->is_using_fps()) {
5258
if (animation->get_step() == 0.0) {
5259
step->set_value(0.0);
5260
} else {
5261
step->set_value(1.0 / animation->get_step());
5262
}
5263
} else {
5264
step->set_value(animation->get_step());
5265
}
5266
5267
step->set_block_signals(false);
5268
_update_snap_unit();
5269
}
5270
5271
void AnimationTrackEditor::_update_fps_compat_mode(bool p_enabled) {
5272
_update_snap_unit();
5273
}
5274
5275
void AnimationTrackEditor::_update_nearest_fps_label() {
5276
bool is_fps_invalid = nearest_fps == 0;
5277
if (is_fps_invalid) {
5278
nearest_fps_label->hide();
5279
} else {
5280
nearest_fps_label->show();
5281
nearest_fps_label->set_text(vformat(TTR("Nearest FPS: %d"), nearest_fps));
5282
}
5283
}
5284
5285
void AnimationTrackEditor::_animation_update() {
5286
timeline->queue_redraw();
5287
timeline->update_values();
5288
5289
bool same = true;
5290
5291
if (animation.is_null()) {
5292
return;
5293
}
5294
5295
if (track_edits.size() == animation->get_track_count()) {
5296
// Check tracks are the same.
5297
5298
for (int i = 0; i < track_edits.size(); i++) {
5299
if (track_edits[i]->get_path() != animation->track_get_path(i)) {
5300
same = false;
5301
break;
5302
}
5303
}
5304
} else {
5305
same = false;
5306
}
5307
5308
if (same) {
5309
_redraw_tracks();
5310
_redraw_groups();
5311
} else {
5312
_update_tracks();
5313
}
5314
5315
bezier_edit->queue_redraw();
5316
5317
_update_step_spinbox();
5318
emit_signal(SNAME("animation_step_changed"), animation->get_step());
5319
emit_signal(SNAME("animation_len_changed"), animation->get_length());
5320
5321
animation_changing_awaiting_update = false;
5322
}
5323
5324
MenuButton *AnimationTrackEditor::get_edit_menu() {
5325
return edit;
5326
}
5327
5328
void AnimationTrackEditor::_notification(int p_what) {
5329
switch (p_what) {
5330
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
5331
if (!EditorSettings::get_singleton()->check_changed_settings_in_group("editors/panning")) {
5332
break;
5333
}
5334
[[fallthrough]];
5335
}
5336
case NOTIFICATION_ENTER_TREE: {
5337
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")));
5338
panner->setup_warped_panning(get_viewport(), EDITOR_GET("editors/panning/warped_mouse_panning"));
5339
} break;
5340
case NOTIFICATION_THEME_CHANGED: {
5341
zoom_icon->set_texture(get_editor_theme_icon(SNAME("Zoom")));
5342
bezier_edit_icon->set_button_icon(get_editor_theme_icon(SNAME("EditBezier")));
5343
snap_timeline->set_button_icon(get_editor_theme_icon(SNAME("SnapTimeline")));
5344
snap_keys->set_button_icon(get_editor_theme_icon(SNAME("SnapKeys")));
5345
fps_compat->set_button_icon(get_editor_theme_icon(SNAME("FPS")));
5346
view_group->set_button_icon(get_editor_theme_icon(view_group->is_pressed() ? SNAME("AnimationTrackList") : SNAME("AnimationTrackGroup")));
5347
function_name_toggler->set_button_icon(get_editor_theme_icon(SNAME("MemberMethod")));
5348
selected_filter->set_button_icon(get_editor_theme_icon(SNAME("AnimationFilter")));
5349
alphabetic_sorting->set_button_icon(get_editor_theme_icon(SNAME("Sort")));
5350
imported_anim_warning->set_button_icon(get_editor_theme_icon(SNAME("NodeWarning")));
5351
dummy_player_warning->set_button_icon(get_editor_theme_icon(SNAME("NodeWarning")));
5352
inactive_player_warning->set_button_icon(get_editor_theme_icon(SNAME("NodeWarning")));
5353
main_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("Tree")));
5354
edit->get_popup()->set_item_icon(edit->get_popup()->get_item_index(EDIT_APPLY_RESET), get_editor_theme_icon(SNAME("Reload")));
5355
auto_fit->set_button_icon(get_editor_theme_icon(SNAME("AnimationAutoFit")));
5356
auto_fit_bezier->set_button_icon(get_editor_theme_icon(SNAME("AnimationAutoFitBezier")));
5357
5358
const int timeline_separation = get_theme_constant(SNAME("timeline_v_separation"), SNAME("AnimationTrackEditor"));
5359
timeline_vbox->add_theme_constant_override("separation", timeline_separation);
5360
5361
const int track_separation = get_theme_constant(SNAME("track_v_separation"), SNAME("AnimationTrackEditor"));
5362
track_vbox->add_theme_constant_override("separation", track_separation);
5363
5364
function_name_toggler->add_theme_color_override("icon_pressed_color", get_theme_color("icon_disabled_color", EditorStringName(Editor)));
5365
} break;
5366
5367
case NOTIFICATION_READY: {
5368
EditorNode::get_singleton()->get_editor_selection()->connect("selection_changed", callable_mp(this, &AnimationTrackEditor::_selection_changed));
5369
} break;
5370
5371
case NOTIFICATION_VISIBILITY_CHANGED: {
5372
update_keying();
5373
} break;
5374
}
5375
}
5376
5377
void AnimationTrackEditor::_update_scroll(double) {
5378
_redraw_tracks();
5379
_redraw_groups();
5380
marker_edit->queue_redraw();
5381
}
5382
5383
void AnimationTrackEditor::_update_step(double p_new_step) {
5384
if (animation.is_null()) {
5385
return;
5386
}
5387
5388
_update_snap_unit();
5389
5390
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
5391
undo_redo->create_action(TTR("Change Animation Step"));
5392
double step_value = p_new_step;
5393
if (timeline->is_using_fps()) {
5394
if (step_value != 0.0) {
5395
// A step_value should be less than or equal to 1000 to ensure that no error accumulates due to interactions with retrieving values from inner range.
5396
step_value = 1.0 / MIN(1000.0, p_new_step);
5397
}
5398
timeline->queue_redraw();
5399
}
5400
undo_redo->add_do_method(animation.ptr(), "set_step", step_value);
5401
undo_redo->add_undo_method(animation.ptr(), "set_step", animation->get_step());
5402
step->set_block_signals(true);
5403
undo_redo->commit_action();
5404
step->set_block_signals(false);
5405
emit_signal(SNAME("animation_step_changed"), step_value);
5406
}
5407
5408
void AnimationTrackEditor::_update_length(double p_new_len) {
5409
emit_signal(SNAME("animation_len_changed"), p_new_len);
5410
}
5411
5412
void AnimationTrackEditor::_dropped_track(int p_from_track, int p_to_track) {
5413
if (p_from_track == p_to_track || p_from_track == p_to_track - 1) {
5414
return;
5415
}
5416
5417
_clear_selection(true);
5418
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
5419
undo_redo->create_action(TTR("Rearrange Tracks"));
5420
undo_redo->add_do_method(animation.ptr(), "track_move_to", p_from_track, p_to_track);
5421
// Take into account that the position of the tracks that come after the one removed will change.
5422
int to_track_real = p_to_track > p_from_track ? p_to_track - 1 : p_to_track;
5423
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);
5424
undo_redo->add_do_method(this, "_track_grab_focus", to_track_real);
5425
undo_redo->add_undo_method(this, "_track_grab_focus", p_from_track);
5426
undo_redo->commit_action();
5427
}
5428
5429
void AnimationTrackEditor::_new_track_node_selected(NodePath p_path) {
5430
ERR_FAIL_NULL(root);
5431
Node *node = get_node_or_null(p_path);
5432
ERR_FAIL_NULL(node);
5433
NodePath path_to = root->get_path_to(node, true);
5434
5435
if (adding_track_type == Animation::TYPE_BLEND_SHAPE && !node->is_class("MeshInstance3D")) {
5436
EditorNode::get_singleton()->show_warning(TTR("Blend Shape tracks only apply to MeshInstance3D nodes."));
5437
return;
5438
}
5439
5440
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")) {
5441
EditorNode::get_singleton()->show_warning(TTR("Position/Rotation/Scale 3D tracks only apply to 3D-based nodes."));
5442
return;
5443
}
5444
5445
switch (adding_track_type) {
5446
case Animation::TYPE_VALUE: {
5447
adding_track_path = path_to;
5448
prop_selector->set_type_filter(Vector<Variant::Type>());
5449
prop_selector->select_property_from_instance(node);
5450
} break;
5451
case Animation::TYPE_BLEND_SHAPE: {
5452
adding_track_path = path_to;
5453
Vector<Variant::Type> filter;
5454
filter.push_back(Variant::FLOAT);
5455
prop_selector->set_type_filter(filter);
5456
prop_selector->select_property_from_instance(node);
5457
} break;
5458
case Animation::TYPE_POSITION_3D:
5459
case Animation::TYPE_ROTATION_3D:
5460
case Animation::TYPE_SCALE_3D:
5461
case Animation::TYPE_METHOD: {
5462
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
5463
undo_redo->create_action(TTR("Add Track"));
5464
undo_redo->add_do_method(animation.ptr(), "add_track", adding_track_type);
5465
undo_redo->add_do_method(animation.ptr(), "track_set_path", animation->get_track_count(), path_to);
5466
undo_redo->add_undo_method(animation.ptr(), "remove_track", animation->get_track_count());
5467
undo_redo->commit_action();
5468
5469
} break;
5470
case Animation::TYPE_BEZIER: {
5471
Vector<Variant::Type> filter;
5472
filter.push_back(Variant::INT);
5473
filter.push_back(Variant::FLOAT);
5474
filter.push_back(Variant::VECTOR2);
5475
filter.push_back(Variant::VECTOR3);
5476
filter.push_back(Variant::QUATERNION);
5477
filter.push_back(Variant::PLANE);
5478
filter.push_back(Variant::COLOR);
5479
5480
adding_track_path = path_to;
5481
prop_selector->set_type_filter(filter);
5482
prop_selector->select_property_from_instance(node);
5483
} break;
5484
case Animation::TYPE_AUDIO: {
5485
if (!node->is_class("AudioStreamPlayer") && !node->is_class("AudioStreamPlayer2D") && !node->is_class("AudioStreamPlayer3D")) {
5486
EditorNode::get_singleton()->show_warning(TTR("Audio tracks can only point to nodes of type:\n-AudioStreamPlayer\n-AudioStreamPlayer2D\n-AudioStreamPlayer3D"));
5487
return;
5488
}
5489
5490
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
5491
undo_redo->create_action(TTR("Add Track"));
5492
undo_redo->add_do_method(animation.ptr(), "add_track", adding_track_type);
5493
undo_redo->add_do_method(animation.ptr(), "track_set_path", animation->get_track_count(), path_to);
5494
undo_redo->add_undo_method(animation.ptr(), "remove_track", animation->get_track_count());
5495
undo_redo->commit_action();
5496
5497
} break;
5498
case Animation::TYPE_ANIMATION: {
5499
if (!node->is_class("AnimationPlayer")) {
5500
EditorNode::get_singleton()->show_warning(TTR("Animation tracks can only point to AnimationPlayer nodes."));
5501
return;
5502
}
5503
5504
if (node == AnimationPlayerEditor::get_singleton()->get_player()) {
5505
EditorNode::get_singleton()->show_warning(TTR("AnimationPlayer can't animate itself, only other players."));
5506
return;
5507
}
5508
5509
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
5510
undo_redo->create_action(TTR("Add Track"));
5511
undo_redo->add_do_method(animation.ptr(), "add_track", adding_track_type);
5512
undo_redo->add_do_method(animation.ptr(), "track_set_path", animation->get_track_count(), path_to);
5513
undo_redo->add_undo_method(animation.ptr(), "remove_track", animation->get_track_count());
5514
undo_redo->commit_action();
5515
5516
} break;
5517
}
5518
}
5519
5520
void AnimationTrackEditor::_add_track(int p_type) {
5521
AnimationPlayer *ap = AnimationPlayerEditor::get_singleton()->get_player();
5522
if (!ap) {
5523
ERR_FAIL_EDMSG("No AnimationPlayer is currently being edited.");
5524
}
5525
Node *root_node = ap->get_node_or_null(ap->get_root_node());
5526
if (!root_node) {
5527
EditorNode::get_singleton()->show_warning(TTR("Not possible to add a new track without a root"));
5528
return;
5529
}
5530
adding_track_type = p_type;
5531
Vector<StringName> valid_types;
5532
switch (adding_track_type) {
5533
case Animation::TYPE_BLEND_SHAPE: {
5534
// Blend Shape is a property of MeshInstance3D.
5535
valid_types.push_back(SNAME("MeshInstance3D"));
5536
} break;
5537
case Animation::TYPE_POSITION_3D:
5538
case Animation::TYPE_ROTATION_3D:
5539
case Animation::TYPE_SCALE_3D: {
5540
// 3D Properties come from nodes inheriting Node3D.
5541
valid_types.push_back(SNAME("Node3D"));
5542
} break;
5543
case Animation::TYPE_AUDIO: {
5544
valid_types.push_back(SNAME("AudioStreamPlayer"));
5545
valid_types.push_back(SNAME("AudioStreamPlayer2D"));
5546
valid_types.push_back(SNAME("AudioStreamPlayer3D"));
5547
} break;
5548
case Animation::TYPE_ANIMATION: {
5549
valid_types.push_back(SNAME("AnimationPlayer"));
5550
} break;
5551
}
5552
pick_track->set_valid_types(valid_types);
5553
pick_track->popup_scenetree_dialog(nullptr, root_node);
5554
pick_track->get_filter_line_edit()->clear();
5555
pick_track->get_filter_line_edit()->grab_focus();
5556
}
5557
5558
void AnimationTrackEditor::_fetch_value_track_options(const NodePath &p_path, Animation::UpdateMode *r_update_mode, Animation::InterpolationType *r_interpolation_type, bool *r_loop_wrap) {
5559
AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();
5560
if (player->has_animation(SceneStringName(RESET))) {
5561
Ref<Animation> reset_anim = player->get_animation(SceneStringName(RESET));
5562
int rt = reset_anim->find_track(p_path, Animation::TrackType::TYPE_VALUE);
5563
if (rt >= 0) {
5564
*r_update_mode = reset_anim->value_track_get_update_mode(rt);
5565
*r_interpolation_type = reset_anim->track_get_interpolation_type(rt);
5566
*r_loop_wrap = reset_anim->track_get_interpolation_loop_wrap(rt);
5567
return;
5568
}
5569
rt = reset_anim->find_track(p_path, Animation::TrackType::TYPE_BEZIER);
5570
if (rt >= 0) {
5571
*r_interpolation_type = reset_anim->track_get_interpolation_type(rt);
5572
*r_loop_wrap = reset_anim->track_get_interpolation_loop_wrap(rt);
5573
return;
5574
}
5575
}
5576
5577
// Hack.
5578
NodePath np;
5579
animation->add_track(Animation::TYPE_VALUE);
5580
animation->track_set_path(animation->get_track_count() - 1, p_path);
5581
PropertyInfo h = _find_hint_for_track(animation->get_track_count() - 1, np);
5582
animation->remove_track(animation->get_track_count() - 1); // Hack.
5583
switch (h.type) {
5584
case Variant::FLOAT:
5585
case Variant::VECTOR2:
5586
case Variant::RECT2:
5587
case Variant::VECTOR3:
5588
case Variant::TRANSFORM2D:
5589
case Variant::VECTOR4:
5590
case Variant::PLANE:
5591
case Variant::QUATERNION:
5592
case Variant::AABB:
5593
case Variant::BASIS:
5594
case Variant::TRANSFORM3D:
5595
case Variant::PROJECTION:
5596
case Variant::COLOR:
5597
case Variant::PACKED_FLOAT32_ARRAY:
5598
case Variant::PACKED_FLOAT64_ARRAY:
5599
case Variant::PACKED_VECTOR2_ARRAY:
5600
case Variant::PACKED_VECTOR3_ARRAY:
5601
case Variant::PACKED_COLOR_ARRAY:
5602
case Variant::PACKED_VECTOR4_ARRAY: {
5603
*r_update_mode = Animation::UPDATE_CONTINUOUS;
5604
} break;
5605
default: {
5606
}
5607
}
5608
}
5609
5610
void AnimationTrackEditor::_new_track_property_selected(const String &p_name) {
5611
String full_path = String(adding_track_path) + ":" + p_name;
5612
5613
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
5614
5615
Animation::UpdateMode update_mode = Animation::UPDATE_DISCRETE;
5616
Animation::InterpolationType interp_type = Animation::INTERPOLATION_LINEAR;
5617
bool loop_wrap = true;
5618
_fetch_value_track_options(full_path, &update_mode, &interp_type, &loop_wrap);
5619
if (adding_track_type == Animation::TYPE_BEZIER) {
5620
Vector<String> subindices;
5621
{
5622
// Hack.
5623
NodePath np;
5624
animation->add_track(Animation::TYPE_VALUE);
5625
animation->track_set_path(animation->get_track_count() - 1, full_path);
5626
PropertyInfo h = _find_hint_for_track(animation->get_track_count() - 1, np);
5627
animation->remove_track(animation->get_track_count() - 1); // Hack.
5628
bool valid;
5629
subindices = _get_bezier_subindices_for_type(h.type, &valid);
5630
if (!valid) {
5631
EditorNode::get_singleton()->show_warning(TTR("Invalid track for Bezier (no suitable sub-properties)"));
5632
return;
5633
}
5634
}
5635
5636
undo_redo->create_action(TTR("Add Bezier Track"));
5637
int base_track = animation->get_track_count();
5638
for (int i = 0; i < subindices.size(); i++) {
5639
int track_idx = base_track + i;
5640
undo_redo->add_do_method(animation.ptr(), "add_track", adding_track_type);
5641
undo_redo->add_do_method(animation.ptr(), "track_set_path", track_idx, full_path + subindices[i]);
5642
undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_type", track_idx, interp_type);
5643
undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_loop_wrap", track_idx, loop_wrap);
5644
undo_redo->add_undo_method(animation.ptr(), "remove_track", base_track);
5645
}
5646
undo_redo->commit_action();
5647
} else {
5648
bool is_blend_shape = adding_track_type == Animation::TYPE_BLEND_SHAPE;
5649
if (is_blend_shape) {
5650
PackedStringArray split = p_name.split("/");
5651
if (!split.is_empty()) {
5652
full_path = String(adding_track_path) + ":" + split[split.size() - 1];
5653
}
5654
}
5655
undo_redo->create_action(TTR("Add Track"));
5656
undo_redo->add_do_method(animation.ptr(), "add_track", adding_track_type);
5657
undo_redo->add_do_method(animation.ptr(), "track_set_path", animation->get_track_count(), full_path);
5658
undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_type", animation->get_track_count(), interp_type);
5659
undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_loop_wrap", animation->get_track_count(), loop_wrap);
5660
if (!is_blend_shape) {
5661
undo_redo->add_do_method(animation.ptr(), "value_track_set_update_mode", animation->get_track_count(), update_mode);
5662
}
5663
undo_redo->add_undo_method(animation.ptr(), "remove_track", animation->get_track_count());
5664
undo_redo->commit_action();
5665
}
5666
}
5667
5668
void AnimationTrackEditor::_timeline_value_changed(double) {
5669
timeline->update_play_position();
5670
5671
_redraw_tracks();
5672
for (int i = 0; i < track_edits.size(); i++) {
5673
track_edits[i]->update_play_position();
5674
}
5675
_redraw_groups();
5676
5677
bezier_edit->queue_redraw();
5678
bezier_edit->update_play_position();
5679
5680
marker_edit->update_play_position();
5681
}
5682
5683
int AnimationTrackEditor::_get_track_selected() {
5684
for (int i = 0; i < track_edits.size(); i++) {
5685
if (track_edits[i]->has_focus()) {
5686
return i;
5687
}
5688
}
5689
5690
return -1;
5691
}
5692
5693
void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) {
5694
ERR_FAIL_INDEX(p_track, animation->get_track_count());
5695
5696
if (snap_keys->is_pressed() && step->get_value() != 0) {
5697
p_ofs = snap_time(p_ofs);
5698
}
5699
while (animation->track_find_key(p_track, p_ofs, Animation::FIND_MODE_APPROX) != -1) { // Make sure insertion point is valid.
5700
p_ofs += SECOND_DECIMAL;
5701
}
5702
5703
Node *node = root->get_node_or_null(animation->track_get_path(p_track));
5704
if (!node) {
5705
EditorNode::get_singleton()->show_warning(TTR("Track path is invalid, so can't add a key."));
5706
return;
5707
}
5708
5709
// Special handling for this one.
5710
if (animation->track_get_type(p_track) == Animation::TYPE_METHOD) {
5711
method_selector->select_method_from_instance(node);
5712
5713
insert_key_from_track_call_ofs = p_ofs;
5714
insert_key_from_track_call_track = p_track;
5715
return;
5716
}
5717
5718
InsertData id;
5719
id.path = animation->track_get_path(p_track);
5720
id.advance = false;
5721
id.track_idx = p_track;
5722
id.type = animation->track_get_type(p_track);
5723
// TRANSLATORS: This describes the target of new animation track, will be inserted into another string.
5724
id.query = vformat(TTR("node '%s'"), node->get_name());
5725
id.time = p_ofs;
5726
// id.value is filled in each case handled below.
5727
5728
switch (animation->track_get_type(p_track)) {
5729
case Animation::TYPE_POSITION_3D: {
5730
Node3D *base = Object::cast_to<Node3D>(node);
5731
5732
if (!base) {
5733
EditorNode::get_singleton()->show_warning(TTR("Track is not of type Node3D, can't insert key"));
5734
return;
5735
}
5736
5737
id.value = base->get_position();
5738
} break;
5739
case Animation::TYPE_ROTATION_3D: {
5740
Node3D *base = Object::cast_to<Node3D>(node);
5741
5742
if (!base) {
5743
EditorNode::get_singleton()->show_warning(TTR("Track is not of type Node3D, can't insert key"));
5744
return;
5745
}
5746
5747
id.value = base->get_transform().basis.operator Quaternion();
5748
} break;
5749
case Animation::TYPE_SCALE_3D: {
5750
Node3D *base = Object::cast_to<Node3D>(node);
5751
5752
if (!base) {
5753
EditorNode::get_singleton()->show_warning(TTR("Track is not of type Node3D, can't insert key"));
5754
return;
5755
}
5756
5757
id.value = base->get_scale();
5758
} break;
5759
case Animation::TYPE_BLEND_SHAPE: {
5760
MeshInstance3D *base = Object::cast_to<MeshInstance3D>(node);
5761
5762
if (!base) {
5763
EditorNode::get_singleton()->show_warning(TTR("Track is not of type MeshInstance3D, can't insert key"));
5764
return;
5765
}
5766
5767
id.value = base->get_blend_shape_value(base->find_blend_shape_by_name(id.path.get_subname(0)));
5768
} break;
5769
case Animation::TYPE_VALUE: {
5770
NodePath bp;
5771
_find_hint_for_track(p_track, bp, &id.value);
5772
} break;
5773
case Animation::TYPE_METHOD: {
5774
Node *base = root->get_node_or_null(animation->track_get_path(p_track));
5775
ERR_FAIL_NULL(base);
5776
5777
method_selector->select_method_from_instance(base);
5778
5779
insert_key_from_track_call_ofs = p_ofs;
5780
insert_key_from_track_call_track = p_track;
5781
5782
} break;
5783
case Animation::TYPE_BEZIER: {
5784
NodePath bp;
5785
Variant value;
5786
_find_hint_for_track(p_track, bp, &value);
5787
id.value = animation->make_default_bezier_key(value);
5788
} break;
5789
case Animation::TYPE_AUDIO: {
5790
Dictionary ak;
5791
ak["stream"] = Ref<Resource>();
5792
ak["start_offset"] = 0;
5793
ak["end_offset"] = 0;
5794
5795
id.value = ak;
5796
} break;
5797
case Animation::TYPE_ANIMATION: {
5798
id.value = StringName("[stop]");
5799
} break;
5800
default: {
5801
// All track types should be handled by now.
5802
DEV_ASSERT(false);
5803
}
5804
}
5805
5806
_query_insert(id);
5807
}
5808
5809
void AnimationTrackEditor::_add_method_key(const String &p_method) {
5810
if (!root->has_node(animation->track_get_path(insert_key_from_track_call_track))) {
5811
EditorNode::get_singleton()->show_warning(TTR("Track path is invalid, so can't add a method key."));
5812
return;
5813
}
5814
Node *base = root->get_node_or_null(animation->track_get_path(insert_key_from_track_call_track));
5815
ERR_FAIL_NULL(base);
5816
5817
List<MethodInfo> minfo;
5818
base->get_method_list(&minfo);
5819
5820
for (const MethodInfo &E : minfo) {
5821
if (E.name == p_method) {
5822
Dictionary d;
5823
d["method"] = p_method;
5824
Array params;
5825
int64_t first_defarg = E.arguments.size() - E.default_arguments.size();
5826
5827
for (int64_t i = 0; i < E.arguments.size(); ++i) {
5828
if (i >= first_defarg) {
5829
Variant arg = E.default_arguments[i - first_defarg];
5830
params.push_back(arg);
5831
} else {
5832
Callable::CallError ce;
5833
Variant arg;
5834
Variant::construct(E.arguments[i].type, arg, nullptr, 0, ce);
5835
params.push_back(arg);
5836
}
5837
}
5838
d["args"] = params;
5839
5840
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
5841
undo_redo->create_action(TTR("Add Method Track Key"));
5842
undo_redo->add_do_method(animation.ptr(), "track_insert_key", insert_key_from_track_call_track, insert_key_from_track_call_ofs, d);
5843
undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
5844
undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", insert_key_from_track_call_track, insert_key_from_track_call_ofs);
5845
undo_redo->commit_action();
5846
5847
return;
5848
}
5849
}
5850
5851
EditorNode::get_singleton()->show_warning(TTR("Method not found in object:") + " " + p_method);
5852
}
5853
5854
void AnimationTrackEditor::_key_selected(int p_key, bool p_single, int p_track) {
5855
ERR_FAIL_INDEX(p_track, animation->get_track_count());
5856
ERR_FAIL_INDEX(p_key, animation->track_get_key_count(p_track));
5857
5858
SelectedKey sk;
5859
sk.key = p_key;
5860
sk.track = p_track;
5861
5862
if (p_single) {
5863
_clear_selection();
5864
}
5865
5866
KeyInfo ki;
5867
ki.pos = animation->track_get_key_time(p_track, p_key);
5868
selection[sk] = ki;
5869
5870
_redraw_tracks();
5871
_update_key_edit();
5872
5873
marker_edit->_clear_selection(marker_edit->is_selection_active());
5874
}
5875
5876
void AnimationTrackEditor::_key_deselected(int p_key, int p_track) {
5877
ERR_FAIL_INDEX(p_track, animation->get_track_count());
5878
ERR_FAIL_INDEX(p_key, animation->track_get_key_count(p_track));
5879
5880
SelectedKey sk;
5881
sk.key = p_key;
5882
sk.track = p_track;
5883
5884
selection.erase(sk);
5885
5886
_redraw_tracks();
5887
_update_key_edit();
5888
}
5889
5890
void AnimationTrackEditor::_move_selection_begin() {
5891
moving_selection = true;
5892
moving_selection_offset = 0;
5893
}
5894
5895
void AnimationTrackEditor::_move_selection(float p_offset) {
5896
moving_selection_offset = p_offset;
5897
_redraw_tracks();
5898
}
5899
5900
struct _AnimMoveRestore {
5901
int track = 0;
5902
float time = 0;
5903
Variant key;
5904
float transition = 0;
5905
};
5906
// Used for undo/redo.
5907
5908
void AnimationTrackEditor::_clear_key_edit() {
5909
if (key_edit) {
5910
// If key edit is the object being inspected, remove it first.
5911
if (InspectorDock::get_inspector_singleton()->get_edited_object() == key_edit) {
5912
EditorNode::get_singleton()->push_item(nullptr);
5913
}
5914
5915
// Then actually delete it.
5916
memdelete(key_edit);
5917
key_edit = nullptr;
5918
}
5919
5920
if (multi_key_edit) {
5921
if (InspectorDock::get_inspector_singleton()->get_edited_object() == multi_key_edit) {
5922
EditorNode::get_singleton()->push_item(nullptr);
5923
}
5924
5925
memdelete(multi_key_edit);
5926
multi_key_edit = nullptr;
5927
}
5928
}
5929
5930
void AnimationTrackEditor::_clear_selection(bool p_update) {
5931
selection.clear();
5932
5933
if (p_update) {
5934
_redraw_tracks();
5935
}
5936
5937
_clear_key_edit();
5938
}
5939
5940
void AnimationTrackEditor::_update_key_edit() {
5941
_clear_key_edit();
5942
if (animation.is_null()) {
5943
return;
5944
}
5945
5946
if (selection.size() == 1) {
5947
key_edit = memnew(AnimationTrackKeyEdit);
5948
key_edit->animation = animation;
5949
key_edit->animation_read_only = read_only;
5950
key_edit->track = selection.front()->key().track;
5951
key_edit->use_fps = timeline->is_using_fps();
5952
key_edit->editor = this;
5953
5954
int key_id = selection.front()->key().key;
5955
if (key_id >= animation->track_get_key_count(key_edit->track)) {
5956
_clear_key_edit();
5957
return; // Probably in the process of rearranging the keys.
5958
}
5959
float ofs = animation->track_get_key_time(key_edit->track, key_id);
5960
key_edit->key_ofs = ofs;
5961
key_edit->root_path = root;
5962
5963
NodePath np;
5964
key_edit->hint = _find_hint_for_track(key_edit->track, np);
5965
key_edit->base = np;
5966
5967
EditorNode::get_singleton()->push_item(key_edit);
5968
} else if (selection.size() > 1) {
5969
multi_key_edit = memnew(AnimationMultiTrackKeyEdit);
5970
multi_key_edit->animation = animation;
5971
multi_key_edit->animation_read_only = read_only;
5972
multi_key_edit->editor = this;
5973
5974
RBMap<int, List<float>> key_ofs_map;
5975
RBMap<int, NodePath> base_map;
5976
int first_track = -1;
5977
for (const KeyValue<SelectedKey, KeyInfo> &E : selection) {
5978
int track = E.key.track;
5979
if (first_track < 0) {
5980
first_track = track;
5981
}
5982
5983
if (!key_ofs_map.has(track)) {
5984
key_ofs_map[track] = List<float>();
5985
base_map[track] = NodePath();
5986
}
5987
5988
int key_id = E.key.key;
5989
if (key_id >= animation->track_get_key_count(track)) {
5990
_clear_key_edit();
5991
return; // Probably in the process of rearranging the keys.
5992
}
5993
key_ofs_map[track].push_back(animation->track_get_key_time(track, E.key.key));
5994
}
5995
multi_key_edit->key_ofs_map = key_ofs_map;
5996
multi_key_edit->base_map = base_map;
5997
multi_key_edit->hint = _find_hint_for_track(first_track, base_map[first_track]);
5998
multi_key_edit->use_fps = timeline->is_using_fps();
5999
multi_key_edit->root_path = root;
6000
6001
EditorNode::get_singleton()->push_item(multi_key_edit);
6002
}
6003
}
6004
6005
void AnimationTrackEditor::_clear_selection_for_anim(const Ref<Animation> &p_anim) {
6006
if (animation != p_anim) {
6007
return;
6008
}
6009
6010
_clear_selection();
6011
}
6012
6013
void AnimationTrackEditor::_select_at_anim(const Ref<Animation> &p_anim, int p_track, float p_pos) {
6014
if (animation != p_anim) {
6015
return;
6016
}
6017
6018
int idx = animation->track_find_key(p_track, p_pos, Animation::FIND_MODE_APPROX);
6019
ERR_FAIL_COND(idx < 0);
6020
6021
SelectedKey sk;
6022
sk.track = p_track;
6023
sk.key = idx;
6024
KeyInfo ki;
6025
ki.pos = p_pos;
6026
6027
selection.insert(sk, ki);
6028
_update_key_edit();
6029
6030
marker_edit->_clear_selection(marker_edit->is_selection_active());
6031
}
6032
6033
void AnimationTrackEditor::_move_selection_commit() {
6034
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
6035
undo_redo->create_action(TTR("Animation Move Keys"));
6036
6037
List<_AnimMoveRestore> to_restore;
6038
6039
float motion = moving_selection_offset;
6040
// 1 - remove the keys.
6041
for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
6042
undo_redo->add_do_method(animation.ptr(), "track_remove_key", E->key().track, E->key().key);
6043
}
6044
// 2 - Remove overlapped keys.
6045
for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
6046
float newtime = E->get().pos + motion;
6047
int idx = animation->track_find_key(E->key().track, newtime, Animation::FIND_MODE_APPROX);
6048
if (idx == -1) {
6049
continue;
6050
}
6051
SelectedKey sk;
6052
sk.key = idx;
6053
sk.track = E->key().track;
6054
if (selection.has(sk)) {
6055
continue; // Already in selection, don't save.
6056
}
6057
6058
undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_time", E->key().track, newtime);
6059
_AnimMoveRestore amr;
6060
6061
amr.key = animation->track_get_key_value(E->key().track, idx);
6062
amr.track = E->key().track;
6063
amr.time = newtime;
6064
amr.transition = animation->track_get_key_transition(E->key().track, idx);
6065
6066
to_restore.push_back(amr);
6067
}
6068
6069
// 3 - Move the keys (Reinsert them).
6070
for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
6071
float newpos = E->get().pos + motion;
6072
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));
6073
}
6074
6075
// 4 - (Undo) Remove inserted keys.
6076
for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
6077
float newpos = E->get().pos + motion;
6078
undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", E->key().track, newpos);
6079
}
6080
6081
// 5 - (Undo) Reinsert keys.
6082
for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
6083
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));
6084
}
6085
6086
// 6 - (Undo) Reinsert overlapped keys.
6087
for (_AnimMoveRestore &amr : to_restore) {
6088
undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, amr.transition);
6089
}
6090
6091
undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);
6092
undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
6093
6094
// 7 - Reselect.
6095
for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
6096
float oldpos = E->get().pos;
6097
float newpos = oldpos + motion;
6098
6099
undo_redo->add_do_method(this, "_select_at_anim", animation, E->key().track, newpos);
6100
undo_redo->add_undo_method(this, "_select_at_anim", animation, E->key().track, oldpos);
6101
}
6102
6103
moving_selection = false;
6104
undo_redo->add_do_method(this, "_redraw_tracks");
6105
undo_redo->add_undo_method(this, "_redraw_tracks");
6106
6107
// Update key frame.
6108
AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
6109
if (ape) {
6110
undo_redo->add_do_method(ape, "_animation_update_key_frame");
6111
undo_redo->add_undo_method(ape, "_animation_update_key_frame");
6112
}
6113
6114
undo_redo->commit_action();
6115
}
6116
6117
void AnimationTrackEditor::_move_selection_cancel() {
6118
moving_selection = false;
6119
_redraw_tracks();
6120
}
6121
6122
bool AnimationTrackEditor::is_moving_selection() const {
6123
return moving_selection;
6124
}
6125
6126
float AnimationTrackEditor::get_moving_selection_offset() const {
6127
return moving_selection_offset;
6128
}
6129
6130
void AnimationTrackEditor::_box_selection_draw() {
6131
const Rect2 selection_rect = Rect2(Point2(), box_selection->get_size());
6132
box_selection->draw_rect(selection_rect, get_theme_color(SNAME("box_selection_fill_color"), EditorStringName(Editor)));
6133
box_selection->draw_rect(selection_rect, get_theme_color(SNAME("box_selection_stroke_color"), EditorStringName(Editor)), false, Math::round(EDSCALE));
6134
}
6135
6136
void AnimationTrackEditor::_scroll_input(const Ref<InputEvent> &p_event) {
6137
if (!box_selecting) {
6138
if (panner->gui_input(p_event, scroll->get_global_rect())) {
6139
scroll->accept_event();
6140
return;
6141
}
6142
}
6143
6144
Ref<InputEventMouseButton> mb = p_event;
6145
6146
if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) {
6147
if (mb->is_pressed()) {
6148
box_selecting = true;
6149
box_selecting_from = scroll->get_global_transform().xform(mb->get_position());
6150
box_select_rect = Rect2();
6151
} else if (box_selecting) {
6152
if (box_selection->is_visible_in_tree()) {
6153
// Only if moved.
6154
for (int i = 0; i < track_edits.size(); i++) {
6155
Rect2 local_rect = box_select_rect;
6156
local_rect.position -= track_edits[i]->get_global_position();
6157
track_edits[i]->append_to_selection(local_rect, mb->is_command_or_control_pressed());
6158
}
6159
6160
if (_get_track_selected() == -1 && track_edits.size() > 0) { // Minimal hack to make shortcuts work.
6161
track_edits[track_edits.size() - 1]->grab_focus();
6162
}
6163
} else if (!mb->is_command_or_control_pressed() && !mb->is_shift_pressed()) {
6164
_clear_selection(true); // Clear it.
6165
}
6166
6167
box_selection->hide();
6168
box_selecting = false;
6169
}
6170
}
6171
6172
Ref<InputEventMouseMotion> mm = p_event;
6173
6174
if (mm.is_valid() && box_selecting) {
6175
if (!mm->get_button_mask().has_flag(MouseButtonMask::LEFT)) {
6176
// No longer.
6177
box_selection->hide();
6178
box_selecting = false;
6179
return;
6180
}
6181
6182
if (!box_selection->is_visible_in_tree()) {
6183
if (!mm->is_command_or_control_pressed() && !mm->is_shift_pressed()) {
6184
_clear_selection(true);
6185
}
6186
box_selection->show();
6187
}
6188
6189
Vector2 from = box_selecting_from;
6190
Vector2 to = scroll->get_global_transform().xform(mm->get_position());
6191
6192
box_selecting_to = to;
6193
6194
if (from.x > to.x) {
6195
SWAP(from.x, to.x);
6196
}
6197
6198
if (from.y > to.y) {
6199
SWAP(from.y, to.y);
6200
}
6201
6202
Rect2 rect(from, to - from);
6203
box_selection->set_rect(Rect2(from - scroll->get_global_position(), rect.get_size()));
6204
box_select_rect = rect;
6205
}
6206
}
6207
6208
void AnimationTrackEditor::_toggle_bezier_edit() {
6209
if (bezier_edit->is_visible()) {
6210
_cancel_bezier_edit();
6211
} else {
6212
int track_count = animation->get_track_count();
6213
for (int i = 0; i < track_count; ++i) {
6214
if (animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) {
6215
_bezier_edit(i);
6216
return;
6217
}
6218
}
6219
}
6220
}
6221
6222
void AnimationTrackEditor::_scroll_changed(const Vector2 &p_val) {
6223
if (box_selecting) {
6224
const Vector2 scroll_difference = p_val - prev_scroll_position;
6225
6226
Vector2 from = box_selecting_from - scroll_difference;
6227
Vector2 to = box_selecting_to;
6228
6229
box_selecting_from = from;
6230
6231
if (from.x > to.x) {
6232
SWAP(from.x, to.x);
6233
}
6234
6235
if (from.y > to.y) {
6236
SWAP(from.y, to.y);
6237
}
6238
6239
Rect2 rect(from, to - from);
6240
box_selection->set_rect(Rect2(from - scroll->get_global_position(), rect.get_size()));
6241
box_select_rect = rect;
6242
}
6243
6244
prev_scroll_position = p_val;
6245
}
6246
6247
void AnimationTrackEditor::_v_scroll_changed(float p_val) {
6248
_scroll_changed(Vector2(prev_scroll_position.x, p_val));
6249
}
6250
6251
void AnimationTrackEditor::_h_scroll_changed(float p_val) {
6252
_scroll_changed(Vector2(p_val, prev_scroll_position.y));
6253
}
6254
6255
void AnimationTrackEditor::_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event) {
6256
Ref<InputEventWithModifiers> iewm = p_event;
6257
if (iewm.is_valid() && iewm->is_alt_pressed()) {
6258
if (p_scroll_vec.x < 0 || p_scroll_vec.y < 0) {
6259
goto_prev_step(true);
6260
} else {
6261
goto_next_step(true);
6262
}
6263
} else {
6264
timeline->set_value(timeline->get_value() - p_scroll_vec.x / timeline->get_zoom_scale());
6265
scroll->set_v_scroll(scroll->get_v_scroll() - p_scroll_vec.y);
6266
}
6267
}
6268
6269
void AnimationTrackEditor::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event) {
6270
timeline->_zoom_callback(p_zoom_factor, p_origin, p_event);
6271
}
6272
6273
void AnimationTrackEditor::_cancel_bezier_edit() {
6274
bezier_edit->hide();
6275
box_selection_container->show();
6276
bezier_edit_icon->set_pressed(false);
6277
auto_fit->show();
6278
auto_fit_bezier->hide();
6279
}
6280
6281
void AnimationTrackEditor::_bezier_edit(int p_for_track) {
6282
_clear_selection(); // Bezier probably wants to use a separate selection mode.
6283
bezier_edit->set_root(root);
6284
bezier_edit->set_animation_and_track(animation, p_for_track, read_only);
6285
box_selection_container->hide();
6286
bezier_edit->show();
6287
auto_fit->hide();
6288
auto_fit_bezier->show();
6289
// Search everything within the track and curve - edit it.
6290
}
6291
6292
void 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) {
6293
ERR_FAIL_NULL(p_anim);
6294
p_anim->bezier_track_set_key_handle_mode(p_track, p_index, p_mode, p_set_mode);
6295
}
6296
6297
void AnimationTrackEditor::_bezier_track_set_key_handle_mode_at_time(Animation *p_anim, int p_track, float p_time, Animation::HandleMode p_mode, Animation::HandleSetMode p_set_mode) {
6298
ERR_FAIL_NULL(p_anim);
6299
int index = p_anim->track_find_key(p_track, p_time, Animation::FIND_MODE_APPROX);
6300
ERR_FAIL_COND(index < 0);
6301
_bezier_track_set_key_handle_mode(p_anim, p_track, index, p_mode, p_set_mode);
6302
}
6303
6304
void AnimationTrackEditor::_anim_duplicate_keys(float p_ofs, bool p_ofs_valid, int p_track) {
6305
if (selection.size() && animation.is_valid()) {
6306
int top_track = 0x7FFFFFFF;
6307
float top_time = 1e10;
6308
for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
6309
const SelectedKey &sk = E->key();
6310
6311
float t = animation->track_get_key_time(sk.track, sk.key);
6312
if (t < top_time) {
6313
top_time = t;
6314
}
6315
if (sk.track < top_track) {
6316
top_track = sk.track;
6317
}
6318
}
6319
ERR_FAIL_COND(top_track == 0x7FFFFFFF || top_time == 1e10);
6320
6321
int start_track = p_track;
6322
if (p_track == -1) { // Duplicating from shortcut or Edit menu.
6323
bool is_valid_track_selected = _get_track_selected() >= 0 && _get_track_selected() < animation->get_track_count();
6324
start_track = is_valid_track_selected ? _get_track_selected() : top_track;
6325
}
6326
6327
bool all_compatible = true;
6328
6329
for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
6330
const SelectedKey &sk = E->key();
6331
int dst_track = sk.track + (start_track - top_track);
6332
6333
if (dst_track < 0 || dst_track >= animation->get_track_count()) {
6334
all_compatible = false;
6335
break;
6336
}
6337
6338
Variant::Type value_type = animation->track_get_key_value(sk.track, sk.key).get_type();
6339
Animation::TrackType track_type = animation->track_get_type(sk.track);
6340
if (!_is_track_compatible(dst_track, value_type, track_type)) {
6341
all_compatible = false;
6342
break;
6343
}
6344
}
6345
6346
ERR_FAIL_COND_MSG(!all_compatible, "Duplicate failed: Not all animation keys were compatible with their target tracks");
6347
6348
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
6349
undo_redo->create_action(TTR("Animation Duplicate Keys"));
6350
6351
List<Pair<int, float>> new_selection_values;
6352
6353
for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
6354
const SelectedKey &sk = E->key();
6355
6356
float t = animation->track_get_key_time(sk.track, sk.key);
6357
float insert_pos = p_ofs_valid ? p_ofs : timeline->get_play_position();
6358
6359
if (p_ofs_valid) {
6360
if (snap_keys->is_pressed() && step->get_value() != 0) {
6361
insert_pos = snap_time(insert_pos);
6362
}
6363
}
6364
6365
float dst_time = t + (insert_pos - top_time);
6366
int dst_track = sk.track + (start_track - top_track);
6367
6368
if (dst_track < 0 || dst_track >= animation->get_track_count()) {
6369
continue;
6370
}
6371
6372
int existing_idx = animation->track_find_key(dst_track, dst_time, Animation::FIND_MODE_APPROX);
6373
6374
Variant value = animation->track_get_key_value(sk.track, sk.key);
6375
bool key_is_bezier = animation->track_get_type(sk.track) == Animation::TYPE_BEZIER;
6376
bool track_is_bezier = animation->track_get_type(dst_track) == Animation::TYPE_BEZIER;
6377
if (key_is_bezier && !track_is_bezier) {
6378
value = AnimationBezierTrackEdit::get_bezier_key_value(value);
6379
} else if (!key_is_bezier && track_is_bezier) {
6380
value = animation->make_default_bezier_key(value);
6381
}
6382
6383
undo_redo->add_do_method(animation.ptr(), "track_insert_key", dst_track, dst_time, value, animation->track_get_key_transition(E->key().track, E->key().key));
6384
undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", dst_track, dst_time);
6385
6386
Pair<int, float> p;
6387
p.first = dst_track;
6388
p.second = dst_time;
6389
new_selection_values.push_back(p);
6390
6391
if (existing_idx != -1) {
6392
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));
6393
}
6394
}
6395
6396
undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);
6397
undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
6398
6399
// Reselect duplicated.
6400
RBMap<SelectedKey, KeyInfo> new_selection;
6401
for (const Pair<int, float> &E : new_selection_values) {
6402
undo_redo->add_do_method(this, "_select_at_anim", animation, E.first, E.second);
6403
}
6404
for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
6405
undo_redo->add_undo_method(this, "_select_at_anim", animation, E->key().track, E->get().pos);
6406
}
6407
6408
undo_redo->add_do_method(this, "_redraw_tracks");
6409
undo_redo->add_undo_method(this, "_redraw_tracks");
6410
undo_redo->commit_action();
6411
}
6412
}
6413
6414
void AnimationTrackEditor::_anim_copy_keys(bool p_cut) {
6415
if (is_selection_active() && animation.is_valid()) {
6416
int top_track = 0x7FFFFFFF;
6417
float top_time = 1e10;
6418
6419
for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
6420
const SelectedKey &sk = E->key();
6421
6422
float t = animation->track_get_key_time(sk.track, sk.key);
6423
if (t < top_time) {
6424
top_time = t;
6425
}
6426
if (sk.track < top_track) {
6427
top_track = sk.track;
6428
}
6429
}
6430
6431
ERR_FAIL_COND(top_track == 0x7FFFFFFF || top_time == 1e10);
6432
6433
_set_key_clipboard(top_track, top_time, selection);
6434
6435
if (p_cut) {
6436
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
6437
undo_redo->create_action(TTR("Animation Cut Keys"), UndoRedo::MERGE_DISABLE, animation.ptr());
6438
undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);
6439
undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
6440
for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
6441
int track_idx = E->key().track;
6442
int key_idx = E->key().key;
6443
float time = E->value().pos;
6444
undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_time", track_idx, time);
6445
undo_redo->add_undo_method(animation.ptr(), "track_insert_key", track_idx, time, animation->track_get_key_value(track_idx, key_idx), animation->track_get_key_transition(track_idx, key_idx));
6446
}
6447
for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
6448
undo_redo->add_undo_method(this, "_select_at_anim", animation, E->key().track, E->value().pos);
6449
}
6450
undo_redo->commit_action();
6451
}
6452
}
6453
}
6454
6455
void AnimationTrackEditor::_set_key_clipboard(int p_top_track, float p_top_time, RBMap<SelectedKey, KeyInfo> &p_keys) {
6456
key_clipboard.keys.clear();
6457
key_clipboard.top_track = p_top_track;
6458
for (RBMap<SelectedKey, KeyInfo>::Element *E = p_keys.back(); E; E = E->prev()) {
6459
KeyClipboard::Key k;
6460
k.value = animation->track_get_key_value(E->key().track, E->key().key);
6461
k.transition = animation->track_get_key_transition(E->key().track, E->key().key);
6462
k.time = E->value().pos - p_top_time;
6463
k.track = E->key().track - p_top_track;
6464
k.track_type = animation->track_get_type(E->key().track);
6465
6466
key_clipboard.keys.push_back(k);
6467
}
6468
}
6469
6470
void AnimationTrackEditor::_anim_paste_keys(float p_ofs, bool p_ofs_valid, int p_track) {
6471
if (is_key_clipboard_active() && animation.is_valid()) {
6472
int start_track = p_track;
6473
if (p_track == -1) { // Pasting from shortcut or Edit menu.
6474
bool is_valid_track_selected = _get_track_selected() >= 0 && _get_track_selected() < animation->get_track_count();
6475
start_track = is_valid_track_selected ? _get_track_selected() : key_clipboard.top_track;
6476
}
6477
6478
bool all_compatible = true;
6479
6480
for (int i = 0; i < key_clipboard.keys.size(); i++) {
6481
const KeyClipboard::Key key = key_clipboard.keys[i];
6482
6483
int dst_track = key.track + start_track;
6484
6485
if (dst_track < 0 || dst_track >= animation->get_track_count()) {
6486
all_compatible = false;
6487
break;
6488
}
6489
6490
if (!_is_track_compatible(dst_track, key.value.get_type(), key.track_type)) {
6491
all_compatible = false;
6492
break;
6493
}
6494
}
6495
6496
ERR_FAIL_COND_MSG(!all_compatible, "Paste failed: Not all animation keys were compatible with their target tracks");
6497
6498
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
6499
undo_redo->create_action(TTR("Animation Paste Keys"));
6500
List<Pair<int, float>> new_selection_values;
6501
6502
for (int i = 0; i < key_clipboard.keys.size(); i++) {
6503
const KeyClipboard::Key key = key_clipboard.keys[i];
6504
6505
float insert_pos = p_ofs_valid ? p_ofs : timeline->get_play_position();
6506
6507
if (p_ofs_valid) {
6508
if (snap_keys->is_pressed() && step->get_value() != 0) {
6509
insert_pos = snap_time(insert_pos);
6510
}
6511
}
6512
6513
float dst_time = key.time + insert_pos;
6514
int dst_track = key.track + start_track;
6515
6516
int existing_idx = animation->track_find_key(dst_track, dst_time, Animation::FIND_MODE_APPROX);
6517
6518
Variant value = key.value;
6519
bool key_is_bezier = key.track_type == Animation::TYPE_BEZIER;
6520
bool track_is_bezier = animation->track_get_type(dst_track) == Animation::TYPE_BEZIER;
6521
if (key_is_bezier && !track_is_bezier) {
6522
value = AnimationBezierTrackEdit::get_bezier_key_value(value);
6523
} else if (!key_is_bezier && track_is_bezier) {
6524
value = animation->make_default_bezier_key(value);
6525
}
6526
6527
undo_redo->add_do_method(animation.ptr(), "track_insert_key", dst_track, dst_time, value, key.transition);
6528
undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", dst_track, dst_time);
6529
6530
Pair<int, float> p;
6531
p.first = dst_track;
6532
p.second = dst_time;
6533
new_selection_values.push_back(p);
6534
6535
if (existing_idx != -1) {
6536
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));
6537
}
6538
}
6539
6540
undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);
6541
undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
6542
6543
// Reselect pasted.
6544
for (const Pair<int, float> &E : new_selection_values) {
6545
undo_redo->add_do_method(this, "_select_at_anim", animation, E.first, E.second);
6546
}
6547
for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
6548
undo_redo->add_undo_method(this, "_select_at_anim", animation, E->key().track, E->get().pos);
6549
}
6550
6551
undo_redo->add_do_method(this, "_redraw_tracks");
6552
undo_redo->add_undo_method(this, "_redraw_tracks");
6553
undo_redo->commit_action();
6554
}
6555
}
6556
6557
bool AnimationTrackEditor::_is_track_compatible(int p_target_track_idx, Variant::Type p_source_value_type, Animation::TrackType p_source_track_type) {
6558
if (animation.is_valid()) {
6559
Animation::TrackType target_track_type = animation->track_get_type(p_target_track_idx);
6560
bool track_types_equal = target_track_type == p_source_track_type;
6561
bool is_source_vector3_type = p_source_track_type == Animation::TYPE_POSITION_3D || p_source_track_type == Animation::TYPE_SCALE_3D || p_source_track_type == Animation::TYPE_ROTATION_3D;
6562
bool is_source_bezier = p_source_track_type == Animation::TYPE_BEZIER;
6563
switch (target_track_type) {
6564
case Animation::TYPE_POSITION_3D:
6565
case Animation::TYPE_SCALE_3D:
6566
return p_source_value_type == Variant::VECTOR3;
6567
case Animation::TYPE_ROTATION_3D:
6568
return p_source_value_type == Variant::QUATERNION;
6569
case Animation::TYPE_BEZIER:
6570
return track_types_equal || p_source_value_type == Variant::FLOAT;
6571
case Animation::TYPE_VALUE:
6572
if (track_types_equal || is_source_vector3_type || is_source_bezier) {
6573
bool path_valid = false;
6574
Variant::Type property_type = Variant::NIL;
6575
6576
AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
6577
if (ape) {
6578
AnimationPlayer *ap = ape->get_player();
6579
if (ap) {
6580
NodePath npath = animation->track_get_path(p_target_track_idx);
6581
Node *a_ap_root_node = ap->get_node(ap->get_root_node());
6582
Node *nd = nullptr;
6583
// We must test that we have a valid a_ap_root_node before trying to access its content to init the nd Node.
6584
if (a_ap_root_node) {
6585
nd = a_ap_root_node->get_node(NodePath(npath.get_concatenated_names()));
6586
}
6587
if (nd) {
6588
StringName prop = npath.get_concatenated_subnames();
6589
PropertyInfo prop_info;
6590
path_valid = ClassDB::get_property_info(nd->get_class(), prop, &prop_info);
6591
property_type = prop_info.type;
6592
}
6593
}
6594
}
6595
6596
if (path_valid) {
6597
if (is_source_bezier) {
6598
p_source_value_type = Variant::FLOAT;
6599
}
6600
return property_type == p_source_value_type;
6601
} else {
6602
if (animation->track_get_key_count(p_target_track_idx) > 0) {
6603
Variant::Type first_key_type = animation->track_get_key_value(p_target_track_idx, 0).get_type();
6604
return first_key_type == p_source_value_type;
6605
}
6606
return true; // Type is Undefined.
6607
}
6608
}
6609
return false;
6610
default: // Works for TYPE_ANIMATION; TYPE_AUDIO; TYPE_CALL_METHOD; BLEND_SHAPE.
6611
return track_types_equal;
6612
}
6613
}
6614
return false;
6615
}
6616
6617
void AnimationTrackEditor::_edit_menu_about_to_popup() {
6618
AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();
6619
edit->get_popup()->set_item_disabled(edit->get_popup()->get_item_index(EDIT_APPLY_RESET), !player->can_apply_reset());
6620
6621
bool has_length = false;
6622
for (const KeyValue<SelectedKey, KeyInfo> &E : selection) {
6623
if (animation->track_get_type(E.key.track) == Animation::TYPE_AUDIO && animation->audio_track_get_key_stream(E.key.track, E.key.key).is_valid()) {
6624
has_length = true;
6625
break;
6626
}
6627
}
6628
edit->get_popup()->set_item_disabled(edit->get_popup()->get_item_index(EDIT_SET_START_OFFSET), !has_length);
6629
edit->get_popup()->set_item_disabled(edit->get_popup()->get_item_index(EDIT_SET_END_OFFSET), !has_length);
6630
}
6631
6632
void AnimationTrackEditor::goto_prev_step(bool p_from_mouse_event) {
6633
if (animation.is_null()) {
6634
return;
6635
}
6636
float anim_step = animation->get_step();
6637
if (anim_step == 0.0) {
6638
anim_step = 1.0;
6639
}
6640
if (p_from_mouse_event && Input::get_singleton()->is_key_pressed(Key::SHIFT)) {
6641
// Use more precise snapping when holding Shift.
6642
// This is used when scrobbling the timeline using Alt + Mouse wheel.
6643
anim_step *= 0.25;
6644
}
6645
6646
float pos = timeline->get_play_position();
6647
pos = Math::snapped(pos - anim_step, anim_step);
6648
if (pos < 0.0) {
6649
pos = 0.0;
6650
}
6651
set_anim_pos(pos);
6652
_timeline_changed(pos, false);
6653
}
6654
6655
void AnimationTrackEditor::goto_next_step(bool p_from_mouse_event, bool p_timeline_only) {
6656
if (animation.is_null()) {
6657
return;
6658
}
6659
float anim_step = animation->get_step();
6660
if (anim_step == 0.0) {
6661
anim_step = 1.0;
6662
}
6663
if (p_from_mouse_event && Input::get_singleton()->is_key_pressed(Key::SHIFT)) {
6664
// Use more precise snapping when holding Shift.
6665
// This is used when scrobbling the timeline using Alt + Mouse wheel.
6666
// Do not use precise snapping when using the menu action or keyboard shortcut,
6667
// as the default keyboard shortcut requires pressing Shift.
6668
anim_step *= 0.25;
6669
}
6670
6671
float pos = timeline->get_play_position();
6672
6673
pos = Math::snapped(pos + anim_step, anim_step);
6674
if (pos > animation->get_length()) {
6675
pos = animation->get_length();
6676
}
6677
set_anim_pos(pos);
6678
6679
_timeline_changed(pos, p_timeline_only);
6680
}
6681
6682
void AnimationTrackEditor::_edit_menu_pressed(int p_option) {
6683
switch (p_option) {
6684
case EDIT_COPY_TRACKS: {
6685
track_copy_select->clear();
6686
TreeItem *troot = track_copy_select->create_item();
6687
6688
for (int i = 0; i < animation->get_track_count(); i++) {
6689
NodePath path = animation->track_get_path(i);
6690
Node *node = nullptr;
6691
6692
if (root) {
6693
node = root->get_node_or_null(path);
6694
}
6695
6696
String text;
6697
Ref<Texture2D> icon = get_editor_theme_icon(SNAME("Node"));
6698
if (node) {
6699
if (has_theme_icon(node->get_class(), EditorStringName(EditorIcons))) {
6700
icon = get_editor_theme_icon(node->get_class());
6701
}
6702
6703
text = node->get_name();
6704
Vector<StringName> sn = path.get_subnames();
6705
for (int j = 0; j < sn.size(); j++) {
6706
text += ".";
6707
text += sn[j];
6708
}
6709
6710
path = NodePath(node->get_path().get_names(), path.get_subnames(), true); // Store full path instead for copying.
6711
} else {
6712
text = String(path);
6713
int sep = text.find_char(':');
6714
if (sep != -1) {
6715
text = text.substr(sep + 1);
6716
}
6717
}
6718
6719
String track_type;
6720
switch (animation->track_get_type(i)) {
6721
case Animation::TYPE_POSITION_3D:
6722
track_type = TTR("Position");
6723
break;
6724
case Animation::TYPE_ROTATION_3D:
6725
track_type = TTR("Rotation");
6726
break;
6727
case Animation::TYPE_SCALE_3D:
6728
track_type = TTR("Scale");
6729
break;
6730
case Animation::TYPE_BLEND_SHAPE:
6731
track_type = TTR("BlendShape");
6732
break;
6733
case Animation::TYPE_METHOD:
6734
track_type = TTR("Methods");
6735
break;
6736
case Animation::TYPE_BEZIER:
6737
track_type = TTR("Bezier");
6738
break;
6739
case Animation::TYPE_AUDIO:
6740
track_type = TTR("Audio");
6741
break;
6742
default: {
6743
};
6744
}
6745
if (!track_type.is_empty()) {
6746
text += vformat(" (%s)", track_type);
6747
}
6748
6749
TreeItem *it = track_copy_select->create_item(troot);
6750
it->set_editable(0, true);
6751
it->set_selectable(0, true);
6752
it->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
6753
it->set_icon(0, icon);
6754
it->set_text(0, text);
6755
Dictionary md;
6756
md["track_idx"] = i;
6757
md["path"] = path;
6758
it->set_metadata(0, md);
6759
}
6760
6761
track_copy_dialog->popup_centered(Size2(350, 500) * EDSCALE);
6762
} break;
6763
case EDIT_COPY_TRACKS_CONFIRM: {
6764
track_clipboard.clear();
6765
TreeItem *tree_root = track_copy_select->get_root();
6766
if (tree_root) {
6767
TreeItem *it = tree_root->get_first_child();
6768
while (it) {
6769
Dictionary md = it->get_metadata(0);
6770
int idx = md["track_idx"];
6771
if (it->is_checked(0) && idx >= 0 && idx < animation->get_track_count()) {
6772
TrackClipboard tc;
6773
tc.base_path = animation->track_get_path(idx);
6774
tc.full_path = md["path"];
6775
tc.track_type = animation->track_get_type(idx);
6776
tc.interp_type = animation->track_get_interpolation_type(idx);
6777
if (tc.track_type == Animation::TYPE_VALUE) {
6778
tc.update_mode = animation->value_track_get_update_mode(idx);
6779
}
6780
if (tc.track_type == Animation::TYPE_AUDIO) {
6781
tc.use_blend = animation->audio_track_is_use_blend(idx);
6782
}
6783
tc.loop_wrap = animation->track_get_interpolation_loop_wrap(idx);
6784
tc.enabled = animation->track_is_enabled(idx);
6785
for (int i = 0; i < animation->track_get_key_count(idx); i++) {
6786
TrackClipboard::Key k;
6787
k.time = animation->track_get_key_time(idx, i);
6788
k.value = animation->track_get_key_value(idx, i);
6789
k.transition = animation->track_get_key_transition(idx, i);
6790
tc.keys.push_back(k);
6791
}
6792
track_clipboard.push_back(tc);
6793
}
6794
it = it->get_next();
6795
}
6796
}
6797
} break;
6798
case EDIT_PASTE_TRACKS: {
6799
if (track_clipboard.is_empty()) {
6800
EditorNode::get_singleton()->show_warning(TTR("Clipboard is empty!"));
6801
break;
6802
}
6803
6804
int base_track = animation->get_track_count();
6805
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
6806
undo_redo->create_action(TTR("Paste Tracks"));
6807
for (int i = 0; i < track_clipboard.size(); i++) {
6808
undo_redo->add_do_method(animation.ptr(), "add_track", track_clipboard[i].track_type);
6809
Node *exists = nullptr;
6810
NodePath path = track_clipboard[i].base_path;
6811
6812
if (root) {
6813
NodePath np = track_clipboard[i].full_path;
6814
exists = root->get_node_or_null(np);
6815
if (exists) {
6816
path = NodePath(root->get_path_to(exists).get_names(), track_clipboard[i].full_path.get_subnames(), false);
6817
}
6818
}
6819
6820
undo_redo->add_do_method(animation.ptr(), "track_set_path", base_track, path);
6821
undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_type", base_track, track_clipboard[i].interp_type);
6822
undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_loop_wrap", base_track, track_clipboard[i].loop_wrap);
6823
undo_redo->add_do_method(animation.ptr(), "track_set_enabled", base_track, track_clipboard[i].enabled);
6824
if (track_clipboard[i].track_type == Animation::TYPE_VALUE) {
6825
undo_redo->add_do_method(animation.ptr(), "value_track_set_update_mode", base_track, track_clipboard[i].update_mode);
6826
}
6827
if (track_clipboard[i].track_type == Animation::TYPE_AUDIO) {
6828
undo_redo->add_do_method(animation.ptr(), "audio_track_set_use_blend", base_track, track_clipboard[i].use_blend);
6829
}
6830
6831
for (int j = 0; j < track_clipboard[i].keys.size(); j++) {
6832
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);
6833
}
6834
6835
undo_redo->add_undo_method(animation.ptr(), "remove_track", animation->get_track_count());
6836
6837
base_track++;
6838
}
6839
6840
undo_redo->commit_action();
6841
} break;
6842
case EDIT_SCALE_SELECTION: {
6843
scale_dialog->popup_centered(Size2(200, 100) * EDSCALE);
6844
scale->get_line_edit()->grab_focus();
6845
scale_from_cursor = false;
6846
} break;
6847
case EDIT_SCALE_FROM_CURSOR: {
6848
scale_dialog->popup_centered(Size2(200, 100) * EDSCALE);
6849
scale->get_line_edit()->grab_focus();
6850
scale_from_cursor = true;
6851
} break;
6852
case EDIT_SCALE_CONFIRM: {
6853
if (selection.is_empty()) {
6854
return;
6855
}
6856
6857
float from_t = 1e20;
6858
float to_t = -1e20;
6859
float len = -1e20;
6860
float pivot = 0;
6861
6862
for (const KeyValue<SelectedKey, KeyInfo> &E : selection) {
6863
float t = animation->track_get_key_time(E.key.track, E.key.key);
6864
if (t < from_t) {
6865
from_t = t;
6866
}
6867
if (t > to_t) {
6868
to_t = t;
6869
}
6870
}
6871
6872
len = to_t - from_t;
6873
if (scale_from_cursor) {
6874
pivot = timeline->get_play_position();
6875
} else {
6876
pivot = from_t;
6877
}
6878
6879
float s = scale->get_value();
6880
ERR_FAIL_COND_MSG(s == 0, "Can't scale to 0.");
6881
6882
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
6883
undo_redo->create_action(TTR("Animation Scale Keys"));
6884
6885
List<_AnimMoveRestore> to_restore;
6886
6887
// 1 - Remove the keys.
6888
for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
6889
undo_redo->add_do_method(animation.ptr(), "track_remove_key", E->key().track, E->key().key);
6890
}
6891
// 2 - Remove overlapped keys.
6892
for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
6893
float newtime = (E->get().pos - from_t) * s + from_t;
6894
int idx = animation->track_find_key(E->key().track, newtime, Animation::FIND_MODE_APPROX);
6895
if (idx == -1) {
6896
continue;
6897
}
6898
SelectedKey sk;
6899
sk.key = idx;
6900
sk.track = E->key().track;
6901
if (selection.has(sk)) {
6902
continue; // Already in selection, don't save.
6903
}
6904
6905
undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_time", E->key().track, newtime);
6906
_AnimMoveRestore amr;
6907
6908
amr.key = animation->track_get_key_value(E->key().track, idx);
6909
amr.track = E->key().track;
6910
amr.time = newtime;
6911
amr.transition = animation->track_get_key_transition(E->key().track, idx);
6912
6913
to_restore.push_back(amr);
6914
}
6915
6916
#define NEW_POS(m_ofs) (((s > 0) ? m_ofs : from_t + (len - (m_ofs - from_t))) - pivot) * Math::abs(s) + pivot
6917
// 3 - Move the keys (re insert them).
6918
for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
6919
float newpos = NEW_POS(E->get().pos);
6920
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));
6921
}
6922
6923
// 4 - (Undo) Remove inserted keys.
6924
for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
6925
float newpos = NEW_POS(E->get().pos);
6926
undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", E->key().track, newpos);
6927
}
6928
6929
// 5 - (Undo) Reinsert keys.
6930
for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
6931
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));
6932
}
6933
6934
// 6 - (Undo) Reinsert overlapped keys.
6935
for (_AnimMoveRestore &amr : to_restore) {
6936
undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, amr.transition);
6937
}
6938
6939
undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);
6940
undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
6941
6942
// 7 - Reselect.
6943
for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
6944
float oldpos = E->get().pos;
6945
float newpos = NEW_POS(oldpos);
6946
if (newpos >= 0) {
6947
undo_redo->add_do_method(this, "_select_at_anim", animation, E->key().track, newpos);
6948
}
6949
undo_redo->add_undo_method(this, "_select_at_anim", animation, E->key().track, oldpos);
6950
}
6951
#undef NEW_POS
6952
6953
undo_redo->add_do_method(this, "_redraw_tracks");
6954
undo_redo->add_undo_method(this, "_redraw_tracks");
6955
undo_redo->commit_action();
6956
} break;
6957
6958
case EDIT_SET_START_OFFSET: {
6959
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
6960
undo_redo->create_action(TTR("Animation Set Start Offset"), UndoRedo::MERGE_ENDS);
6961
for (const KeyValue<SelectedKey, KeyInfo> &E : selection) {
6962
if (animation->track_get_type(E.key.track) != Animation::TYPE_AUDIO) {
6963
continue;
6964
}
6965
Ref<AudioStream> stream = animation->audio_track_get_key_stream(E.key.track, E.key.key);
6966
if (stream.is_null()) {
6967
continue;
6968
}
6969
double len = stream->get_length() - animation->audio_track_get_key_end_offset(E.key.track, E.key.key);
6970
real_t prev_offset = animation->audio_track_get_key_start_offset(E.key.track, E.key.key);
6971
double prev_time = animation->track_get_key_time(E.key.track, E.key.key);
6972
float cur_time = timeline->get_play_position();
6973
float diff = prev_offset + cur_time - prev_time;
6974
float destination = cur_time - MIN(0, diff);
6975
if (diff >= len || animation->track_find_key(E.key.track, destination, Animation::FIND_MODE_EXACT) >= 0) {
6976
continue;
6977
}
6978
undo_redo->add_do_method(animation.ptr(), "audio_track_set_key_start_offset", E.key.track, E.key.key, diff);
6979
undo_redo->add_do_method(animation.ptr(), "track_set_key_time", E.key.track, E.key.key, destination);
6980
undo_redo->add_undo_method(animation.ptr(), "track_set_key_time", E.key.track, E.key.key, prev_time);
6981
undo_redo->add_undo_method(animation.ptr(), "audio_track_set_key_start_offset", E.key.track, E.key.key, prev_offset);
6982
}
6983
undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);
6984
undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
6985
undo_redo->commit_action();
6986
} break;
6987
case EDIT_SET_END_OFFSET: {
6988
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
6989
undo_redo->create_action(TTR("Animation Set End Offset"), UndoRedo::MERGE_ENDS);
6990
for (const KeyValue<SelectedKey, KeyInfo> &E : selection) {
6991
if (animation->track_get_type(E.key.track) != Animation::TYPE_AUDIO) {
6992
continue;
6993
}
6994
Ref<AudioStream> stream = animation->audio_track_get_key_stream(E.key.track, E.key.key);
6995
if (stream.is_null()) {
6996
continue;
6997
}
6998
double len = stream->get_length() - animation->audio_track_get_key_start_offset(E.key.track, E.key.key);
6999
real_t prev_offset = animation->audio_track_get_key_end_offset(E.key.track, E.key.key);
7000
double prev_time = animation->track_get_key_time(E.key.track, E.key.key);
7001
float cur_time = timeline->get_play_position();
7002
float diff = prev_time + len - cur_time;
7003
if (diff >= len) {
7004
continue;
7005
}
7006
undo_redo->add_do_method(animation.ptr(), "audio_track_set_key_end_offset", E.key.track, E.key.key, diff);
7007
undo_redo->add_undo_method(animation.ptr(), "audio_track_set_key_end_offset", E.key.track, E.key.key, prev_offset);
7008
}
7009
undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);
7010
undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
7011
undo_redo->commit_action();
7012
} break;
7013
7014
case EDIT_EASE_SELECTION: {
7015
ease_dialog->popup_centered(Size2(200, 100) * EDSCALE);
7016
} break;
7017
case EDIT_EASE_CONFIRM: {
7018
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
7019
undo_redo->create_action(TTR("Make Easing Keys"));
7020
7021
Tween::TransitionType transition_type = static_cast<Tween::TransitionType>(transition_selection->get_selected_id());
7022
Tween::EaseType ease_type = static_cast<Tween::EaseType>(ease_selection->get_selected_id());
7023
float fps = ease_fps->get_value();
7024
double dur_step = 1.0 / fps;
7025
7026
// Organize track and key.
7027
HashMap<int, Vector<int>> keymap;
7028
Vector<int> tracks;
7029
for (const KeyValue<SelectedKey, KeyInfo> &E : selection) {
7030
if (!tracks.has(E.key.track)) {
7031
tracks.append(E.key.track);
7032
}
7033
}
7034
for (int i = 0; i < tracks.size(); i++) {
7035
switch (animation->track_get_type(tracks[i])) {
7036
case Animation::TYPE_VALUE:
7037
case Animation::TYPE_POSITION_3D:
7038
case Animation::TYPE_ROTATION_3D:
7039
case Animation::TYPE_SCALE_3D:
7040
case Animation::TYPE_BLEND_SHAPE: {
7041
Vector<int> keys;
7042
for (const KeyValue<SelectedKey, KeyInfo> &E : selection) {
7043
if (E.key.track == tracks[i]) {
7044
keys.append(E.key.key);
7045
}
7046
}
7047
keys.sort();
7048
keymap.insert(tracks[i], keys);
7049
} break;
7050
default: {
7051
} break;
7052
}
7053
}
7054
7055
// Make easing.
7056
HashMap<int, Vector<int>>::Iterator E = keymap.begin();
7057
while (E) {
7058
int track = E->key;
7059
Vector<int> keys = E->value;
7060
int len = keys.size() - 1;
7061
7062
// Special case for angle interpolation.
7063
bool is_using_angle = animation->track_get_interpolation_type(track) == Animation::INTERPOLATION_LINEAR_ANGLE || animation->track_get_interpolation_type(track) == Animation::INTERPOLATION_CUBIC_ANGLE;
7064
7065
// Make insert queue.
7066
Vector<Pair<real_t, Variant>> insert_queue_new;
7067
for (int i = 0; i < len; i++) {
7068
// Check neighboring keys.
7069
if (keys[i] + 1 == keys[i + 1]) {
7070
double from_t = animation->track_get_key_time(track, keys[i]);
7071
double to_t = animation->track_get_key_time(track, keys[i + 1]);
7072
Variant from_v = animation->track_get_key_value(track, keys[i]);
7073
Variant to_v = animation->track_get_key_value(track, keys[i + 1]);
7074
if (is_using_angle) {
7075
real_t a = from_v;
7076
real_t b = to_v;
7077
real_t to_diff = std::fmod(b - a, Math::TAU);
7078
to_v = a + std::fmod(2.0 * to_diff, Math::TAU) - to_diff;
7079
}
7080
Variant delta_v = Animation::subtract_variant(to_v, from_v);
7081
double duration = to_t - from_t;
7082
double fixed_duration = duration - UNIT_EPSILON; // Prevent to overwrap keys...
7083
for (double delta_t = dur_step; delta_t < fixed_duration; delta_t += dur_step) {
7084
Pair<real_t, Variant> keydata;
7085
keydata.first = from_t + delta_t;
7086
keydata.second = Tween::interpolate_variant(from_v, delta_v, delta_t, duration, transition_type, ease_type);
7087
insert_queue_new.append(keydata);
7088
}
7089
}
7090
}
7091
7092
// Do insertion.
7093
for (int i = 0; i < insert_queue_new.size(); i++) {
7094
undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, insert_queue_new[i].first, insert_queue_new[i].second);
7095
undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", track, insert_queue_new[i].first);
7096
}
7097
7098
++E;
7099
}
7100
7101
undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);
7102
undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
7103
undo_redo->add_do_method(this, "_redraw_tracks");
7104
undo_redo->add_undo_method(this, "_redraw_tracks");
7105
undo_redo->commit_action();
7106
_update_key_edit();
7107
7108
} break;
7109
7110
case EDIT_DUPLICATE_SELECTED_KEYS: {
7111
if (bezier_edit->is_visible()) {
7112
bezier_edit->duplicate_selected_keys(-1.0, false);
7113
break;
7114
}
7115
_anim_duplicate_keys(-1.0, false, -1.0);
7116
} break;
7117
case EDIT_CUT_KEYS: {
7118
if (bezier_edit->is_visible()) {
7119
bezier_edit->copy_selected_keys(true);
7120
break;
7121
}
7122
_anim_copy_keys(true);
7123
} break;
7124
case EDIT_COPY_KEYS: {
7125
if (bezier_edit->is_visible()) {
7126
bezier_edit->copy_selected_keys(false);
7127
break;
7128
}
7129
_anim_copy_keys(false);
7130
} break;
7131
case EDIT_PASTE_KEYS: {
7132
_anim_paste_keys(-1.0, false, -1.0);
7133
} break;
7134
case EDIT_MOVE_FIRST_SELECTED_KEY_TO_CURSOR: {
7135
if (moving_selection || selection.is_empty()) {
7136
break;
7137
}
7138
real_t from_t = 1e20;
7139
for (const KeyValue<SelectedKey, KeyInfo> &E : selection) {
7140
real_t t = animation->track_get_key_time(E.key.track, E.key.key);
7141
if (t < from_t) {
7142
from_t = t;
7143
}
7144
}
7145
_move_selection_begin();
7146
_move_selection(timeline->get_play_position() - from_t);
7147
_move_selection_commit();
7148
} break;
7149
case EDIT_MOVE_LAST_SELECTED_KEY_TO_CURSOR: {
7150
if (moving_selection || selection.is_empty()) {
7151
break;
7152
}
7153
real_t to_t = -1e20;
7154
for (const KeyValue<SelectedKey, KeyInfo> &E : selection) {
7155
real_t t = animation->track_get_key_time(E.key.track, E.key.key);
7156
if (t > to_t) {
7157
to_t = t;
7158
}
7159
}
7160
_move_selection_begin();
7161
_move_selection(timeline->get_play_position() - to_t);
7162
_move_selection_commit();
7163
} break;
7164
case EDIT_ADD_RESET_KEY: {
7165
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
7166
undo_redo->create_action(TTR("Animation Add RESET Keys"));
7167
7168
Ref<Animation> reset = _create_and_get_reset_animation();
7169
int reset_tracks = reset->get_track_count();
7170
HashSet<int> tracks_added;
7171
7172
for (const KeyValue<SelectedKey, KeyInfo> &E : selection) {
7173
const SelectedKey &sk = E.key;
7174
7175
const Animation::TrackType track_type = animation->track_get_type(E.key.track);
7176
if (track_type == Animation::TYPE_ANIMATION || track_type == Animation::TYPE_AUDIO || track_type == Animation::TYPE_METHOD) {
7177
continue;
7178
}
7179
7180
// Only add one key per track.
7181
if (tracks_added.has(sk.track)) {
7182
continue;
7183
}
7184
tracks_added.insert(sk.track);
7185
7186
int dst_track = -1;
7187
7188
const NodePath &path = animation->track_get_path(sk.track);
7189
for (int i = 0; i < reset->get_track_count(); i++) {
7190
if (reset->track_get_path(i) == path) {
7191
dst_track = i;
7192
break;
7193
}
7194
}
7195
7196
int existing_idx = -1;
7197
if (dst_track == -1) {
7198
// If adding multiple tracks, make sure that correct track is referenced.
7199
dst_track = reset_tracks;
7200
reset_tracks++;
7201
7202
undo_redo->add_do_method(reset.ptr(), "add_track", animation->track_get_type(sk.track));
7203
undo_redo->add_do_method(reset.ptr(), "track_set_path", dst_track, path);
7204
undo_redo->add_undo_method(reset.ptr(), "remove_track", dst_track);
7205
} else {
7206
existing_idx = reset->track_find_key(dst_track, 0, Animation::FIND_MODE_APPROX);
7207
}
7208
7209
if (animation->track_get_type(sk.track) == Animation::TYPE_VALUE) {
7210
undo_redo->add_do_method(reset.ptr(), "value_track_set_update_mode", dst_track, animation->value_track_get_update_mode(sk.track));
7211
}
7212
if (animation->track_get_type(sk.track) == Animation::TYPE_AUDIO) {
7213
undo_redo->add_do_method(reset.ptr(), "audio_track_set_use_blend", dst_track, animation->audio_track_is_use_blend(sk.track));
7214
}
7215
undo_redo->add_do_method(reset.ptr(), "track_set_interpolation_type", dst_track, animation->track_get_interpolation_type(sk.track));
7216
undo_redo->add_do_method(reset.ptr(), "track_set_interpolation_loop_wrap", dst_track, animation->track_get_interpolation_loop_wrap(sk.track));
7217
7218
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));
7219
undo_redo->add_undo_method(reset.ptr(), "track_remove_key_at_time", dst_track, 0);
7220
7221
if (existing_idx != -1) {
7222
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));
7223
}
7224
}
7225
7226
undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);
7227
undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
7228
undo_redo->add_do_method(this, "_redraw_tracks");
7229
undo_redo->add_undo_method(this, "_redraw_tracks");
7230
undo_redo->commit_action();
7231
7232
} break;
7233
case EDIT_DELETE_SELECTION: {
7234
if (bezier_edit->is_visible()) {
7235
bezier_edit->delete_selection();
7236
break;
7237
}
7238
7239
if (selection.size()) {
7240
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
7241
undo_redo->create_action(TTR("Animation Delete Keys"));
7242
7243
for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
7244
undo_redo->add_do_method(animation.ptr(), "track_remove_key", E->key().track, E->key().key);
7245
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));
7246
}
7247
undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);
7248
undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
7249
undo_redo->add_do_method(this, "_redraw_tracks");
7250
undo_redo->add_undo_method(this, "_redraw_tracks");
7251
undo_redo->commit_action();
7252
_update_key_edit();
7253
}
7254
} break;
7255
case EDIT_GOTO_NEXT_STEP_TIMELINE_ONLY:
7256
case EDIT_GOTO_NEXT_STEP: {
7257
goto_next_step(false, p_option == EDIT_GOTO_NEXT_STEP_TIMELINE_ONLY);
7258
} break;
7259
case EDIT_GOTO_PREV_STEP: {
7260
goto_prev_step(false);
7261
} break;
7262
7263
case EDIT_APPLY_RESET: {
7264
AnimationPlayerEditor::get_singleton()->get_player()->apply_reset(true);
7265
} break;
7266
7267
case EDIT_BAKE_ANIMATION: {
7268
bake_dialog->popup_centered(Size2(200, 100) * EDSCALE);
7269
} break;
7270
case EDIT_BAKE_ANIMATION_CONFIRM: {
7271
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
7272
undo_redo->create_action(TTR("Bake Animation as Linear Keys"));
7273
7274
int track_len = animation->get_track_count();
7275
bool b_trs = bake_trs->is_pressed();
7276
bool b_bs = bake_blendshape->is_pressed();
7277
bool b_v = bake_value->is_pressed();
7278
7279
double anim_len = animation->get_length() + CMP_EPSILON; // For end key.
7280
float fps = bake_fps->get_value();
7281
double dur_step = 1.0 / fps;
7282
7283
for (int i = 0; i < track_len; i++) {
7284
bool do_bake = false;
7285
Animation::TrackType type = animation->track_get_type(i);
7286
do_bake |= b_trs && (type == Animation::TYPE_POSITION_3D || type == Animation::TYPE_ROTATION_3D || type == Animation::TYPE_SCALE_3D);
7287
do_bake |= b_bs && type == Animation::TYPE_BLEND_SHAPE;
7288
do_bake |= b_v && type == Animation::TYPE_VALUE;
7289
if (do_bake && !animation->track_is_compressed(i)) {
7290
Animation::InterpolationType it = animation->track_get_interpolation_type(i);
7291
if (it == Animation::INTERPOLATION_NEAREST) {
7292
continue; // Nearest interpolation cannot be baked.
7293
}
7294
7295
// Special case for angle interpolation.
7296
bool is_using_angle = it == Animation::INTERPOLATION_LINEAR_ANGLE || it == Animation::INTERPOLATION_CUBIC_ANGLE;
7297
7298
// Make insert queue.
7299
Vector<Pair<real_t, Variant>> insert_queue_new;
7300
7301
switch (type) {
7302
case Animation::TYPE_POSITION_3D: {
7303
for (double delta_t = 0.0; delta_t <= anim_len; delta_t += dur_step) {
7304
Pair<real_t, Variant> keydata;
7305
keydata.first = delta_t;
7306
Vector3 v;
7307
animation->try_position_track_interpolate(i, delta_t, &v);
7308
keydata.second = v;
7309
insert_queue_new.append(keydata);
7310
}
7311
} break;
7312
case Animation::TYPE_ROTATION_3D: {
7313
for (double delta_t = 0.0; delta_t <= anim_len; delta_t += dur_step) {
7314
Pair<real_t, Variant> keydata;
7315
keydata.first = delta_t;
7316
Quaternion v;
7317
animation->try_rotation_track_interpolate(i, delta_t, &v);
7318
keydata.second = v;
7319
insert_queue_new.append(keydata);
7320
}
7321
} break;
7322
case Animation::TYPE_SCALE_3D: {
7323
for (double delta_t = 0.0; delta_t <= anim_len; delta_t += dur_step) {
7324
Pair<real_t, Variant> keydata;
7325
keydata.first = delta_t;
7326
Vector3 v;
7327
animation->try_scale_track_interpolate(i, delta_t, &v);
7328
keydata.second = v;
7329
insert_queue_new.append(keydata);
7330
}
7331
} break;
7332
case Animation::TYPE_BLEND_SHAPE: {
7333
for (double delta_t = 0.0; delta_t <= anim_len; delta_t += dur_step) {
7334
Pair<real_t, Variant> keydata;
7335
keydata.first = delta_t;
7336
float v;
7337
animation->try_blend_shape_track_interpolate(i, delta_t, &v);
7338
keydata.second = v;
7339
insert_queue_new.append(keydata);
7340
}
7341
} break;
7342
case Animation::TYPE_VALUE: {
7343
for (double delta_t = 0.0; delta_t < anim_len; delta_t += dur_step) {
7344
Pair<real_t, Variant> keydata;
7345
keydata.first = delta_t;
7346
keydata.second = animation->value_track_interpolate(i, delta_t);
7347
insert_queue_new.append(keydata);
7348
}
7349
} break;
7350
default: {
7351
} break;
7352
}
7353
7354
// Cleanup keys.
7355
int key_len = animation->track_get_key_count(i);
7356
for (int j = key_len - 1; j >= 0; j--) {
7357
undo_redo->add_do_method(animation.ptr(), "track_remove_key", i, j);
7358
}
7359
7360
// Insert keys.
7361
undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_type", i, is_using_angle ? Animation::INTERPOLATION_LINEAR_ANGLE : Animation::INTERPOLATION_LINEAR);
7362
for (int j = insert_queue_new.size() - 1; j >= 0; j--) {
7363
undo_redo->add_do_method(animation.ptr(), "track_insert_key", i, insert_queue_new[j].first, insert_queue_new[j].second);
7364
undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", i, insert_queue_new[j].first);
7365
}
7366
7367
// Undo methods.
7368
undo_redo->add_undo_method(animation.ptr(), "track_set_interpolation_type", i, animation->track_get_interpolation_type(i));
7369
for (int j = key_len - 1; j >= 0; j--) {
7370
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));
7371
}
7372
}
7373
}
7374
7375
undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);
7376
undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
7377
undo_redo->add_do_method(this, "_redraw_tracks");
7378
undo_redo->add_undo_method(this, "_redraw_tracks");
7379
undo_redo->commit_action();
7380
_update_key_edit();
7381
7382
} break;
7383
7384
case EDIT_OPTIMIZE_ANIMATION: {
7385
optimize_dialog->popup_centered(Size2(250, 180) * EDSCALE);
7386
7387
} break;
7388
case EDIT_OPTIMIZE_ANIMATION_CONFIRM: {
7389
animation->optimize(optimize_velocity_error->get_value(), optimize_angular_error->get_value(), optimize_precision_error->get_value());
7390
_redraw_tracks();
7391
_update_key_edit();
7392
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
7393
undo_redo->clear_history(undo_redo->get_history_id_for_object(animation.ptr()));
7394
undo_redo->clear_history(undo_redo->get_history_id_for_object(this));
7395
7396
} break;
7397
case EDIT_CLEAN_UP_ANIMATION: {
7398
cleanup_dialog->popup_centered(Size2(300, 0) * EDSCALE);
7399
7400
} break;
7401
case EDIT_CLEAN_UP_ANIMATION_CONFIRM: {
7402
if (cleanup_all->is_pressed()) {
7403
List<StringName> names;
7404
AnimationPlayerEditor::get_singleton()->get_player()->get_animation_list(&names);
7405
for (const StringName &E : names) {
7406
_cleanup_animation(AnimationPlayerEditor::get_singleton()->get_player()->get_animation(E));
7407
}
7408
} else {
7409
_cleanup_animation(animation);
7410
}
7411
7412
} break;
7413
}
7414
}
7415
7416
void AnimationTrackEditor::_cleanup_animation(Ref<Animation> p_animation) {
7417
_clear_selection();
7418
for (int i = 0; i < p_animation->get_track_count(); i++) {
7419
if (!root->has_node_and_resource(p_animation->track_get_path(i))) {
7420
continue;
7421
}
7422
Ref<Resource> res;
7423
Vector<StringName> leftover_path;
7424
Node *node = root->get_node_and_resource(p_animation->track_get_path(i), res, leftover_path);
7425
7426
bool prop_exists = false;
7427
Variant::Type valid_type = Variant::NIL;
7428
Object *obj = nullptr;
7429
7430
if (res.is_valid()) {
7431
obj = res.ptr();
7432
} else if (node) {
7433
obj = node;
7434
}
7435
7436
if (obj && p_animation->track_get_type(i) == Animation::TYPE_VALUE) {
7437
valid_type = obj->get_static_property_type_indexed(leftover_path, &prop_exists);
7438
}
7439
7440
if (!obj && cleanup_tracks->is_pressed()) {
7441
p_animation->remove_track(i);
7442
i--;
7443
continue;
7444
}
7445
7446
if (cleanup_keys_with_trimming_head->is_pressed()) {
7447
// Check is necessary because if there is already a key in position 0, it should not be replaced.
7448
if (p_animation->track_get_type(i) == Animation::TYPE_AUDIO && p_animation->track_find_key(i, 0, Animation::FIND_MODE_EXACT) < 0) {
7449
for (int j = 0; j < p_animation->track_get_key_count(i); j++) {
7450
double t = p_animation->track_get_key_time(i, j);
7451
if (t < 0) {
7452
if (j == p_animation->track_get_key_count(i) - 1 || (j + 1 < p_animation->track_get_key_count(i) && p_animation->track_get_key_time(i, j + 1) > 0)) {
7453
Ref<AudioStream> stream = p_animation->audio_track_get_key_stream(i, j);
7454
double len = stream->get_length() - p_animation->audio_track_get_key_end_offset(i, j);
7455
double prev_offset = p_animation->audio_track_get_key_start_offset(i, j);
7456
double prev_time = p_animation->track_get_key_time(i, j);
7457
double diff = prev_offset - prev_time;
7458
if (diff >= len) {
7459
p_animation->track_remove_key(i, j);
7460
j--;
7461
continue;
7462
}
7463
p_animation->audio_track_set_key_start_offset(i, j, diff);
7464
p_animation->track_set_key_time(i, j, 0);
7465
} else {
7466
p_animation->track_remove_key(i, j);
7467
j--;
7468
}
7469
}
7470
}
7471
} else {
7472
for (int j = 0; j < p_animation->track_get_key_count(i); j++) {
7473
double t = p_animation->track_get_key_time(i, j);
7474
if (t < 0) {
7475
p_animation->track_remove_key(i, j);
7476
j--;
7477
}
7478
}
7479
}
7480
}
7481
7482
if (cleanup_keys_with_trimming_end->is_pressed()) {
7483
if (p_animation->track_get_type(i) == Animation::TYPE_AUDIO) {
7484
for (int j = 0; j < p_animation->track_get_key_count(i); j++) {
7485
double t = p_animation->track_get_key_time(i, j);
7486
if (t <= p_animation->get_length() && (j == p_animation->track_get_key_count(i) - 1 || (j + 1 < p_animation->track_get_key_count(i) && p_animation->track_get_key_time(i, j + 1) > p_animation->get_length()))) {
7487
Ref<AudioStream> stream = animation->audio_track_get_key_stream(i, j);
7488
double len = stream->get_length() - animation->audio_track_get_key_start_offset(i, j);
7489
if (t + len < p_animation->get_length()) {
7490
continue;
7491
}
7492
double prev_time = animation->track_get_key_time(i, j);
7493
double diff = prev_time + len - p_animation->get_length();
7494
if (diff >= len) {
7495
p_animation->track_remove_key(i, j);
7496
j--;
7497
continue;
7498
}
7499
p_animation->audio_track_set_key_end_offset(i, j, diff);
7500
} else if (t > p_animation->get_length()) {
7501
p_animation->track_remove_key(i, j);
7502
j--;
7503
}
7504
}
7505
} else {
7506
for (int j = 0; j < p_animation->track_get_key_count(i); j++) {
7507
double t = p_animation->track_get_key_time(i, j);
7508
if (t > p_animation->get_length()) {
7509
p_animation->track_remove_key(i, j);
7510
j--;
7511
}
7512
}
7513
}
7514
}
7515
7516
if (!prop_exists || p_animation->track_get_type(i) != Animation::TYPE_VALUE || !cleanup_keys->is_pressed()) {
7517
continue;
7518
}
7519
7520
for (int j = 0; j < p_animation->track_get_key_count(i); j++) {
7521
Variant v = p_animation->track_get_key_value(i, j);
7522
7523
if (!Variant::can_convert(v.get_type(), valid_type)) {
7524
p_animation->track_remove_key(i, j);
7525
j--;
7526
}
7527
}
7528
7529
if (p_animation->track_get_key_count(i) == 0 && cleanup_tracks->is_pressed()) {
7530
p_animation->remove_track(i);
7531
i--;
7532
}
7533
}
7534
7535
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
7536
undo_redo->clear_history(undo_redo->get_history_id_for_object(animation.ptr()));
7537
undo_redo->clear_history(undo_redo->get_history_id_for_object(this));
7538
_update_tracks();
7539
}
7540
7541
void AnimationTrackEditor::_toggle_function_names() {
7542
_redraw_tracks();
7543
}
7544
7545
void AnimationTrackEditor::_view_group_toggle() {
7546
_update_tracks();
7547
view_group->set_button_icon(get_editor_theme_icon(view_group->is_pressed() ? SNAME("AnimationTrackList") : SNAME("AnimationTrackGroup")));
7548
bezier_edit->set_filtered(selected_filter->is_pressed());
7549
}
7550
7551
bool AnimationTrackEditor::is_grouping_tracks() {
7552
if (!view_group) {
7553
return false;
7554
}
7555
7556
return !view_group->is_pressed();
7557
}
7558
7559
bool AnimationTrackEditor::is_sorting_alphabetically() {
7560
return alphabetic_sorting->is_pressed();
7561
}
7562
7563
bool AnimationTrackEditor::is_function_name_pressed() {
7564
return function_name_toggler->is_pressed();
7565
}
7566
7567
void AnimationTrackEditor::_auto_fit() {
7568
timeline->auto_fit();
7569
}
7570
7571
void AnimationTrackEditor::_auto_fit_bezier() {
7572
timeline->auto_fit();
7573
7574
if (bezier_edit->is_visible()) {
7575
bezier_edit->auto_fit_vertically();
7576
}
7577
}
7578
7579
void AnimationTrackEditor::_selection_changed() {
7580
if (selected_filter->is_pressed()) {
7581
_update_tracks(); // Needs updating.
7582
} else {
7583
_redraw_tracks();
7584
_redraw_groups();
7585
}
7586
}
7587
7588
void AnimationTrackEditor::_update_snap_unit() {
7589
nearest_fps = 0;
7590
7591
if (step->get_value() <= 0) {
7592
snap_unit = 0;
7593
_update_nearest_fps_label();
7594
return; // Avoid zero div.
7595
}
7596
7597
if (timeline->is_using_fps()) {
7598
snap_unit = 1.0 / step->get_value();
7599
} else {
7600
if (fps_compat->is_pressed()) {
7601
snap_unit = CLAMP(step->get_value(), 0.0, 1.0);
7602
if (!Math::is_zero_approx(snap_unit)) {
7603
real_t fps = Math::round(1.0 / snap_unit);
7604
nearest_fps = int(fps);
7605
snap_unit = 1.0 / fps;
7606
}
7607
} else {
7608
snap_unit = step->get_value();
7609
}
7610
}
7611
_update_nearest_fps_label();
7612
}
7613
7614
float AnimationTrackEditor::snap_time(float p_value, bool p_relative) {
7615
if (is_snap_keys_enabled()) {
7616
double current_snap = snap_unit;
7617
if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) {
7618
// Use more precise snapping when holding Shift.
7619
current_snap *= 0.25;
7620
}
7621
7622
if (p_relative) {
7623
double rel = Math::fmod(timeline->get_value(), current_snap);
7624
p_value = Math::snapped(p_value + rel, current_snap) - rel;
7625
} else {
7626
p_value = Math::snapped(p_value, current_snap);
7627
}
7628
}
7629
7630
return p_value;
7631
}
7632
7633
float AnimationTrackEditor::get_snap_unit() {
7634
return snap_unit;
7635
}
7636
7637
void AnimationTrackEditor::_show_imported_anim_warning() {
7638
// It looks terrible on a single line but the TTR extractor doesn't support line breaks yet.
7639
EditorNode::get_singleton()->show_warning(
7640
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\"."));
7641
}
7642
7643
void AnimationTrackEditor::_show_dummy_player_warning() {
7644
EditorNode::get_singleton()->show_warning(
7645
TTR("Some AnimationPlayerEditor's options are disabled since this is the dummy AnimationPlayer for preview.\n\nThe dummy player is forced active, non-deterministic and doesn't have the root motion track. Furthermore, the original node is inactive temporary."));
7646
}
7647
7648
void AnimationTrackEditor::_show_inactive_player_warning() {
7649
EditorNode::get_singleton()->show_warning(
7650
TTR("AnimationPlayer is inactive. The playback will not be processed."));
7651
}
7652
7653
void AnimationTrackEditor::_select_all_tracks_for_copy() {
7654
TreeItem *track = track_copy_select->get_root()->get_first_child();
7655
if (!track) {
7656
return;
7657
}
7658
7659
bool all_selected = true;
7660
while (track) {
7661
if (!track->is_checked(0)) {
7662
all_selected = false;
7663
}
7664
7665
track = track->get_next();
7666
}
7667
7668
track = track_copy_select->get_root()->get_first_child();
7669
while (track) {
7670
track->set_checked(0, !all_selected);
7671
track = track->get_next();
7672
}
7673
}
7674
7675
void AnimationTrackEditor::_bind_methods() {
7676
ClassDB::bind_method("_track_grab_focus", &AnimationTrackEditor::_track_grab_focus);
7677
ClassDB::bind_method("_redraw_tracks", &AnimationTrackEditor::_redraw_tracks);
7678
ClassDB::bind_method("_clear_selection_for_anim", &AnimationTrackEditor::_clear_selection_for_anim);
7679
ClassDB::bind_method("_select_at_anim", &AnimationTrackEditor::_select_at_anim);
7680
ClassDB::bind_method("_clear_selection", &AnimationTrackEditor::_clear_selection);
7681
7682
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));
7683
ClassDB::bind_method(D_METHOD("_bezier_track_set_key_handle_mode_at_time", "animation", "track_idx", "time", "key_handle_mode", "key_handle_set_mode"), &AnimationTrackEditor::_bezier_track_set_key_handle_mode_at_time, DEFVAL(Animation::HANDLE_SET_MODE_NONE));
7684
7685
ADD_SIGNAL(MethodInfo("timeline_changed", PropertyInfo(Variant::FLOAT, "position"), PropertyInfo(Variant::BOOL, "timeline_only"), PropertyInfo(Variant::BOOL, "update_position_only")));
7686
ADD_SIGNAL(MethodInfo("keying_changed"));
7687
ADD_SIGNAL(MethodInfo("animation_len_changed", PropertyInfo(Variant::FLOAT, "len")));
7688
ADD_SIGNAL(MethodInfo("animation_step_changed", PropertyInfo(Variant::FLOAT, "step")));
7689
}
7690
7691
void AnimationTrackEditor::_pick_track_filter_text_changed(const String &p_newtext) {
7692
TreeItem *root_item = pick_track->get_scene_tree()->get_scene_tree()->get_root();
7693
7694
Vector<Node *> select_candidates;
7695
Node *to_select = nullptr;
7696
7697
String filter = pick_track->get_filter_line_edit()->get_text();
7698
7699
_pick_track_select_recursive(root_item, filter, select_candidates);
7700
7701
if (!select_candidates.is_empty()) {
7702
for (int i = 0; i < select_candidates.size(); ++i) {
7703
Node *candidate = select_candidates[i];
7704
7705
if (((String)candidate->get_name()).to_lower().begins_with(filter.to_lower())) {
7706
to_select = candidate;
7707
break;
7708
}
7709
}
7710
7711
if (!to_select) {
7712
to_select = select_candidates[0];
7713
}
7714
}
7715
7716
pick_track->get_scene_tree()->set_selected(to_select);
7717
}
7718
7719
void AnimationTrackEditor::_pick_track_select_recursive(TreeItem *p_item, const String &p_filter, Vector<Node *> &p_select_candidates) {
7720
if (!p_item) {
7721
return;
7722
}
7723
7724
NodePath np = p_item->get_metadata(0);
7725
Node *node = get_node_or_null(np);
7726
7727
if (node && !p_filter.is_empty() && ((String)node->get_name()).containsn(p_filter)) {
7728
p_select_candidates.push_back(node);
7729
}
7730
7731
TreeItem *c = p_item->get_first_child();
7732
7733
while (c) {
7734
_pick_track_select_recursive(c, p_filter, p_select_candidates);
7735
c = c->get_next();
7736
}
7737
}
7738
7739
AnimationTrackEditor::AnimationTrackEditor() {
7740
main_panel = memnew(PanelContainer);
7741
main_panel->set_focus_mode(FOCUS_ALL); // Allow panel to have focus so that shortcuts work as expected.
7742
add_child(main_panel);
7743
main_panel->set_v_size_flags(SIZE_EXPAND_FILL);
7744
HBoxContainer *timeline_scroll = memnew(HBoxContainer);
7745
main_panel->add_child(timeline_scroll);
7746
timeline_scroll->set_v_size_flags(SIZE_EXPAND_FILL);
7747
7748
timeline_vbox = memnew(VBoxContainer);
7749
timeline_scroll->add_child(timeline_vbox);
7750
timeline_vbox->set_v_size_flags(SIZE_EXPAND_FILL);
7751
timeline_vbox->set_h_size_flags(SIZE_EXPAND_FILL);
7752
7753
info_message = memnew(Label);
7754
info_message->set_focus_mode(FOCUS_ACCESSIBILITY);
7755
info_message->set_text(TTR("Select an AnimationPlayer node to create and edit animations."));
7756
info_message->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
7757
info_message->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
7758
info_message->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART);
7759
info_message->set_custom_minimum_size(Size2(100 * EDSCALE, 0));
7760
info_message->set_anchors_and_offsets_preset(PRESET_FULL_RECT, PRESET_MODE_KEEP_SIZE, 8 * EDSCALE);
7761
main_panel->add_child(info_message);
7762
7763
timeline = memnew(AnimationTimelineEdit);
7764
timeline_vbox->add_child(timeline);
7765
timeline->connect("timeline_changed", callable_mp(this, &AnimationTrackEditor::_timeline_changed));
7766
timeline->connect("name_limit_changed", callable_mp(this, &AnimationTrackEditor::_name_limit_changed));
7767
timeline->connect("track_added", callable_mp(this, &AnimationTrackEditor::_add_track));
7768
timeline->connect(SceneStringName(value_changed), callable_mp(this, &AnimationTrackEditor::_timeline_value_changed));
7769
timeline->connect("length_changed", callable_mp(this, &AnimationTrackEditor::_update_length));
7770
timeline->connect("filter_changed", callable_mp(this, &AnimationTrackEditor::_update_tracks));
7771
7772
panner.instantiate();
7773
panner->set_scroll_zoom_factor(AnimationTimelineEdit::SCROLL_ZOOM_FACTOR_IN);
7774
panner->set_callbacks(callable_mp(this, &AnimationTrackEditor::_pan_callback), callable_mp(this, &AnimationTrackEditor::_zoom_callback));
7775
7776
box_selection_container = memnew(Control);
7777
box_selection_container->set_v_size_flags(SIZE_EXPAND_FILL);
7778
box_selection_container->set_clip_contents(true);
7779
timeline_vbox->add_child(box_selection_container);
7780
7781
marker_edit = memnew(AnimationMarkerEdit);
7782
timeline->get_child(0)->add_child(marker_edit);
7783
marker_edit->set_editor(this);
7784
marker_edit->set_timeline(timeline);
7785
marker_edit->set_h_size_flags(SIZE_EXPAND_FILL);
7786
marker_edit->set_anchors_and_offsets_preset(Control::LayoutPreset::PRESET_FULL_RECT);
7787
marker_edit->connect(SceneStringName(draw), callable_mp(this, &AnimationTrackEditor::_redraw_groups));
7788
marker_edit->connect(SceneStringName(draw), callable_mp(this, &AnimationTrackEditor::_redraw_tracks));
7789
7790
scroll = memnew(ScrollContainer);
7791
box_selection_container->add_child(scroll);
7792
scroll->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
7793
7794
VScrollBar *sb = scroll->get_v_scroll_bar();
7795
scroll->remove_child(sb);
7796
timeline_scroll->add_child(sb); // Move here so timeline and tracks are always aligned.
7797
scroll->set_focus_mode(FOCUS_CLICK);
7798
scroll->connect(SceneStringName(gui_input), callable_mp(this, &AnimationTrackEditor::_scroll_input));
7799
scroll->connect(SceneStringName(focus_exited), callable_mp(panner.ptr(), &ViewPanner::release_pan_key));
7800
7801
scroll->get_v_scroll_bar()->connect(SceneStringName(value_changed), callable_mp(this, &AnimationTrackEditor::_v_scroll_changed));
7802
scroll->get_h_scroll_bar()->connect(SceneStringName(value_changed), callable_mp(this, &AnimationTrackEditor::_h_scroll_changed));
7803
7804
bezier_edit = memnew(AnimationBezierTrackEdit);
7805
timeline_vbox->add_child(bezier_edit);
7806
bezier_edit->set_editor(this);
7807
bezier_edit->set_timeline(timeline);
7808
bezier_edit->hide();
7809
bezier_edit->set_v_size_flags(SIZE_EXPAND_FILL);
7810
bezier_edit->connect("timeline_changed", callable_mp(this, &AnimationTrackEditor::_timeline_changed));
7811
7812
timeline_vbox->set_custom_minimum_size(Size2(0, 150) * EDSCALE);
7813
7814
hscroll = memnew(HScrollBar);
7815
hscroll->share(timeline);
7816
hscroll->hide();
7817
hscroll->connect(SceneStringName(value_changed), callable_mp(this, &AnimationTrackEditor::_update_scroll));
7818
timeline_vbox->add_child(hscroll);
7819
timeline->set_hscroll(hscroll);
7820
7821
track_vbox = memnew(VBoxContainer);
7822
scroll->add_child(track_vbox);
7823
track_vbox->set_h_size_flags(SIZE_EXPAND_FILL);
7824
scroll->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED);
7825
7826
HFlowContainer *bottom_hf = memnew(HFlowContainer);
7827
add_child(bottom_hf);
7828
7829
imported_anim_warning = memnew(Button);
7830
imported_anim_warning->hide();
7831
imported_anim_warning->set_text(TTR("Imported Scene"));
7832
imported_anim_warning->set_tooltip_text(TTR("Warning: Editing imported animation"));
7833
imported_anim_warning->connect(SceneStringName(pressed), callable_mp(this, &AnimationTrackEditor::_show_imported_anim_warning));
7834
bottom_hf->add_child(imported_anim_warning);
7835
7836
dummy_player_warning = memnew(Button);
7837
dummy_player_warning->hide();
7838
dummy_player_warning->set_text(TTR("Dummy Player"));
7839
dummy_player_warning->set_tooltip_text(TTR("Warning: Editing dummy AnimationPlayer"));
7840
dummy_player_warning->connect(SceneStringName(pressed), callable_mp(this, &AnimationTrackEditor::_show_dummy_player_warning));
7841
bottom_hf->add_child(dummy_player_warning);
7842
7843
inactive_player_warning = memnew(Button);
7844
inactive_player_warning->hide();
7845
inactive_player_warning->set_text(TTR("Inactive Player"));
7846
inactive_player_warning->set_tooltip_text(TTR("Warning: AnimationPlayer is inactive"));
7847
inactive_player_warning->connect(SceneStringName(pressed), callable_mp(this, &AnimationTrackEditor::_show_inactive_player_warning));
7848
bottom_hf->add_child(inactive_player_warning);
7849
7850
Control *spacer = memnew(Control);
7851
spacer->set_mouse_filter(MOUSE_FILTER_PASS);
7852
spacer->set_h_size_flags(SIZE_EXPAND_FILL);
7853
bottom_hf->add_child(spacer);
7854
7855
Label *bezier_key_default_label = memnew(Label);
7856
bezier_key_default_label->set_text(TTR("Bezier Default Mode:"));
7857
bottom_hf->add_child(bezier_key_default_label);
7858
7859
bezier_key_mode = memnew(OptionButton);
7860
bezier_key_mode->add_item(TTR("Free"));
7861
bezier_key_mode->add_item(TTR("Linear"));
7862
bezier_key_mode->add_item(TTR("Balanced"));
7863
bezier_key_mode->add_item(TTR("Mirrored"));
7864
bezier_key_mode->set_tooltip_text(TTR("Set the default behavior of new bezier keys."));
7865
bezier_key_mode->select(2);
7866
7867
bottom_hf->add_child(bezier_key_mode);
7868
bottom_hf->add_child(memnew(VSeparator));
7869
7870
bezier_edit_icon = memnew(Button);
7871
bezier_edit_icon->set_flat(true);
7872
bezier_edit_icon->set_disabled(true);
7873
bezier_edit_icon->set_toggle_mode(true);
7874
bezier_edit_icon->connect(SceneStringName(pressed), callable_mp(this, &AnimationTrackEditor::_toggle_bezier_edit));
7875
bezier_edit_icon->set_tooltip_text(TTR("Toggle between the bezier curve editor and track editor."));
7876
7877
bottom_hf->add_child(bezier_edit_icon);
7878
7879
function_name_toggler = memnew(Button);
7880
function_name_toggler->set_flat(true);
7881
function_name_toggler->connect(SceneStringName(pressed), callable_mp(this, &AnimationTrackEditor::_toggle_function_names));
7882
function_name_toggler->set_shortcut(ED_SHORTCUT("animation_editor/toggle_function_names", TTRC("Toggle method names")));
7883
function_name_toggler->set_toggle_mode(true);
7884
function_name_toggler->set_shortcut_in_tooltip(false);
7885
function_name_toggler->set_tooltip_text(TTRC("Toggle function names in the track editor."));
7886
7887
bottom_hf->add_child(function_name_toggler);
7888
7889
selected_filter = memnew(Button);
7890
selected_filter->set_flat(true);
7891
selected_filter->connect(SceneStringName(pressed), callable_mp(this, &AnimationTrackEditor::_view_group_toggle)); // Same function works the same.
7892
selected_filter->set_toggle_mode(true);
7893
selected_filter->set_tooltip_text(TTR("Only show tracks from nodes selected in tree."));
7894
7895
bottom_hf->add_child(selected_filter);
7896
7897
alphabetic_sorting = memnew(Button);
7898
alphabetic_sorting->set_flat(true);
7899
alphabetic_sorting->connect(SceneStringName(pressed), callable_mp(this, &AnimationTrackEditor::_update_tracks));
7900
alphabetic_sorting->set_toggle_mode(true);
7901
alphabetic_sorting->set_tooltip_text(TTR("Sort tracks/groups alphabetically.\nIf disabled, tracks are shown in the order they are added and can be reordered using drag-and-drop."));
7902
7903
bottom_hf->add_child(alphabetic_sorting);
7904
7905
view_group = memnew(Button);
7906
view_group->set_flat(true);
7907
view_group->connect(SceneStringName(pressed), callable_mp(this, &AnimationTrackEditor::_view_group_toggle));
7908
view_group->set_toggle_mode(true);
7909
view_group->set_tooltip_text(TTR("Group tracks by node or display them as plain list."));
7910
7911
bottom_hf->add_child(view_group);
7912
bottom_hf->add_child(memnew(VSeparator));
7913
7914
snap_timeline = memnew(Button);
7915
snap_timeline->set_flat(true);
7916
bottom_hf->add_child(snap_timeline);
7917
snap_timeline->set_disabled(true);
7918
snap_timeline->set_toggle_mode(true);
7919
snap_timeline->set_pressed(false);
7920
snap_timeline->set_tooltip_text(TTR("Apply snapping to timeline cursor."));
7921
7922
snap_keys = memnew(Button);
7923
snap_keys->set_flat(true);
7924
bottom_hf->add_child(snap_keys);
7925
snap_keys->set_disabled(true);
7926
snap_keys->set_toggle_mode(true);
7927
snap_keys->set_pressed(true);
7928
snap_keys->set_tooltip_text(TTR("Apply snapping to selected key(s)."));
7929
7930
fps_compat = memnew(Button);
7931
fps_compat->set_flat(true);
7932
bottom_hf->add_child(fps_compat);
7933
fps_compat->set_disabled(true);
7934
fps_compat->set_toggle_mode(true);
7935
fps_compat->set_pressed(true);
7936
fps_compat->set_tooltip_text(TTR("Apply snapping to the nearest integer FPS."));
7937
fps_compat->connect(SceneStringName(toggled), callable_mp(this, &AnimationTrackEditor::_update_fps_compat_mode));
7938
7939
nearest_fps_label = memnew(Label);
7940
nearest_fps_label->set_focus_mode(FOCUS_ACCESSIBILITY);
7941
nearest_fps_label->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
7942
bottom_hf->add_child(nearest_fps_label);
7943
7944
step = memnew(EditorSpinSlider);
7945
step->set_min(0);
7946
step->set_max(1000000);
7947
step->set_step(SECOND_DECIMAL);
7948
step->set_hide_slider(true);
7949
step->set_custom_minimum_size(Size2(100, 0) * EDSCALE);
7950
step->set_tooltip_text(TTR("Animation step value."));
7951
step->set_accessibility_name(TTRC("Animation step value."));
7952
bottom_hf->add_child(step);
7953
step->connect(SceneStringName(value_changed), callable_mp(this, &AnimationTrackEditor::_update_step));
7954
step->set_read_only(true);
7955
7956
snap_mode = memnew(OptionButton);
7957
snap_mode->add_item(TTR("Seconds"));
7958
snap_mode->add_item(TTR("FPS"));
7959
snap_mode->set_accessibility_name(TTRC("Snap Mode"));
7960
snap_mode->set_disabled(true);
7961
bottom_hf->add_child(snap_mode);
7962
snap_mode->connect(SceneStringName(item_selected), callable_mp(this, &AnimationTrackEditor::_snap_mode_changed));
7963
7964
bottom_hf->add_child(memnew(VSeparator));
7965
7966
HBoxContainer *zoom_hb = memnew(HBoxContainer);
7967
zoom_icon = memnew(TextureRect);
7968
zoom_icon->set_v_size_flags(SIZE_SHRINK_CENTER);
7969
zoom_hb->add_child(zoom_icon);
7970
zoom = memnew(HSlider);
7971
zoom->set_step(0.01);
7972
zoom->set_min(0.0);
7973
zoom->set_max(2.0);
7974
zoom->set_value(1.0);
7975
zoom->set_custom_minimum_size(Size2(200, 0) * EDSCALE);
7976
zoom->set_v_size_flags(SIZE_SHRINK_CENTER);
7977
zoom->set_accessibility_name(TTRC("Zoom"));
7978
zoom_hb->add_child(zoom);
7979
bottom_hf->add_child(zoom_hb);
7980
timeline->set_zoom(zoom);
7981
7982
ED_SHORTCUT("animation_editor/auto_fit", TTRC("Fit to panel"), KeyModifierMask::ALT | Key::F);
7983
7984
auto_fit = memnew(Button);
7985
auto_fit->set_flat(true);
7986
auto_fit->connect(SceneStringName(pressed), callable_mp(this, &AnimationTrackEditor::_auto_fit));
7987
auto_fit->set_shortcut(ED_GET_SHORTCUT("animation_editor/auto_fit"));
7988
auto_fit->set_accessibility_name(TTRC("Auto Fit"));
7989
bottom_hf->add_child(auto_fit);
7990
7991
auto_fit_bezier = memnew(Button);
7992
auto_fit_bezier->set_flat(true);
7993
auto_fit_bezier->set_visible(false);
7994
auto_fit_bezier->connect(SceneStringName(pressed), callable_mp(this, &AnimationTrackEditor::_auto_fit_bezier));
7995
auto_fit_bezier->set_shortcut(ED_GET_SHORTCUT("animation_editor/auto_fit"));
7996
auto_fit_bezier->set_accessibility_name(TTRC("Auto Fit Bezier"));
7997
bottom_hf->add_child(auto_fit_bezier);
7998
7999
edit = memnew(MenuButton);
8000
edit->set_shortcut_context(this);
8001
edit->set_text(TTR("Edit"));
8002
edit->set_flat(false);
8003
edit->set_disabled(true);
8004
edit->set_tooltip_text(TTR("Animation properties."));
8005
edit->set_accessibility_name(TTRC("Animation properties."));
8006
edit->get_popup()->add_item(TTR("Copy Tracks..."), EDIT_COPY_TRACKS);
8007
edit->get_popup()->add_item(TTR("Paste Tracks"), EDIT_PASTE_TRACKS);
8008
edit->get_popup()->add_separator();
8009
edit->get_popup()->add_item(TTR("Scale Selection..."), EDIT_SCALE_SELECTION);
8010
edit->get_popup()->add_item(TTR("Scale From Cursor..."), EDIT_SCALE_FROM_CURSOR);
8011
edit->get_popup()->add_separator();
8012
edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/set_start_offset", TTRC("Set Start Offset (Audio)"), KeyModifierMask::CMD_OR_CTRL | Key::BRACKETLEFT), EDIT_SET_START_OFFSET);
8013
edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/set_end_offset", TTRC("Set End Offset (Audio)"), KeyModifierMask::CMD_OR_CTRL | Key::BRACKETRIGHT), EDIT_SET_END_OFFSET);
8014
edit->get_popup()->add_separator();
8015
edit->get_popup()->add_item(TTR("Make Easing Selection..."), EDIT_EASE_SELECTION);
8016
edit->get_popup()->add_separator();
8017
edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/duplicate_selected_keys", TTRC("Duplicate Selected Keys"), KeyModifierMask::CMD_OR_CTRL | Key::D), EDIT_DUPLICATE_SELECTED_KEYS);
8018
edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/cut_selected_keys", TTRC("Cut Selected Keys"), KeyModifierMask::CMD_OR_CTRL | Key::X), EDIT_CUT_KEYS);
8019
edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/copy_selected_keys", TTRC("Copy Selected Keys"), KeyModifierMask::CMD_OR_CTRL | Key::C), EDIT_COPY_KEYS);
8020
edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/paste_keys", TTRC("Paste Keys"), KeyModifierMask::CMD_OR_CTRL | Key::V), EDIT_PASTE_KEYS);
8021
edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/add_reset_value", TTRC("Add RESET Value(s)")));
8022
edit->get_popup()->add_separator();
8023
edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/move_first_selected_key_to_cursor", TTRC("Move First Selected Key to Cursor"), Key::BRACKETLEFT), EDIT_MOVE_FIRST_SELECTED_KEY_TO_CURSOR);
8024
edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/move_last_selected_key_to_cursor", TTRC("Move Last Selected Key to Cursor"), Key::BRACKETRIGHT), EDIT_MOVE_LAST_SELECTED_KEY_TO_CURSOR);
8025
edit->get_popup()->add_separator();
8026
edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/delete_selection", TTRC("Delete Selection"), Key::KEY_DELETE), EDIT_DELETE_SELECTION);
8027
8028
edit->get_popup()->add_separator();
8029
edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/goto_next_step", TTRC("Go to Next Step"), KeyModifierMask::CMD_OR_CTRL | Key::RIGHT), EDIT_GOTO_NEXT_STEP);
8030
edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/goto_prev_step", TTRC("Go to Previous Step"), KeyModifierMask::CMD_OR_CTRL | Key::LEFT), EDIT_GOTO_PREV_STEP);
8031
edit->get_popup()->add_separator();
8032
edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/apply_reset", TTRC("Apply Reset")), EDIT_APPLY_RESET);
8033
edit->get_popup()->add_separator();
8034
edit->get_popup()->add_item(TTR("Bake Animation..."), EDIT_BAKE_ANIMATION);
8035
edit->get_popup()->add_item(TTR("Optimize Animation (no undo)..."), EDIT_OPTIMIZE_ANIMATION);
8036
edit->get_popup()->add_item(TTR("Clean-Up Animation (no undo)..."), EDIT_CLEAN_UP_ANIMATION);
8037
8038
edit->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed));
8039
edit->get_popup()->connect("about_to_popup", callable_mp(this, &AnimationTrackEditor::_edit_menu_about_to_popup));
8040
8041
pick_track = memnew(SceneTreeDialog);
8042
add_child(pick_track);
8043
pick_track->set_title(TTR("Pick a node to animate:"));
8044
pick_track->connect("selected", callable_mp(this, &AnimationTrackEditor::_new_track_node_selected));
8045
pick_track->get_filter_line_edit()->connect(SceneStringName(text_changed), callable_mp(this, &AnimationTrackEditor::_pick_track_filter_text_changed));
8046
8047
prop_selector = memnew(PropertySelector);
8048
add_child(prop_selector);
8049
prop_selector->connect("selected", callable_mp(this, &AnimationTrackEditor::_new_track_property_selected));
8050
prop_selector->set_accessibility_name(TTRC("Track Property"));
8051
8052
method_selector = memnew(PropertySelector);
8053
add_child(method_selector);
8054
method_selector->connect("selected", callable_mp(this, &AnimationTrackEditor::_add_method_key));
8055
method_selector->set_accessibility_name(TTRC("Method Key"));
8056
8057
insert_confirm = memnew(ConfirmationDialog);
8058
add_child(insert_confirm);
8059
insert_confirm->connect(SceneStringName(confirmed), callable_mp(this, &AnimationTrackEditor::_confirm_insert_list));
8060
VBoxContainer *icvb = memnew(VBoxContainer);
8061
insert_confirm->add_child(icvb);
8062
insert_confirm_text = memnew(Label);
8063
insert_confirm_text->set_focus_mode(FOCUS_ACCESSIBILITY);
8064
icvb->add_child(insert_confirm_text);
8065
HBoxContainer *ichb = memnew(HBoxContainer);
8066
icvb->add_child(ichb);
8067
insert_confirm_bezier = memnew(CheckBox);
8068
insert_confirm_bezier->set_text(TTR("Use Bezier Curves"));
8069
insert_confirm_bezier->set_pressed(EDITOR_GET("editors/animation/default_create_bezier_tracks"));
8070
ichb->add_child(insert_confirm_bezier);
8071
insert_confirm_reset = memnew(CheckBox);
8072
insert_confirm_reset->set_text(TTR("Create RESET Track(s)", ""));
8073
insert_confirm_reset->set_pressed(EDITOR_GET("editors/animation/default_create_reset_tracks"));
8074
ichb->add_child(insert_confirm_reset);
8075
8076
box_selection = memnew(Control);
8077
box_selection_container->add_child(box_selection);
8078
box_selection->set_mouse_filter(MOUSE_FILTER_IGNORE);
8079
box_selection->hide();
8080
box_selection->connect(SceneStringName(draw), callable_mp(this, &AnimationTrackEditor::_box_selection_draw));
8081
8082
// Default Plugins.
8083
8084
Ref<AnimationTrackEditDefaultPlugin> def_plugin;
8085
def_plugin.instantiate();
8086
add_track_edit_plugin(def_plugin);
8087
8088
// Dialogs.
8089
8090
optimize_dialog = memnew(ConfirmationDialog);
8091
add_child(optimize_dialog);
8092
optimize_dialog->set_title(TTR("Animation Optimizer"));
8093
VBoxContainer *optimize_vb = memnew(VBoxContainer);
8094
optimize_dialog->add_child(optimize_vb);
8095
8096
optimize_velocity_error = memnew(SpinBox);
8097
optimize_velocity_error->set_max(1.0);
8098
optimize_velocity_error->set_min(0.001);
8099
optimize_velocity_error->set_step(0.001);
8100
optimize_velocity_error->set_value(0.01);
8101
optimize_velocity_error->set_accessibility_name(TTRC("Max Velocity Error:"));
8102
optimize_vb->add_margin_child(TTR("Max Velocity Error:"), optimize_velocity_error);
8103
optimize_angular_error = memnew(SpinBox);
8104
optimize_angular_error->set_max(1.0);
8105
optimize_angular_error->set_min(0.001);
8106
optimize_angular_error->set_step(0.001);
8107
optimize_angular_error->set_value(0.01);
8108
optimize_angular_error->set_accessibility_name(TTRC("Max Angular Error:"));
8109
optimize_vb->add_margin_child(TTR("Max Angular Error:"), optimize_angular_error);
8110
optimize_precision_error = memnew(SpinBox);
8111
optimize_precision_error->set_max(6);
8112
optimize_precision_error->set_min(1);
8113
optimize_precision_error->set_step(1);
8114
optimize_precision_error->set_value(3);
8115
optimize_precision_error->set_accessibility_name(TTRC("Max Precision Error:"));
8116
optimize_vb->add_margin_child(TTR("Max Precision Error:"), optimize_precision_error);
8117
8118
optimize_dialog->set_ok_button_text(TTR("Optimize"));
8119
optimize_dialog->connect(SceneStringName(confirmed), callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed).bind(EDIT_OPTIMIZE_ANIMATION_CONFIRM));
8120
8121
//
8122
cleanup_dialog = memnew(ConfirmationDialog);
8123
add_child(cleanup_dialog);
8124
VBoxContainer *cleanup_vb = memnew(VBoxContainer);
8125
cleanup_dialog->add_child(cleanup_vb);
8126
8127
cleanup_keys_with_trimming_head = memnew(CheckBox);
8128
cleanup_keys_with_trimming_head->set_text(TTR("Trim keys placed in negative time"));
8129
cleanup_keys_with_trimming_head->set_pressed(true);
8130
cleanup_vb->add_child(cleanup_keys_with_trimming_head);
8131
8132
cleanup_keys_with_trimming_end = memnew(CheckBox);
8133
cleanup_keys_with_trimming_end->set_text(TTR("Trim keys placed exceed the animation length"));
8134
cleanup_keys_with_trimming_end->set_pressed(true);
8135
cleanup_vb->add_child(cleanup_keys_with_trimming_end);
8136
8137
cleanup_keys = memnew(CheckBox);
8138
cleanup_keys->set_text(TTR("Remove invalid keys"));
8139
cleanup_keys->set_pressed(true);
8140
cleanup_vb->add_child(cleanup_keys);
8141
8142
cleanup_tracks = memnew(CheckBox);
8143
cleanup_tracks->set_text(TTR("Remove unresolved and empty tracks"));
8144
cleanup_tracks->set_pressed(true);
8145
cleanup_vb->add_child(cleanup_tracks);
8146
8147
cleanup_all = memnew(CheckBox);
8148
cleanup_all->set_text(TTR("Clean-up all animations"));
8149
cleanup_vb->add_child(cleanup_all);
8150
8151
cleanup_dialog->set_title(TTR("Clean-Up Animation(s) (NO UNDO!)"));
8152
cleanup_dialog->set_ok_button_text(TTR("Clean-Up"));
8153
8154
cleanup_dialog->connect(SceneStringName(confirmed), callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed).bind(EDIT_CLEAN_UP_ANIMATION_CONFIRM));
8155
8156
//
8157
scale_dialog = memnew(ConfirmationDialog);
8158
VBoxContainer *vbc = memnew(VBoxContainer);
8159
scale_dialog->add_child(vbc);
8160
8161
scale = memnew(SpinBox);
8162
scale->set_min(-99999);
8163
scale->set_max(99999);
8164
scale->set_step(0.001);
8165
scale->set_select_all_on_focus(true);
8166
scale->set_accessibility_name(TTRC("Scale Ratio"));
8167
vbc->add_margin_child(TTR("Scale Ratio:"), scale);
8168
scale_dialog->connect(SceneStringName(confirmed), callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed).bind(EDIT_SCALE_CONFIRM), CONNECT_DEFERRED);
8169
add_child(scale_dialog);
8170
8171
scale_dialog->register_text_enter(scale->get_line_edit());
8172
8173
//
8174
ease_dialog = memnew(ConfirmationDialog);
8175
ease_dialog->set_title(TTR("Select Transition and Easing"));
8176
ease_dialog->connect(SceneStringName(confirmed), callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed).bind(EDIT_EASE_CONFIRM));
8177
add_child(ease_dialog);
8178
GridContainer *ease_grid = memnew(GridContainer);
8179
ease_grid->set_columns(2);
8180
ease_dialog->add_child(ease_grid);
8181
transition_selection = memnew(OptionButton);
8182
transition_selection->set_accessibility_name(TTRC("Transition Type:"));
8183
transition_selection->add_item(TTR("Linear", "Transition Type"), Tween::TRANS_LINEAR);
8184
transition_selection->add_item(TTR("Sine", "Transition Type"), Tween::TRANS_SINE);
8185
transition_selection->add_item(TTR("Quint", "Transition Type"), Tween::TRANS_QUINT);
8186
transition_selection->add_item(TTR("Quart", "Transition Type"), Tween::TRANS_QUART);
8187
transition_selection->add_item(TTR("Quad", "Transition Type"), Tween::TRANS_QUAD);
8188
transition_selection->add_item(TTR("Expo", "Transition Type"), Tween::TRANS_EXPO);
8189
transition_selection->add_item(TTR("Elastic", "Transition Type"), Tween::TRANS_ELASTIC);
8190
transition_selection->add_item(TTR("Cubic", "Transition Type"), Tween::TRANS_CUBIC);
8191
transition_selection->add_item(TTR("Circ", "Transition Type"), Tween::TRANS_CIRC);
8192
transition_selection->add_item(TTR("Bounce", "Transition Type"), Tween::TRANS_BOUNCE);
8193
transition_selection->add_item(TTR("Back", "Transition Type"), Tween::TRANS_BACK);
8194
transition_selection->add_item(TTR("Spring", "Transition Type"), Tween::TRANS_SPRING);
8195
transition_selection->select(Tween::TRANS_LINEAR); // Default
8196
transition_selection->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); // Translation context is needed.
8197
ease_selection = memnew(OptionButton);
8198
ease_selection->set_accessibility_name(TTRC("Ease Type:"));
8199
ease_selection->add_item(TTR("In", "Ease Type"), Tween::EASE_IN);
8200
ease_selection->add_item(TTR("Out", "Ease Type"), Tween::EASE_OUT);
8201
ease_selection->add_item(TTR("InOut", "Ease Type"), Tween::EASE_IN_OUT);
8202
ease_selection->add_item(TTR("OutIn", "Ease Type"), Tween::EASE_OUT_IN);
8203
ease_selection->select(Tween::EASE_IN_OUT); // Default
8204
ease_selection->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); // Translation context is needed.
8205
ease_fps = memnew(SpinBox);
8206
ease_fps->set_min(FPS_DECIMAL);
8207
ease_fps->set_max(999);
8208
ease_fps->set_step(FPS_DECIMAL);
8209
ease_fps->set_value(30); // Default
8210
ease_fps->set_accessibility_name(TTRC("FPS:"));
8211
ease_grid->add_child(memnew(Label(TTR("Transition Type:"))));
8212
ease_grid->add_child(transition_selection);
8213
ease_grid->add_child(memnew(Label(TTR("Ease Type:"))));
8214
ease_grid->add_child(ease_selection);
8215
ease_grid->add_child(memnew(Label(TTR("FPS:"))));
8216
ease_grid->add_child(ease_fps);
8217
8218
//
8219
bake_dialog = memnew(ConfirmationDialog);
8220
bake_dialog->set_title(TTR("Animation Baker"));
8221
bake_dialog->connect(SceneStringName(confirmed), callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed).bind(EDIT_BAKE_ANIMATION_CONFIRM));
8222
add_child(bake_dialog);
8223
GridContainer *bake_grid = memnew(GridContainer);
8224
bake_grid->set_columns(2);
8225
bake_dialog->add_child(bake_grid);
8226
bake_trs = memnew(CheckBox);
8227
bake_trs->set_accessibility_name(TTRC("3D Pos/Rot/Scl Track:"));
8228
bake_trs->set_pressed(true);
8229
bake_blendshape = memnew(CheckBox);
8230
bake_blendshape->set_accessibility_name(TTRC("Blendshape Track:"));
8231
bake_blendshape->set_pressed(true);
8232
bake_value = memnew(CheckBox);
8233
bake_value->set_accessibility_name(TTRC("Value Track:"));
8234
bake_value->set_pressed(true);
8235
bake_fps = memnew(SpinBox);
8236
bake_fps->set_accessibility_name(TTRC("FPS:"));
8237
bake_fps->set_min(FPS_DECIMAL);
8238
bake_fps->set_max(999);
8239
bake_fps->set_step(FPS_DECIMAL);
8240
bake_fps->set_value(30); // Default
8241
bake_grid->add_child(memnew(Label(TTR("3D Pos/Rot/Scl Track:"))));
8242
bake_grid->add_child(bake_trs);
8243
bake_grid->add_child(memnew(Label(TTR("Blendshape Track:"))));
8244
bake_grid->add_child(bake_blendshape);
8245
bake_grid->add_child(memnew(Label(TTR("Value Track:"))));
8246
bake_grid->add_child(bake_value);
8247
bake_grid->add_child(memnew(Label(TTR("FPS:"))));
8248
bake_grid->add_child(bake_fps);
8249
8250
track_copy_dialog = memnew(ConfirmationDialog);
8251
add_child(track_copy_dialog);
8252
track_copy_dialog->set_title(TTR("Select Tracks to Copy"));
8253
track_copy_dialog->set_ok_button_text(TTR("Copy"));
8254
8255
VBoxContainer *track_copy_vbox = memnew(VBoxContainer);
8256
track_copy_dialog->add_child(track_copy_vbox);
8257
8258
Button *select_all_button = memnew(Button);
8259
select_all_button->set_text(TTR("Select All/None"));
8260
select_all_button->connect(SceneStringName(pressed), callable_mp(this, &AnimationTrackEditor::_select_all_tracks_for_copy));
8261
track_copy_vbox->add_child(select_all_button);
8262
8263
track_copy_select = memnew(Tree);
8264
track_copy_select->set_accessibility_name(TTRC("Copy Selection"));
8265
track_copy_select->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
8266
track_copy_select->set_h_size_flags(SIZE_EXPAND_FILL);
8267
track_copy_select->set_v_size_flags(SIZE_EXPAND_FILL);
8268
track_copy_select->set_hide_root(true);
8269
track_copy_vbox->add_child(track_copy_select);
8270
track_copy_dialog->connect(SceneStringName(confirmed), callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed).bind(EDIT_COPY_TRACKS_CONFIRM));
8271
}
8272
8273
AnimationTrackEditor::~AnimationTrackEditor() {
8274
if (key_edit) {
8275
memdelete(key_edit);
8276
}
8277
if (multi_key_edit) {
8278
memdelete(multi_key_edit);
8279
}
8280
}
8281
8282
// AnimationTrackKeyEditEditorPlugin.
8283
8284
void AnimationTrackKeyEditEditor::_time_edit_spun() {
8285
_time_edit_entered();
8286
_time_edit_exited();
8287
}
8288
8289
void AnimationTrackKeyEditEditor::_time_edit_entered() {
8290
int key = animation->track_find_key(track, key_ofs, Animation::FIND_MODE_APPROX);
8291
if (key == -1) {
8292
return;
8293
}
8294
key_data_cache.time = animation->track_get_key_time(track, key);
8295
key_data_cache.transition = animation->track_get_key_transition(track, key);
8296
key_data_cache.value = animation->track_get_key_value(track, key);
8297
}
8298
8299
void AnimationTrackKeyEditEditor::_time_edit_exited() {
8300
real_t new_time = spinner->get_value();
8301
8302
if (use_fps) {
8303
real_t fps = animation->get_step();
8304
if (fps > 0) {
8305
fps = 1.0 / fps;
8306
}
8307
new_time /= fps;
8308
}
8309
8310
if (Math::is_equal_approx(new_time, key_data_cache.time)) {
8311
return; // No change.
8312
}
8313
8314
int existing = animation->track_find_key(track, new_time, Animation::FIND_MODE_APPROX);
8315
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
8316
undo_redo->create_action(TTR("Animation Change Keyframe Time"));
8317
8318
if (existing != -1) {
8319
undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_time", track, animation->track_get_key_time(track, existing));
8320
}
8321
undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_time", track, key_data_cache.time);
8322
undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, new_time, key_data_cache.value, key_data_cache.transition);
8323
undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", track, new_time);
8324
undo_redo->add_undo_method(animation.ptr(), "track_insert_key", track, key_data_cache.time, key_data_cache.value, key_data_cache.transition);
8325
if (existing != -1) {
8326
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));
8327
}
8328
8329
// Reselect key.
8330
AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
8331
if (ape) {
8332
AnimationTrackEditor *ate = ape->get_track_editor();
8333
if (ate) {
8334
undo_redo->add_do_method(ate, "_clear_selection_for_anim", animation);
8335
undo_redo->add_undo_method(ate, "_clear_selection_for_anim", animation);
8336
undo_redo->add_do_method(ate, "_select_at_anim", animation, track, new_time);
8337
undo_redo->add_undo_method(ate, "_select_at_anim", animation, track, key_data_cache.time);
8338
}
8339
undo_redo->add_do_method(ape, "_animation_update_key_frame");
8340
undo_redo->add_undo_method(ape, "_animation_update_key_frame");
8341
}
8342
8343
undo_redo->commit_action();
8344
}
8345
8346
AnimationTrackKeyEditEditor::AnimationTrackKeyEditEditor(Ref<Animation> p_animation, int p_track, real_t p_key_ofs, bool p_use_fps) {
8347
if (p_animation.is_null()) {
8348
return;
8349
}
8350
8351
animation = p_animation;
8352
track = p_track;
8353
key_ofs = p_key_ofs;
8354
use_fps = p_use_fps;
8355
8356
set_label("Time");
8357
8358
spinner = memnew(EditorSpinSlider);
8359
spinner->set_focus_mode(Control::FOCUS_CLICK);
8360
spinner->set_min(0);
8361
spinner->set_allow_greater(true);
8362
spinner->set_allow_lesser(true);
8363
add_child(spinner);
8364
8365
if (use_fps) {
8366
spinner->set_step(FPS_DECIMAL);
8367
real_t fps = animation->get_step();
8368
if (fps > 0) {
8369
fps = 1.0 / fps;
8370
}
8371
spinner->set_value(key_ofs * fps);
8372
spinner->connect("updown_pressed", callable_mp(this, &AnimationTrackKeyEditEditor::_time_edit_spun), CONNECT_DEFERRED);
8373
} else {
8374
spinner->set_step(SECOND_DECIMAL);
8375
spinner->set_value(key_ofs);
8376
spinner->set_max(animation->get_length());
8377
}
8378
8379
spinner->connect("grabbed", callable_mp(this, &AnimationTrackKeyEditEditor::_time_edit_entered), CONNECT_DEFERRED);
8380
spinner->connect("ungrabbed", callable_mp(this, &AnimationTrackKeyEditEditor::_time_edit_exited), CONNECT_DEFERRED);
8381
spinner->connect("value_focus_entered", callable_mp(this, &AnimationTrackKeyEditEditor::_time_edit_entered), CONNECT_DEFERRED);
8382
spinner->connect("value_focus_exited", callable_mp(this, &AnimationTrackKeyEditEditor::_time_edit_exited), CONNECT_DEFERRED);
8383
}
8384
8385
void AnimationMarkerEdit::_zoom_changed() {
8386
queue_redraw();
8387
play_position->queue_redraw();
8388
}
8389
8390
void AnimationMarkerEdit::_menu_selected(int p_index) {
8391
switch (p_index) {
8392
case MENU_KEY_INSERT: {
8393
_insert_marker(insert_at_pos);
8394
} break;
8395
case MENU_KEY_RENAME: {
8396
if (selection.size() > 0) {
8397
_rename_marker(*selection.last());
8398
}
8399
} break;
8400
case MENU_KEY_DELETE: {
8401
_delete_selected_markers();
8402
} break;
8403
case MENU_KEY_TOGGLE_MARKER_NAMES: {
8404
should_show_all_marker_names = !should_show_all_marker_names;
8405
queue_redraw();
8406
} break;
8407
}
8408
}
8409
8410
void AnimationMarkerEdit::_play_position_draw() {
8411
if (animation.is_null() || play_position_pos < 0) {
8412
return;
8413
}
8414
8415
float scale = timeline->get_zoom_scale();
8416
int h = get_size().height;
8417
8418
int px = (play_position_pos - timeline->get_value()) * scale + timeline->get_name_limit();
8419
8420
if (px >= timeline->get_name_limit() && px < (get_size().width - timeline->get_buttons_width())) {
8421
Color color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
8422
play_position->draw_line(Point2(px, 0), Point2(px, h), color, Math::round(2 * EDSCALE));
8423
}
8424
}
8425
8426
bool AnimationMarkerEdit::_try_select_at_ui_pos(const Point2 &p_pos, bool p_aggregate, bool p_deselectable) {
8427
int limit = timeline->get_name_limit();
8428
int limit_end = get_size().width - timeline->get_buttons_width();
8429
// Left Border including space occupied by keyframes on t=0.
8430
int limit_start_hitbox = limit - type_icon->get_width();
8431
8432
if (p_pos.x >= limit_start_hitbox && p_pos.x <= limit_end) {
8433
int key_idx = -1;
8434
float key_distance = 1e20;
8435
PackedStringArray names = animation->get_marker_names();
8436
for (int i = 0; i < names.size(); i++) {
8437
Rect2 rect = const_cast<AnimationMarkerEdit *>(this)->get_key_rect(timeline->get_zoom_scale());
8438
float offset = animation->get_marker_time(names[i]) - timeline->get_value();
8439
offset = offset * timeline->get_zoom_scale() + limit;
8440
rect.position.x += offset;
8441
if (rect.has_point(p_pos)) {
8442
if (const_cast<AnimationMarkerEdit *>(this)->is_key_selectable_by_distance()) {
8443
float distance = Math::abs(offset - p_pos.x);
8444
if (key_idx == -1 || distance < key_distance) {
8445
key_idx = i;
8446
key_distance = distance;
8447
}
8448
} else {
8449
// First one does it.
8450
break;
8451
}
8452
}
8453
}
8454
8455
if (key_idx != -1) {
8456
if (p_aggregate) {
8457
StringName name = names[key_idx];
8458
if (selection.has(name)) {
8459
if (p_deselectable) {
8460
call_deferred("_deselect_key", name);
8461
moving_selection_pivot = 0.0f;
8462
moving_selection_mouse_begin_x = 0.0f;
8463
}
8464
} else {
8465
call_deferred("_select_key", name, false);
8466
moving_selection_attempt = true;
8467
moving_selection_effective = false;
8468
select_single_attempt = StringName();
8469
moving_selection_pivot = animation->get_marker_time(name);
8470
moving_selection_mouse_begin_x = p_pos.x;
8471
}
8472
8473
} else {
8474
StringName name = names[key_idx];
8475
if (!selection.has(name)) {
8476
call_deferred("_select_key", name, true);
8477
select_single_attempt = StringName();
8478
} else {
8479
select_single_attempt = name;
8480
}
8481
8482
moving_selection_attempt = true;
8483
moving_selection_effective = false;
8484
moving_selection_pivot = animation->get_marker_time(name);
8485
moving_selection_mouse_begin_x = p_pos.x;
8486
}
8487
8488
if (read_only) {
8489
moving_selection_attempt = false;
8490
moving_selection_pivot = 0.0f;
8491
moving_selection_mouse_begin_x = 0.0f;
8492
}
8493
return true;
8494
}
8495
}
8496
8497
return false;
8498
}
8499
8500
bool AnimationMarkerEdit::_is_ui_pos_in_current_section(const Point2 &p_pos) {
8501
int limit = timeline->get_name_limit();
8502
int limit_end = get_size().width - timeline->get_buttons_width();
8503
8504
if (p_pos.x >= limit && p_pos.x <= limit_end) {
8505
PackedStringArray section = get_selected_section();
8506
if (!section.is_empty()) {
8507
StringName start_marker = section[0];
8508
StringName end_marker = section[1];
8509
float start_offset = (animation->get_marker_time(start_marker) - timeline->get_value()) * timeline->get_zoom_scale() + limit;
8510
float end_offset = (animation->get_marker_time(end_marker) - timeline->get_value()) * timeline->get_zoom_scale() + limit;
8511
return p_pos.x >= start_offset && p_pos.x <= end_offset;
8512
}
8513
}
8514
8515
return false;
8516
}
8517
8518
HBoxContainer *AnimationMarkerEdit::_create_hbox_labeled_control(const String &p_text, Control *p_control) const {
8519
HBoxContainer *hbox = memnew(HBoxContainer);
8520
Label *label = memnew(Label);
8521
label->set_text(p_text);
8522
hbox->add_child(label);
8523
hbox->add_child(p_control);
8524
hbox->set_h_size_flags(SIZE_EXPAND_FILL);
8525
label->set_h_size_flags(SIZE_EXPAND_FILL);
8526
label->set_stretch_ratio(1.0);
8527
p_control->set_h_size_flags(SIZE_EXPAND_FILL);
8528
p_control->set_stretch_ratio(1.0);
8529
return hbox;
8530
}
8531
8532
void AnimationMarkerEdit::_update_key_edit() {
8533
_clear_key_edit();
8534
if (animation.is_null()) {
8535
return;
8536
}
8537
8538
if (selection.size() == 1) {
8539
key_edit = memnew(AnimationMarkerKeyEdit);
8540
key_edit->animation = animation;
8541
key_edit->animation_read_only = read_only;
8542
key_edit->marker_name = *selection.begin();
8543
key_edit->use_fps = timeline->is_using_fps();
8544
key_edit->marker_edit = this;
8545
8546
EditorNode::get_singleton()->push_item(key_edit);
8547
8548
InspectorDock::get_singleton()->set_info(TTR("Marker name is read-only in the inspector."), TTR("A marker's name can only be changed by right-clicking it in the animation editor and selecting \"Rename Marker\", in order to make sure that marker names are all unique."), true);
8549
} else if (selection.size() > 1) {
8550
multi_key_edit = memnew(AnimationMultiMarkerKeyEdit);
8551
multi_key_edit->animation = animation;
8552
multi_key_edit->animation_read_only = read_only;
8553
multi_key_edit->marker_edit = this;
8554
for (const StringName &name : selection) {
8555
multi_key_edit->marker_names.push_back(name);
8556
}
8557
8558
EditorNode::get_singleton()->push_item(multi_key_edit);
8559
}
8560
}
8561
8562
void AnimationMarkerEdit::_clear_key_edit() {
8563
if (key_edit) {
8564
// If key edit is the object being inspected, remove it first.
8565
if (InspectorDock::get_inspector_singleton()->get_edited_object() == key_edit) {
8566
EditorNode::get_singleton()->push_item(nullptr);
8567
}
8568
8569
// Then actually delete it.
8570
memdelete(key_edit);
8571
key_edit = nullptr;
8572
}
8573
8574
if (multi_key_edit) {
8575
if (InspectorDock::get_inspector_singleton()->get_edited_object() == multi_key_edit) {
8576
EditorNode::get_singleton()->push_item(nullptr);
8577
}
8578
8579
memdelete(multi_key_edit);
8580
multi_key_edit = nullptr;
8581
}
8582
}
8583
8584
void AnimationMarkerEdit::_bind_methods() {
8585
ClassDB::bind_method("_clear_selection_for_anim", &AnimationMarkerEdit::_clear_selection_for_anim);
8586
ClassDB::bind_method("_select_key", &AnimationMarkerEdit::_select_key);
8587
ClassDB::bind_method("_deselect_key", &AnimationMarkerEdit::_deselect_key);
8588
}
8589
8590
void AnimationMarkerEdit::_notification(int p_what) {
8591
switch (p_what) {
8592
case NOTIFICATION_THEME_CHANGED: {
8593
if (animation.is_null()) {
8594
return;
8595
}
8596
8597
type_icon = get_editor_theme_icon(SNAME("Marker"));
8598
selected_icon = get_editor_theme_icon(SNAME("MarkerSelected"));
8599
} break;
8600
8601
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
8602
RID ae = get_accessibility_element();
8603
ERR_FAIL_COND(ae.is_null());
8604
8605
//TODO
8606
DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_STATIC_TEXT);
8607
DisplayServer::get_singleton()->accessibility_update_set_value(ae, TTR(vformat("The %s is not accessible at this time.", "Animation marker editor")));
8608
} break;
8609
8610
case NOTIFICATION_DRAW: {
8611
if (animation.is_null()) {
8612
return;
8613
}
8614
8615
int limit = timeline->get_name_limit();
8616
8617
Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label"));
8618
Color color = get_theme_color(SceneStringName(font_color), SNAME("Label"));
8619
8620
// SECTION PREVIEW //
8621
8622
{
8623
float scale = timeline->get_zoom_scale();
8624
int limit_end = get_size().width - timeline->get_buttons_width();
8625
8626
PackedStringArray section = get_selected_section();
8627
if (section.size() == 2) {
8628
StringName start_marker = section[0];
8629
StringName end_marker = section[1];
8630
double start_time = animation->get_marker_time(start_marker);
8631
double end_time = animation->get_marker_time(end_marker);
8632
8633
// When AnimationPlayer is playing, don't move the preview rect, so it still indicates the playback section.
8634
AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();
8635
if (moving_selection && !(player && player->is_playing())) {
8636
start_time += moving_selection_offset;
8637
end_time += moving_selection_offset;
8638
}
8639
8640
if (start_time < animation->get_length() && end_time >= 0) {
8641
float start_ofs = MAX(0, start_time) - timeline->get_value();
8642
float end_ofs = MIN(animation->get_length(), end_time) - timeline->get_value();
8643
start_ofs = start_ofs * scale + limit;
8644
end_ofs = end_ofs * scale + limit;
8645
start_ofs = MAX(start_ofs, limit);
8646
end_ofs = MIN(end_ofs, limit_end);
8647
Rect2 rect;
8648
rect.set_position(Vector2(start_ofs, 0));
8649
rect.set_size(Vector2(end_ofs - start_ofs, get_size().height));
8650
8651
draw_rect(rect, Color(1, 0.1, 0.1, 0.2));
8652
}
8653
}
8654
}
8655
8656
// KEYFRAMES //
8657
8658
draw_bg(limit, get_size().width - timeline->get_buttons_width());
8659
8660
{
8661
float scale = timeline->get_zoom_scale();
8662
int limit_end = get_size().width - timeline->get_buttons_width();
8663
8664
PackedStringArray names = animation->get_marker_names();
8665
for (int i = 0; i < names.size(); i++) {
8666
StringName name = names[i];
8667
bool is_selected = selection.has(name);
8668
float offset = animation->get_marker_time(name) - timeline->get_value();
8669
if (is_selected && moving_selection) {
8670
offset += moving_selection_offset;
8671
}
8672
8673
offset = offset * scale + limit;
8674
8675
draw_key(name, scale, int(offset), is_selected, limit, limit_end);
8676
8677
const int font_size = 12 * EDSCALE;
8678
Size2 string_size = font->get_string_size(name, HORIZONTAL_ALIGNMENT_LEFT, -1.0, font_size);
8679
if (int(offset) <= limit_end && int(offset) >= limit && should_show_all_marker_names) {
8680
float bottom = get_size().height + string_size.y - font->get_descent(font_size);
8681
float extrusion = MAX(0, offset + string_size.x - limit_end); // How much the string would extrude outside limit_end if unadjusted.
8682
Color marker_color = animation->get_marker_color(name);
8683
float margin = 4 * EDSCALE;
8684
Point2 pos = Point2(offset - extrusion + margin, bottom + margin);
8685
draw_string(font, pos, name, HORIZONTAL_ALIGNMENT_LEFT, -1.0, font_size, marker_color);
8686
draw_string_outline(font, pos, name, HORIZONTAL_ALIGNMENT_LEFT, -1.0, font_size, 1, color);
8687
}
8688
}
8689
}
8690
8691
draw_fg(limit, get_size().width - timeline->get_buttons_width());
8692
} break;
8693
8694
case NOTIFICATION_MOUSE_ENTER:
8695
hovered = true;
8696
queue_redraw();
8697
break;
8698
case NOTIFICATION_MOUSE_EXIT:
8699
hovered = false;
8700
// When the mouse cursor exits the track, we're no longer hovering any keyframe.
8701
hovering_marker = StringName();
8702
queue_redraw();
8703
break;
8704
}
8705
}
8706
8707
void AnimationMarkerEdit::gui_input(const Ref<InputEvent> &p_event) {
8708
ERR_FAIL_COND(p_event.is_null());
8709
8710
if (animation.is_null()) {
8711
return;
8712
}
8713
8714
if (p_event->is_pressed()) {
8715
if (ED_IS_SHORTCUT("animation_marker_edit/rename_marker", p_event)) {
8716
if (!read_only) {
8717
_menu_selected(MENU_KEY_RENAME);
8718
}
8719
}
8720
8721
if (ED_IS_SHORTCUT("animation_marker_edit/delete_selection", p_event)) {
8722
if (!read_only) {
8723
_menu_selected(MENU_KEY_DELETE);
8724
}
8725
}
8726
8727
if (ED_IS_SHORTCUT("animation_marker_edit/toggle_marker_names", p_event)) {
8728
if (!read_only) {
8729
_menu_selected(MENU_KEY_TOGGLE_MARKER_NAMES);
8730
}
8731
}
8732
}
8733
8734
Ref<InputEventMouseButton> mb = p_event;
8735
8736
if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
8737
Point2 pos = mb->get_position();
8738
if (_try_select_at_ui_pos(pos, mb->is_command_or_control_pressed() || mb->is_shift_pressed(), true)) {
8739
accept_event();
8740
} else if (!_is_ui_pos_in_current_section(pos)) {
8741
_clear_selection_for_anim(animation);
8742
}
8743
}
8744
8745
if (mb.is_valid() && moving_selection_attempt) {
8746
if (!mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
8747
moving_selection_attempt = false;
8748
if (moving_selection && moving_selection_effective) {
8749
if (Math::abs(moving_selection_offset) > CMP_EPSILON) {
8750
_move_selection_commit();
8751
accept_event(); // So play position doesn't snap to the end of move selection.
8752
}
8753
} else if (select_single_attempt) {
8754
call_deferred("_select_key", select_single_attempt, true);
8755
8756
// First select click should not affect play position.
8757
if (!selection.has(select_single_attempt)) {
8758
accept_event();
8759
} else {
8760
// Second click and onwards should snap to marker time.
8761
double ofs = animation->get_marker_time(select_single_attempt);
8762
timeline->set_play_position(ofs);
8763
timeline->emit_signal(SNAME("timeline_changed"), ofs, mb->is_alt_pressed());
8764
accept_event();
8765
}
8766
} else {
8767
// First select click should not affect play position.
8768
if (!selection.has(select_single_attempt)) {
8769
accept_event();
8770
}
8771
}
8772
8773
moving_selection = false;
8774
select_single_attempt = StringName();
8775
}
8776
8777
if (moving_selection && mb->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) {
8778
moving_selection_attempt = false;
8779
moving_selection = false;
8780
_move_selection_cancel();
8781
}
8782
}
8783
8784
if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) {
8785
Point2 pos = mb->get_position();
8786
if (pos.x >= timeline->get_name_limit() && pos.x <= get_size().width - timeline->get_buttons_width()) {
8787
// Can do something with menu too! show insert key.
8788
float offset = (pos.x - timeline->get_name_limit()) / timeline->get_zoom_scale();
8789
if (!read_only) {
8790
bool selected = _try_select_at_ui_pos(pos, mb->is_command_or_control_pressed() || mb->is_shift_pressed(), false);
8791
8792
menu->clear();
8793
menu->add_icon_item(get_editor_theme_icon(SNAME("Key")), TTR("Insert Marker..."), MENU_KEY_INSERT);
8794
8795
if (selected || selection.size() > 0) {
8796
menu->add_icon_item(get_editor_theme_icon(SNAME("Edit")), TTR("Rename Marker"), MENU_KEY_RENAME);
8797
menu->add_icon_item(get_editor_theme_icon(SNAME("Remove")), TTR("Delete Marker(s)"), MENU_KEY_DELETE);
8798
}
8799
8800
menu->add_icon_item(get_editor_theme_icon(should_show_all_marker_names ? SNAME("GuiChecked") : SNAME("GuiUnchecked")), TTR("Show All Marker Names"), MENU_KEY_TOGGLE_MARKER_NAMES);
8801
menu->reset_size();
8802
8803
moving_selection_attempt = false;
8804
moving_selection = false;
8805
8806
menu->set_position(get_screen_position() + get_local_mouse_position());
8807
menu->popup();
8808
8809
insert_at_pos = offset + timeline->get_value();
8810
accept_event();
8811
}
8812
}
8813
}
8814
8815
Ref<InputEventMouseMotion> mm = p_event;
8816
8817
if (mm.is_valid()) {
8818
const StringName previous_hovering_marker = hovering_marker;
8819
8820
// Hovering compressed keyframes for editing is not possible.
8821
const float scale = timeline->get_zoom_scale();
8822
const int limit = timeline->get_name_limit();
8823
const int limit_end = get_size().width - timeline->get_buttons_width();
8824
// Left Border including space occupied by keyframes on t=0.
8825
const int limit_start_hitbox = limit - type_icon->get_width();
8826
const Point2 pos = mm->get_position();
8827
8828
if (pos.x >= limit_start_hitbox && pos.x <= limit_end) {
8829
// Use the same logic as key selection to ensure that hovering accurately represents
8830
// which key will be selected when clicking.
8831
int key_idx = -1;
8832
float key_distance = 1e20;
8833
8834
hovering_marker = StringName();
8835
8836
PackedStringArray names = animation->get_marker_names();
8837
8838
// Hovering should happen in the opposite order of drawing for more accurate overlap hovering.
8839
for (int i = names.size() - 1; i >= 0; i--) {
8840
StringName name = names[i];
8841
Rect2 rect = get_key_rect(scale);
8842
float offset = animation->get_marker_time(name) - timeline->get_value();
8843
offset = offset * scale + limit;
8844
rect.position.x += offset;
8845
8846
if (rect.has_point(pos)) {
8847
if (is_key_selectable_by_distance()) {
8848
const float distance = Math::abs(offset - pos.x);
8849
if (key_idx == -1 || distance < key_distance) {
8850
key_idx = i;
8851
key_distance = distance;
8852
hovering_marker = name;
8853
}
8854
} else {
8855
// First one does it.
8856
hovering_marker = name;
8857
break;
8858
}
8859
}
8860
}
8861
8862
if (hovering_marker != previous_hovering_marker) {
8863
// Required to draw keyframe hover feedback on the correct keyframe.
8864
queue_redraw();
8865
}
8866
}
8867
}
8868
8869
if (mm.is_valid() && mm->get_button_mask().has_flag(MouseButtonMask::LEFT) && moving_selection_attempt) {
8870
if (!moving_selection) {
8871
moving_selection = true;
8872
_move_selection_begin();
8873
}
8874
8875
float moving_begin_time = ((moving_selection_mouse_begin_x - timeline->get_name_limit()) / timeline->get_zoom_scale()) + timeline->get_value();
8876
float new_time = ((mm->get_position().x - timeline->get_name_limit()) / timeline->get_zoom_scale()) + timeline->get_value();
8877
float delta = new_time - moving_begin_time;
8878
float snapped_time = editor->snap_time(moving_selection_pivot + delta);
8879
8880
float offset = 0.0;
8881
if (Math::abs(editor->get_moving_selection_offset()) > CMP_EPSILON || (snapped_time > moving_selection_pivot && delta > CMP_EPSILON) || (snapped_time < moving_selection_pivot && delta < -CMP_EPSILON)) {
8882
offset = snapped_time - moving_selection_pivot;
8883
moving_selection_effective = true;
8884
}
8885
8886
_move_selection(offset);
8887
}
8888
}
8889
8890
String AnimationMarkerEdit::get_tooltip(const Point2 &p_pos) const {
8891
if (animation.is_null()) {
8892
return Control::get_tooltip(p_pos);
8893
}
8894
8895
int limit = timeline->get_name_limit();
8896
int limit_end = get_size().width - timeline->get_buttons_width();
8897
// Left Border including space occupied by keyframes on t=0.
8898
int limit_start_hitbox = limit - type_icon->get_width();
8899
8900
if (p_pos.x >= limit_start_hitbox && p_pos.x <= limit_end) {
8901
int key_idx = -1;
8902
float key_distance = 1e20;
8903
8904
PackedStringArray names = animation->get_marker_names();
8905
8906
// Select should happen in the opposite order of drawing for more accurate overlap select.
8907
for (int i = names.size() - 1; i >= 0; i--) {
8908
StringName name = names[i];
8909
Rect2 rect = const_cast<AnimationMarkerEdit *>(this)->get_key_rect(timeline->get_zoom_scale());
8910
float offset = animation->get_marker_time(name) - timeline->get_value();
8911
offset = offset * timeline->get_zoom_scale() + limit;
8912
rect.position.x += offset;
8913
8914
if (rect.has_point(p_pos)) {
8915
if (const_cast<AnimationMarkerEdit *>(this)->is_key_selectable_by_distance()) {
8916
float distance = Math::abs(offset - p_pos.x);
8917
if (key_idx == -1 || distance < key_distance) {
8918
key_idx = i;
8919
key_distance = distance;
8920
}
8921
} else {
8922
// First one does it.
8923
break;
8924
}
8925
}
8926
}
8927
8928
if (key_idx != -1) {
8929
String name = names[key_idx];
8930
String text = TTR("Time (s):") + " " + TS->format_number(rtos(Math::snapped(animation->get_marker_time(name), 0.0001))) + "\n";
8931
text += TTR("Marker:") + " " + name + "\n";
8932
return text;
8933
}
8934
}
8935
8936
return Control::get_tooltip(p_pos);
8937
}
8938
8939
int AnimationMarkerEdit::get_key_height() const {
8940
if (animation.is_null()) {
8941
return 0;
8942
}
8943
8944
return type_icon->get_height();
8945
}
8946
8947
Rect2 AnimationMarkerEdit::get_key_rect(float p_pixels_sec) const {
8948
if (animation.is_null()) {
8949
return Rect2();
8950
}
8951
8952
Rect2 rect = Rect2(-type_icon->get_width() / 2, get_size().height - type_icon->get_size().height, type_icon->get_width(), type_icon->get_size().height);
8953
8954
// Make it a big easier to click.
8955
rect.position.x -= rect.size.x * 0.5;
8956
rect.size.x *= 2;
8957
return rect;
8958
}
8959
8960
PackedStringArray AnimationMarkerEdit::get_selected_section() const {
8961
if (selection.size() >= 2) {
8962
PackedStringArray arr;
8963
arr.push_back(""); // Marker with smallest time.
8964
arr.push_back(""); // Marker with largest time.
8965
double min_time = Math::INF;
8966
double max_time = -Math::INF;
8967
for (const StringName &marker_name : selection) {
8968
double time = animation->get_marker_time(marker_name);
8969
if (time < min_time) {
8970
arr.set(0, marker_name);
8971
min_time = time;
8972
}
8973
if (time > max_time) {
8974
arr.set(1, marker_name);
8975
max_time = time;
8976
}
8977
}
8978
return arr;
8979
}
8980
8981
return PackedStringArray();
8982
}
8983
8984
bool AnimationMarkerEdit::is_marker_selected(const StringName &p_marker) const {
8985
return selection.has(p_marker);
8986
}
8987
8988
bool AnimationMarkerEdit::is_key_selectable_by_distance() const {
8989
return true;
8990
}
8991
8992
void AnimationMarkerEdit::draw_key(const StringName &p_name, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) {
8993
if (animation.is_null()) {
8994
return;
8995
}
8996
8997
if (p_x < p_clip_left || p_x > p_clip_right) {
8998
return;
8999
}
9000
9001
Ref<Texture2D> icon_to_draw = p_selected ? selected_icon : type_icon;
9002
9003
Vector2 ofs(p_x - icon_to_draw->get_width() / 2, int(get_size().height - icon_to_draw->get_height()));
9004
9005
// Don't apply custom marker color when the key is selected.
9006
Color marker_color = p_selected ? Color(1, 1, 1) : animation->get_marker_color(p_name);
9007
9008
// Use a different color for the currently hovered key.
9009
// The color multiplier is chosen to work with both dark and light editor themes,
9010
// and on both unselected and selected key icons.
9011
draw_texture(
9012
icon_to_draw,
9013
ofs,
9014
p_name == hovering_marker ? get_theme_color(SNAME("folder_icon_color"), SNAME("FileDialog")) : marker_color);
9015
}
9016
9017
void AnimationMarkerEdit::draw_bg(int p_clip_left, int p_clip_right) {
9018
}
9019
9020
void AnimationMarkerEdit::draw_fg(int p_clip_left, int p_clip_right) {
9021
}
9022
9023
Ref<Animation> AnimationMarkerEdit::get_animation() const {
9024
return animation;
9025
}
9026
9027
void AnimationMarkerEdit::set_animation(const Ref<Animation> &p_animation, bool p_read_only) {
9028
if (animation.is_valid()) {
9029
_clear_selection_for_anim(animation);
9030
}
9031
animation = p_animation;
9032
read_only = p_read_only;
9033
type_icon = get_editor_theme_icon(SNAME("Marker"));
9034
selected_icon = get_editor_theme_icon(SNAME("MarkerSelected"));
9035
9036
queue_redraw();
9037
}
9038
9039
Size2 AnimationMarkerEdit::get_minimum_size() const {
9040
Ref<Texture2D> texture = get_editor_theme_icon(SNAME("Object"));
9041
Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label"));
9042
int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label"));
9043
int separation = get_theme_constant(SNAME("v_separation"), SNAME("ItemList"));
9044
9045
int max_h = MAX(texture->get_height(), font->get_height(font_size));
9046
max_h = MAX(max_h, get_key_height());
9047
9048
return Vector2(1, max_h + separation);
9049
}
9050
9051
void AnimationMarkerEdit::set_timeline(AnimationTimelineEdit *p_timeline) {
9052
timeline = p_timeline;
9053
timeline->connect("zoom_changed", callable_mp(this, &AnimationMarkerEdit::_zoom_changed));
9054
timeline->connect("name_limit_changed", callable_mp(this, &AnimationMarkerEdit::_zoom_changed));
9055
}
9056
9057
void AnimationMarkerEdit::set_editor(AnimationTrackEditor *p_editor) {
9058
editor = p_editor;
9059
}
9060
9061
void AnimationMarkerEdit::set_play_position(float p_pos) {
9062
play_position_pos = p_pos;
9063
play_position->queue_redraw();
9064
}
9065
9066
void AnimationMarkerEdit::update_play_position() {
9067
play_position->queue_redraw();
9068
}
9069
9070
void AnimationMarkerEdit::set_use_fps(bool p_use_fps) {
9071
if (key_edit) {
9072
key_edit->use_fps = p_use_fps;
9073
key_edit->notify_property_list_changed();
9074
}
9075
}
9076
9077
void AnimationMarkerEdit::_move_selection_begin() {
9078
moving_selection = true;
9079
moving_selection_offset = 0;
9080
}
9081
9082
void AnimationMarkerEdit::_move_selection(float p_offset) {
9083
moving_selection_offset = p_offset;
9084
queue_redraw();
9085
}
9086
9087
void AnimationMarkerEdit::_move_selection_commit() {
9088
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
9089
undo_redo->create_action(TTR("Animation Move Markers"));
9090
9091
for (HashSet<StringName>::Iterator E = selection.last(); E; --E) {
9092
StringName name = *E;
9093
double time = animation->get_marker_time(name);
9094
float newpos = time + moving_selection_offset;
9095
undo_redo->add_do_method(animation.ptr(), "remove_marker", name);
9096
undo_redo->add_do_method(animation.ptr(), "add_marker", name, newpos);
9097
undo_redo->add_do_method(animation.ptr(), "set_marker_color", name, animation->get_marker_color(name));
9098
undo_redo->add_undo_method(animation.ptr(), "remove_marker", name);
9099
undo_redo->add_undo_method(animation.ptr(), "add_marker", name, time);
9100
undo_redo->add_undo_method(animation.ptr(), "set_marker_color", name, animation->get_marker_color(name));
9101
9102
// add_marker will overwrite the overlapped key on the redo pass, so we add it back on the undo pass.
9103
if (StringName overlap = animation->get_marker_at_time(newpos)) {
9104
if (select_single_attempt == overlap) {
9105
select_single_attempt = "";
9106
}
9107
undo_redo->add_undo_method(animation.ptr(), "add_marker", overlap, newpos);
9108
undo_redo->add_undo_method(animation.ptr(), "set_marker_color", overlap, animation->get_marker_color(overlap));
9109
}
9110
}
9111
9112
moving_selection = false;
9113
AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();
9114
if (player) {
9115
PackedStringArray selected_section = get_selected_section();
9116
if (selected_section.size() >= 2) {
9117
undo_redo->add_do_method(player, "set_section_with_markers", selected_section[0], selected_section[1]);
9118
undo_redo->add_undo_method(player, "set_section_with_markers", selected_section[0], selected_section[1]);
9119
}
9120
}
9121
undo_redo->add_do_method(timeline, "queue_redraw");
9122
undo_redo->add_undo_method(timeline, "queue_redraw");
9123
undo_redo->add_do_method(this, "queue_redraw");
9124
undo_redo->add_undo_method(this, "queue_redraw");
9125
undo_redo->commit_action();
9126
_update_key_edit();
9127
}
9128
9129
void AnimationMarkerEdit::_delete_selected_markers() {
9130
if (selection.size()) {
9131
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
9132
undo_redo->create_action(TTR("Animation Delete Markers"));
9133
for (const StringName &name : selection) {
9134
double time = animation->get_marker_time(name);
9135
undo_redo->add_do_method(animation.ptr(), "remove_marker", name);
9136
undo_redo->add_undo_method(animation.ptr(), "add_marker", name, time);
9137
undo_redo->add_undo_method(animation.ptr(), "set_marker_color", name, animation->get_marker_color(name));
9138
}
9139
_clear_selection_for_anim(animation);
9140
9141
undo_redo->add_do_method(this, "queue_redraw");
9142
undo_redo->add_undo_method(this, "queue_redraw");
9143
undo_redo->commit_action();
9144
_update_key_edit();
9145
}
9146
}
9147
9148
void AnimationMarkerEdit::_move_selection_cancel() {
9149
moving_selection = false;
9150
queue_redraw();
9151
}
9152
9153
void AnimationMarkerEdit::_clear_selection(bool p_update) {
9154
AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();
9155
if (player) {
9156
player->reset_section();
9157
}
9158
9159
selection.clear();
9160
9161
if (p_update) {
9162
queue_redraw();
9163
}
9164
9165
_clear_key_edit();
9166
}
9167
9168
void AnimationMarkerEdit::_clear_selection_for_anim(const Ref<Animation> &p_anim) {
9169
if (animation != p_anim) {
9170
return;
9171
}
9172
9173
_clear_selection(true);
9174
}
9175
9176
void AnimationMarkerEdit::_select_key(const StringName &p_name, bool is_single) {
9177
if (is_single) {
9178
_clear_selection(false);
9179
}
9180
9181
selection.insert(p_name);
9182
9183
AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();
9184
if (player) {
9185
if (selection.size() >= 2) {
9186
PackedStringArray selected_section = get_selected_section();
9187
double start_time = animation->get_marker_time(selected_section[0]);
9188
double end_time = animation->get_marker_time(selected_section[1]);
9189
player->set_section(start_time, end_time);
9190
} else {
9191
player->reset_section();
9192
}
9193
}
9194
9195
queue_redraw();
9196
_update_key_edit();
9197
9198
editor->_clear_selection(editor->is_selection_active());
9199
}
9200
9201
void AnimationMarkerEdit::_deselect_key(const StringName &p_name) {
9202
selection.erase(p_name);
9203
9204
AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();
9205
if (player) {
9206
if (selection.size() >= 2) {
9207
PackedStringArray selected_section = get_selected_section();
9208
double start_time = animation->get_marker_time(selected_section[0]);
9209
double end_time = animation->get_marker_time(selected_section[1]);
9210
player->set_section(start_time, end_time);
9211
} else {
9212
player->reset_section();
9213
}
9214
}
9215
9216
queue_redraw();
9217
_update_key_edit();
9218
}
9219
9220
void AnimationMarkerEdit::_insert_marker(float p_ofs) {
9221
if (editor->is_snap_timeline_enabled()) {
9222
p_ofs = editor->snap_time(p_ofs);
9223
}
9224
9225
marker_insert_confirm->popup_centered(Size2(200, 100) * EDSCALE);
9226
marker_insert_color->set_pick_color(Color(1, 1, 1));
9227
9228
String base = "new_marker";
9229
int count = 1;
9230
while (true) {
9231
String attempt = base;
9232
if (count > 1) {
9233
attempt += vformat("_%d", count);
9234
}
9235
if (animation->has_marker(attempt)) {
9236
count++;
9237
continue;
9238
}
9239
base = attempt;
9240
break;
9241
}
9242
9243
marker_insert_new_name->set_text(base);
9244
_marker_insert_new_name_changed(base);
9245
marker_insert_ofs = p_ofs;
9246
}
9247
9248
void AnimationMarkerEdit::_rename_marker(const StringName &p_name) {
9249
marker_rename_confirm->popup_centered(Size2i(200, 0) * EDSCALE);
9250
marker_rename_prev_name = p_name;
9251
marker_rename_new_name->set_text(p_name);
9252
}
9253
9254
void AnimationMarkerEdit::_marker_insert_confirmed() {
9255
StringName name = marker_insert_new_name->get_text();
9256
9257
if (animation->has_marker(name)) {
9258
marker_insert_error_dialog->set_text(vformat(TTR("Marker '%s' already exists!"), name));
9259
marker_insert_error_dialog->popup_centered();
9260
return;
9261
}
9262
9263
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
9264
9265
undo_redo->create_action(TTR("Add Marker Key"));
9266
undo_redo->add_do_method(animation.ptr(), "add_marker", name, marker_insert_ofs);
9267
undo_redo->add_undo_method(animation.ptr(), "remove_marker", name);
9268
StringName existing_marker = animation->get_marker_at_time(marker_insert_ofs);
9269
if (existing_marker) {
9270
undo_redo->add_undo_method(animation.ptr(), "add_marker", existing_marker, marker_insert_ofs);
9271
undo_redo->add_undo_method(animation.ptr(), "set_marker_color", existing_marker, animation->get_marker_color(existing_marker));
9272
}
9273
undo_redo->add_do_method(animation.ptr(), "set_marker_color", name, marker_insert_color->get_pick_color());
9274
9275
undo_redo->add_do_method(this, "queue_redraw");
9276
undo_redo->add_undo_method(this, "queue_redraw");
9277
9278
undo_redo->commit_action();
9279
9280
marker_insert_confirm->hide();
9281
}
9282
9283
void AnimationMarkerEdit::_marker_insert_new_name_changed(const String &p_text) {
9284
marker_insert_confirm->get_ok_button()->set_disabled(p_text.is_empty());
9285
}
9286
9287
void AnimationMarkerEdit::_marker_rename_confirmed() {
9288
StringName new_name = marker_rename_new_name->get_text();
9289
StringName prev_name = marker_rename_prev_name;
9290
9291
if (new_name == StringName()) {
9292
marker_rename_error_dialog->set_text(TTR("Empty marker names are not allowed."));
9293
marker_rename_error_dialog->popup_centered();
9294
return;
9295
}
9296
9297
if (new_name != prev_name && animation->has_marker(new_name)) {
9298
marker_rename_error_dialog->set_text(vformat(TTR("Marker '%s' already exists!"), new_name));
9299
marker_rename_error_dialog->popup_centered();
9300
return;
9301
}
9302
9303
if (prev_name != new_name) {
9304
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
9305
undo_redo->create_action(TTR("Rename Marker"));
9306
undo_redo->add_do_method(animation.ptr(), "remove_marker", prev_name);
9307
undo_redo->add_do_method(animation.ptr(), "add_marker", new_name, animation->get_marker_time(prev_name));
9308
undo_redo->add_do_method(animation.ptr(), "set_marker_color", new_name, animation->get_marker_color(prev_name));
9309
undo_redo->add_undo_method(animation.ptr(), "remove_marker", new_name);
9310
undo_redo->add_undo_method(animation.ptr(), "add_marker", prev_name, animation->get_marker_time(prev_name));
9311
undo_redo->add_undo_method(animation.ptr(), "set_marker_color", prev_name, animation->get_marker_color(prev_name));
9312
undo_redo->add_do_method(this, "_select_key", new_name, true);
9313
undo_redo->add_undo_method(this, "_select_key", prev_name, true);
9314
undo_redo->commit_action();
9315
select_single_attempt = StringName();
9316
}
9317
marker_rename_confirm->hide();
9318
}
9319
9320
void AnimationMarkerEdit::_marker_rename_new_name_changed(const String &p_text) {
9321
marker_rename_confirm->get_ok_button()->set_disabled(p_text.is_empty());
9322
}
9323
9324
AnimationMarkerEdit::AnimationMarkerEdit() {
9325
play_position = memnew(Control);
9326
play_position->set_mouse_filter(MOUSE_FILTER_PASS);
9327
add_child(play_position);
9328
play_position->connect(SceneStringName(draw), callable_mp(this, &AnimationMarkerEdit::_play_position_draw));
9329
set_focus_mode(FOCUS_CLICK);
9330
set_mouse_filter(MOUSE_FILTER_PASS); // Scroll has to work too for selection.
9331
9332
menu = memnew(PopupMenu);
9333
add_child(menu);
9334
menu->connect(SceneStringName(id_pressed), callable_mp(this, &AnimationMarkerEdit::_menu_selected));
9335
menu->add_shortcut(ED_SHORTCUT("animation_marker_edit/rename_marker", TTRC("Rename Marker"), Key::R), MENU_KEY_RENAME);
9336
menu->add_shortcut(ED_SHORTCUT("animation_marker_edit/delete_selection", TTRC("Delete Marker(s)"), Key::KEY_DELETE), MENU_KEY_DELETE);
9337
menu->add_shortcut(ED_SHORTCUT("animation_marker_edit/toggle_marker_names", TTRC("Show All Marker Names"), Key::M), MENU_KEY_TOGGLE_MARKER_NAMES);
9338
9339
marker_insert_confirm = memnew(ConfirmationDialog);
9340
marker_insert_confirm->set_title(TTR("Insert Marker"));
9341
marker_insert_confirm->set_hide_on_ok(false);
9342
marker_insert_confirm->connect(SceneStringName(confirmed), callable_mp(this, &AnimationMarkerEdit::_marker_insert_confirmed));
9343
add_child(marker_insert_confirm);
9344
VBoxContainer *marker_insert_vbox = memnew(VBoxContainer);
9345
marker_insert_vbox->set_anchors_and_offsets_preset(Control::LayoutPreset::PRESET_FULL_RECT);
9346
marker_insert_confirm->add_child(marker_insert_vbox);
9347
marker_insert_new_name = memnew(LineEdit);
9348
marker_insert_new_name->connect(SceneStringName(text_changed), callable_mp(this, &AnimationMarkerEdit::_marker_insert_new_name_changed));
9349
marker_insert_confirm->register_text_enter(marker_insert_new_name);
9350
marker_insert_vbox->add_child(_create_hbox_labeled_control(TTR("Marker Name"), marker_insert_new_name));
9351
marker_insert_color = memnew(ColorPickerButton);
9352
marker_insert_color->set_edit_alpha(false);
9353
marker_insert_color->get_popup()->connect("about_to_popup", callable_mp(EditorNode::get_singleton(), &EditorNode::setup_color_picker).bind(marker_insert_color->get_picker()));
9354
marker_insert_vbox->add_child(_create_hbox_labeled_control(TTR("Marker Color"), marker_insert_color));
9355
marker_insert_error_dialog = memnew(AcceptDialog);
9356
marker_insert_error_dialog->set_ok_button_text(TTR("Close"));
9357
marker_insert_error_dialog->set_title(TTR("Error!"));
9358
marker_insert_confirm->add_child(marker_insert_error_dialog);
9359
9360
marker_rename_confirm = memnew(ConfirmationDialog);
9361
marker_rename_confirm->set_title(TTR("Rename Marker"));
9362
marker_rename_confirm->set_hide_on_ok(false);
9363
marker_rename_confirm->connect(SceneStringName(confirmed), callable_mp(this, &AnimationMarkerEdit::_marker_rename_confirmed));
9364
add_child(marker_rename_confirm);
9365
VBoxContainer *marker_rename_vbox = memnew(VBoxContainer);
9366
marker_rename_vbox->set_anchors_and_offsets_preset(Control::LayoutPreset::PRESET_FULL_RECT);
9367
marker_rename_confirm->add_child(marker_rename_vbox);
9368
Label *marker_rename_new_name_label = memnew(Label);
9369
marker_rename_new_name_label->set_text(TTR("Change Marker Name:"));
9370
marker_rename_vbox->add_child(marker_rename_new_name_label);
9371
marker_rename_new_name = memnew(LineEdit);
9372
marker_rename_new_name->set_accessibility_name(TTRC("Change Marker Name:"));
9373
marker_rename_new_name->connect(SceneStringName(text_changed), callable_mp(this, &AnimationMarkerEdit::_marker_rename_new_name_changed));
9374
marker_rename_confirm->register_text_enter(marker_rename_new_name);
9375
marker_rename_vbox->add_child(marker_rename_new_name);
9376
9377
marker_rename_error_dialog = memnew(AcceptDialog);
9378
marker_rename_error_dialog->set_ok_button_text(TTR("Close"));
9379
marker_rename_error_dialog->set_title(TTR("Error!"));
9380
marker_rename_confirm->add_child(marker_rename_error_dialog);
9381
}
9382
9383
float AnimationMarkerKeyEdit::get_time() const {
9384
return animation->get_marker_time(marker_name);
9385
}
9386
9387
void AnimationMarkerKeyEdit::_bind_methods() {
9388
ClassDB::bind_method(D_METHOD("_hide_script_from_inspector"), &AnimationMarkerKeyEdit::_hide_script_from_inspector);
9389
ClassDB::bind_method(D_METHOD("_hide_metadata_from_inspector"), &AnimationMarkerKeyEdit::_hide_metadata_from_inspector);
9390
ClassDB::bind_method(D_METHOD("_dont_undo_redo"), &AnimationMarkerKeyEdit::_dont_undo_redo);
9391
ClassDB::bind_method(D_METHOD("_is_read_only"), &AnimationMarkerKeyEdit::_is_read_only);
9392
ClassDB::bind_method(D_METHOD("_set_marker_name"), &AnimationMarkerKeyEdit::_set_marker_name);
9393
}
9394
9395
void AnimationMarkerKeyEdit::_set_marker_name(const StringName &p_name) {
9396
marker_name = p_name;
9397
}
9398
9399
bool AnimationMarkerKeyEdit::_set(const StringName &p_name, const Variant &p_value) {
9400
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
9401
9402
if (p_name == "color") {
9403
Color color = p_value;
9404
Color prev_color = animation->get_marker_color(marker_name);
9405
if (color != prev_color) {
9406
undo_redo->create_action(TTR("Edit Marker Color"), UndoRedo::MERGE_ENDS);
9407
undo_redo->add_do_method(animation.ptr(), "set_marker_color", marker_name, color);
9408
undo_redo->add_undo_method(animation.ptr(), "set_marker_color", marker_name, prev_color);
9409
undo_redo->add_do_method(marker_edit, "queue_redraw");
9410
undo_redo->add_undo_method(marker_edit, "queue_redraw");
9411
undo_redo->commit_action();
9412
}
9413
return true;
9414
}
9415
9416
return false;
9417
}
9418
9419
bool AnimationMarkerKeyEdit::_get(const StringName &p_name, Variant &r_ret) const {
9420
if (p_name == "name") {
9421
r_ret = marker_name;
9422
return true;
9423
}
9424
9425
if (p_name == "color") {
9426
r_ret = animation->get_marker_color(marker_name);
9427
return true;
9428
}
9429
9430
return false;
9431
}
9432
9433
void AnimationMarkerKeyEdit::_get_property_list(List<PropertyInfo> *p_list) const {
9434
if (animation.is_null()) {
9435
return;
9436
}
9437
9438
p_list->push_back(PropertyInfo(Variant::STRING_NAME, "name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_READ_ONLY | PROPERTY_USAGE_EDITOR));
9439
p_list->push_back(PropertyInfo(Variant::COLOR, "color", PROPERTY_HINT_COLOR_NO_ALPHA));
9440
}
9441
9442
void AnimationMultiMarkerKeyEdit::_bind_methods() {
9443
ClassDB::bind_method(D_METHOD("_hide_script_from_inspector"), &AnimationMultiMarkerKeyEdit::_hide_script_from_inspector);
9444
ClassDB::bind_method(D_METHOD("_hide_metadata_from_inspector"), &AnimationMultiMarkerKeyEdit::_hide_metadata_from_inspector);
9445
ClassDB::bind_method(D_METHOD("_dont_undo_redo"), &AnimationMultiMarkerKeyEdit::_dont_undo_redo);
9446
ClassDB::bind_method(D_METHOD("_is_read_only"), &AnimationMultiMarkerKeyEdit::_is_read_only);
9447
}
9448
9449
bool AnimationMultiMarkerKeyEdit::_set(const StringName &p_name, const Variant &p_value) {
9450
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
9451
if (p_name == "color") {
9452
Color color = p_value;
9453
9454
undo_redo->create_action(TTR("Multi Edit Marker Color"), UndoRedo::MERGE_ENDS);
9455
9456
for (const StringName &marker_name : marker_names) {
9457
undo_redo->add_do_method(animation.ptr(), "set_marker_color", marker_name, color);
9458
undo_redo->add_undo_method(animation.ptr(), "set_marker_color", marker_name, animation->get_marker_color(marker_name));
9459
}
9460
9461
undo_redo->add_do_method(marker_edit, "queue_redraw");
9462
undo_redo->add_undo_method(marker_edit, "queue_redraw");
9463
undo_redo->commit_action();
9464
9465
return true;
9466
}
9467
9468
return false;
9469
}
9470
9471
bool AnimationMultiMarkerKeyEdit::_get(const StringName &p_name, Variant &r_ret) const {
9472
if (p_name == "color") {
9473
r_ret = animation->get_marker_color(marker_names[0]);
9474
return true;
9475
}
9476
9477
return false;
9478
}
9479
9480
void AnimationMultiMarkerKeyEdit::_get_property_list(List<PropertyInfo> *p_list) const {
9481
if (animation.is_null()) {
9482
return;
9483
}
9484
9485
p_list->push_back(PropertyInfo(Variant::COLOR, "color", PROPERTY_HINT_COLOR_NO_ALPHA));
9486
}
9487
9488
// AnimationMarkerKeyEditEditorPlugin
9489
9490
void AnimationMarkerKeyEditEditor::_time_edit_exited() {
9491
real_t new_time = spinner->get_value();
9492
9493
if (use_fps) {
9494
real_t fps = animation->get_step();
9495
if (fps > 0) {
9496
fps = 1.0 / fps;
9497
}
9498
new_time /= fps;
9499
}
9500
9501
real_t prev_time = animation->get_marker_time(marker_name);
9502
9503
if (Math::is_equal_approx(new_time, prev_time)) {
9504
return; // No change.
9505
}
9506
9507
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
9508
undo_redo->create_action(TTR("Animation Change Marker Time"));
9509
9510
Color color = animation->get_marker_color(marker_name);
9511
undo_redo->add_do_method(animation.ptr(), "add_marker", marker_name, new_time);
9512
undo_redo->add_do_method(animation.ptr(), "set_marker_color", marker_name, color);
9513
undo_redo->add_undo_method(animation.ptr(), "remove_marker", marker_name);
9514
undo_redo->add_undo_method(animation.ptr(), "add_marker", marker_name, prev_time);
9515
undo_redo->add_undo_method(animation.ptr(), "set_marker_color", marker_name, color);
9516
StringName existing_marker = animation->get_marker_at_time(new_time);
9517
if (existing_marker) {
9518
undo_redo->add_undo_method(animation.ptr(), "add_marker", existing_marker, animation->get_marker_time(existing_marker));
9519
undo_redo->add_undo_method(animation.ptr(), "set_marker_color", existing_marker, animation->get_marker_color(existing_marker));
9520
}
9521
AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
9522
if (ape) {
9523
AnimationTrackEditor *ate = ape->get_track_editor();
9524
if (ate) {
9525
AnimationMarkerEdit *ame = ate->marker_edit;
9526
undo_redo->add_do_method(ame, "queue_redraw");
9527
undo_redo->add_undo_method(ame, "queue_redraw");
9528
}
9529
}
9530
undo_redo->commit_action();
9531
}
9532
9533
AnimationMarkerKeyEditEditor::AnimationMarkerKeyEditEditor(Ref<Animation> p_animation, const StringName &p_name, bool p_use_fps) {
9534
if (p_animation.is_null()) {
9535
return;
9536
}
9537
9538
animation = p_animation;
9539
use_fps = p_use_fps;
9540
marker_name = p_name;
9541
9542
set_label("Time");
9543
9544
spinner = memnew(EditorSpinSlider);
9545
spinner->set_focus_mode(Control::FOCUS_CLICK);
9546
spinner->set_min(0);
9547
spinner->set_allow_greater(true);
9548
spinner->set_allow_lesser(true);
9549
add_child(spinner);
9550
9551
float time = animation->get_marker_time(marker_name);
9552
9553
if (use_fps) {
9554
spinner->set_step(FPS_DECIMAL);
9555
real_t fps = animation->get_step();
9556
if (fps > 0) {
9557
fps = 1.0 / fps;
9558
}
9559
spinner->set_value(time * fps);
9560
spinner->connect("updown_pressed", callable_mp(this, &AnimationMarkerKeyEditEditor::_time_edit_exited), CONNECT_DEFERRED);
9561
} else {
9562
spinner->set_step(SECOND_DECIMAL);
9563
spinner->set_value(time);
9564
spinner->set_max(animation->get_length());
9565
}
9566
9567
spinner->connect("ungrabbed", callable_mp(this, &AnimationMarkerKeyEditEditor::_time_edit_exited), CONNECT_DEFERRED);
9568
spinner->connect("value_focus_exited", callable_mp(this, &AnimationMarkerKeyEditEditor::_time_edit_exited), CONNECT_DEFERRED);
9569
}
9570
9571