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