1/**************************************************************************/
2/* animation_track_editor_plugins.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_plugins.h"
32
33#include "editor/audio_stream_preview.h"
34#include "editor/editor_resource_preview.h"
35#include "editor/editor_scale.h"
36#include "editor/editor_string_names.h"
37#include "editor/editor_undo_redo_manager.h"
38#include "scene/2d/animated_sprite_2d.h"
39#include "scene/2d/sprite_2d.h"
40#include "scene/3d/sprite_3d.h"
41#include "scene/animation/animation_player.h"
42#include "scene/resources/text_line.h"
43#include "servers/audio/audio_stream.h"
44
45/// BOOL ///
46int AnimationTrackEditBool::get_key_height() const {
47 Ref<Texture2D> checked = get_theme_icon(SNAME("checked"), SNAME("CheckBox"));
48 return checked->get_height();
49}
50
51Rect2 AnimationTrackEditBool::get_key_rect(int p_index, float p_pixels_sec) {
52 Ref<Texture2D> checked = get_theme_icon(SNAME("checked"), SNAME("CheckBox"));
53 return Rect2(-checked->get_width() / 2, 0, checked->get_width(), get_size().height);
54}
55
56bool AnimationTrackEditBool::is_key_selectable_by_distance() const {
57 return false;
58}
59
60void AnimationTrackEditBool::draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) {
61 bool checked = get_animation()->track_get_key_value(get_track(), p_index);
62 Ref<Texture2D> icon = get_theme_icon(checked ? "checked" : "unchecked", "CheckBox");
63
64 Vector2 ofs(p_x - icon->get_width() / 2, int(get_size().height - icon->get_height()) / 2);
65
66 if (ofs.x + icon->get_width() / 2 < p_clip_left) {
67 return;
68 }
69
70 if (ofs.x + icon->get_width() / 2 > p_clip_right) {
71 return;
72 }
73
74 draw_texture(icon, ofs);
75
76 if (p_selected) {
77 Color color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
78 draw_rect_clipped(Rect2(ofs, icon->get_size()), color, false);
79 }
80}
81
82/// COLOR ///
83
84int AnimationTrackEditColor::get_key_height() const {
85 Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
86 int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
87 return font->get_height(font_size) * 0.8;
88}
89
90Rect2 AnimationTrackEditColor::get_key_rect(int p_index, float p_pixels_sec) {
91 Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
92 int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
93 int fh = font->get_height(font_size) * 0.8;
94 return Rect2(-fh / 2, 0, fh, get_size().height);
95}
96
97bool AnimationTrackEditColor::is_key_selectable_by_distance() const {
98 return false;
99}
100
101void AnimationTrackEditColor::draw_key_link(int p_index, float p_pixels_sec, int p_x, int p_next_x, int p_clip_left, int p_clip_right) {
102 Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
103 int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
104 int fh = (font->get_height(font_size) * 0.8);
105
106 fh /= 3;
107
108 int x_from = p_x + fh / 2 - 1;
109 int x_to = p_next_x - fh / 2 + 1;
110 x_from = MAX(x_from, p_clip_left);
111 x_to = MIN(x_to, p_clip_right);
112
113 int y_from = (get_size().height - fh) / 2;
114
115 if (x_from > p_clip_right || x_to < p_clip_left) {
116 return;
117 }
118
119 Vector<Color> color_samples;
120 color_samples.append(get_animation()->track_get_key_value(get_track(), p_index));
121
122 if (get_animation()->track_get_type(get_track()) == Animation::TYPE_VALUE) {
123 if (get_animation()->track_get_interpolation_type(get_track()) != Animation::INTERPOLATION_NEAREST &&
124 (get_animation()->value_track_get_update_mode(get_track()) == Animation::UPDATE_CONTINUOUS ||
125 get_animation()->value_track_get_update_mode(get_track()) == Animation::UPDATE_CAPTURE) &&
126 !Math::is_zero_approx(get_animation()->track_get_key_transition(get_track(), p_index))) {
127 float start_time = get_animation()->track_get_key_time(get_track(), p_index);
128 float end_time = get_animation()->track_get_key_time(get_track(), p_index + 1);
129
130 Color color_next = get_animation()->value_track_interpolate(get_track(), end_time);
131
132 if (!color_samples[0].is_equal_approx(color_next)) {
133 color_samples.resize(1 + (x_to - x_from) / 64); // Make a color sample every 64 px.
134 for (int i = 1; i < color_samples.size(); i++) {
135 float j = i;
136 color_samples.write[i] = get_animation()->value_track_interpolate(
137 get_track(),
138 Math::lerp(start_time, end_time, j / color_samples.size()));
139 }
140 }
141 color_samples.append(color_next);
142 } else {
143 color_samples.append(color_samples[0]);
144 }
145 } else {
146 color_samples.append(get_animation()->track_get_key_value(get_track(), p_index + 1));
147 }
148
149 for (int i = 0; i < color_samples.size() - 1; i++) {
150 Vector<Vector2> points = {
151 Vector2(Math::lerp(x_from, x_to, float(i) / (color_samples.size() - 1)), y_from),
152 Vector2(Math::lerp(x_from, x_to, float(i + 1) / (color_samples.size() - 1)), y_from),
153 Vector2(Math::lerp(x_from, x_to, float(i + 1) / (color_samples.size() - 1)), y_from + fh),
154 Vector2(Math::lerp(x_from, x_to, float(i) / (color_samples.size() - 1)), y_from + fh)
155 };
156
157 Vector<Color> colors = {
158 color_samples[i],
159 color_samples[i + 1],
160 color_samples[i + 1],
161 color_samples[i]
162 };
163
164 draw_primitive(points, colors, Vector<Vector2>());
165 }
166}
167
168void AnimationTrackEditColor::draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) {
169 Color color = get_animation()->track_get_key_value(get_track(), p_index);
170
171 Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
172 int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
173 int fh = font->get_height(font_size) * 0.8;
174
175 Rect2 rect(Vector2(p_x - fh / 2, int(get_size().height - fh) / 2), Size2(fh, fh));
176
177 draw_rect_clipped(Rect2(rect.position, rect.size / 2), Color(0.4, 0.4, 0.4));
178 draw_rect_clipped(Rect2(rect.position + rect.size / 2, rect.size / 2), Color(0.4, 0.4, 0.4));
179 draw_rect_clipped(Rect2(rect.position + Vector2(rect.size.x / 2, 0), rect.size / 2), Color(0.6, 0.6, 0.6));
180 draw_rect_clipped(Rect2(rect.position + Vector2(0, rect.size.y / 2), rect.size / 2), Color(0.6, 0.6, 0.6));
181 draw_rect_clipped(rect, color);
182
183 if (p_selected) {
184 Color accent = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
185 draw_rect_clipped(rect, accent, false);
186 }
187}
188
189/// AUDIO ///
190
191void AnimationTrackEditAudio::_preview_changed(ObjectID p_which) {
192 Object *object = ObjectDB::get_instance(id);
193
194 if (!object) {
195 return;
196 }
197
198 Ref<AudioStream> stream = object->call("get_stream");
199
200 if (stream.is_valid() && stream->get_instance_id() == p_which) {
201 queue_redraw();
202 }
203}
204
205int AnimationTrackEditAudio::get_key_height() const {
206 if (!ObjectDB::get_instance(id)) {
207 return AnimationTrackEdit::get_key_height();
208 }
209
210 Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
211 int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
212 return int(font->get_height(font_size) * 1.5);
213}
214
215Rect2 AnimationTrackEditAudio::get_key_rect(int p_index, float p_pixels_sec) {
216 Object *object = ObjectDB::get_instance(id);
217
218 if (!object) {
219 return AnimationTrackEdit::get_key_rect(p_index, p_pixels_sec);
220 }
221
222 Ref<AudioStream> stream = object->call("get_stream");
223
224 if (!stream.is_valid()) {
225 return AnimationTrackEdit::get_key_rect(p_index, p_pixels_sec);
226 }
227
228 bool play = get_animation()->track_get_key_value(get_track(), p_index);
229 if (play) {
230 float len = stream->get_length();
231
232 if (len == 0) {
233 Ref<AudioStreamPreview> preview = AudioStreamPreviewGenerator::get_singleton()->generate_preview(stream);
234 len = preview->get_length();
235 }
236
237 if (get_animation()->track_get_key_count(get_track()) > p_index + 1) {
238 len = MIN(len, get_animation()->track_get_key_time(get_track(), p_index + 1) - get_animation()->track_get_key_time(get_track(), p_index));
239 }
240
241 return Rect2(0, 0, len * p_pixels_sec, get_size().height);
242 } else {
243 Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
244 int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
245 int fh = font->get_height(font_size) * 0.8;
246 return Rect2(0, 0, fh, get_size().height);
247 }
248}
249
250bool AnimationTrackEditAudio::is_key_selectable_by_distance() const {
251 return false;
252}
253
254void AnimationTrackEditAudio::draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) {
255 Object *object = ObjectDB::get_instance(id);
256
257 if (!object) {
258 AnimationTrackEdit::draw_key(p_index, p_pixels_sec, p_x, p_selected, p_clip_left, p_clip_right);
259 return;
260 }
261
262 Ref<AudioStream> stream = object->call("get_stream");
263
264 if (!stream.is_valid()) {
265 AnimationTrackEdit::draw_key(p_index, p_pixels_sec, p_x, p_selected, p_clip_left, p_clip_right);
266 return;
267 }
268
269 bool play = get_animation()->track_get_key_value(get_track(), p_index);
270 if (play) {
271 float len = stream->get_length();
272
273 Ref<AudioStreamPreview> preview = AudioStreamPreviewGenerator::get_singleton()->generate_preview(stream);
274
275 float preview_len = preview->get_length();
276
277 if (len == 0) {
278 len = preview_len;
279 }
280
281 int pixel_len = len * p_pixels_sec;
282
283 int pixel_begin = p_x;
284 int pixel_end = p_x + pixel_len;
285
286 if (pixel_end < p_clip_left) {
287 return;
288 }
289
290 if (pixel_begin > p_clip_right) {
291 return;
292 }
293
294 int from_x = MAX(pixel_begin, p_clip_left);
295 int to_x = MIN(pixel_end, p_clip_right);
296
297 if (get_animation()->track_get_key_count(get_track()) > p_index + 1) {
298 float limit = MIN(len, get_animation()->track_get_key_time(get_track(), p_index + 1) - get_animation()->track_get_key_time(get_track(), p_index));
299 int limit_x = pixel_begin + limit * p_pixels_sec;
300 to_x = MIN(limit_x, to_x);
301 }
302
303 if (to_x <= from_x) {
304 return;
305 }
306
307 Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
308 int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
309 float fh = int(font->get_height(font_size) * 1.5);
310 Rect2 rect = Rect2(from_x, (get_size().height - fh) / 2, to_x - from_x, fh);
311 draw_rect(rect, Color(0.25, 0.25, 0.25));
312
313 Vector<Vector2> points;
314 points.resize((to_x - from_x) * 2);
315 preview_len = preview->get_length();
316
317 for (int i = from_x; i < to_x; i++) {
318 float ofs = (i - pixel_begin) * preview_len / pixel_len;
319 float ofs_n = ((i + 1) - pixel_begin) * preview_len / pixel_len;
320 float max = preview->get_max(ofs, ofs_n) * 0.5 + 0.5;
321 float min = preview->get_min(ofs, ofs_n) * 0.5 + 0.5;
322
323 int idx = i - from_x;
324 points.write[idx * 2 + 0] = Vector2(i, rect.position.y + min * rect.size.y);
325 points.write[idx * 2 + 1] = Vector2(i, rect.position.y + max * rect.size.y);
326 }
327
328 Vector<Color> colors = { Color(0.75, 0.75, 0.75) };
329
330 RS::get_singleton()->canvas_item_add_multiline(get_canvas_item(), points, colors);
331
332 if (p_selected) {
333 Color accent = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
334 draw_rect(rect, accent, false);
335 }
336 } else {
337 Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
338 int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
339 int fh = font->get_height(font_size) * 0.8;
340 Rect2 rect(Vector2(p_x, int(get_size().height - fh) / 2), Size2(fh, fh));
341
342 Color color = get_theme_color(SNAME("font_color"), SNAME("Label"));
343 draw_rect_clipped(rect, color);
344
345 if (p_selected) {
346 Color accent = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
347 draw_rect_clipped(rect, accent, false);
348 }
349 }
350}
351
352void AnimationTrackEditAudio::set_node(Object *p_object) {
353 id = p_object->get_instance_id();
354}
355
356void AnimationTrackEditAudio::_bind_methods() {
357}
358
359AnimationTrackEditAudio::AnimationTrackEditAudio() {
360 AudioStreamPreviewGenerator::get_singleton()->connect("preview_updated", callable_mp(this, &AnimationTrackEditAudio::_preview_changed));
361}
362
363/// SPRITE FRAME / FRAME_COORDS ///
364
365int AnimationTrackEditSpriteFrame::get_key_height() const {
366 if (!ObjectDB::get_instance(id)) {
367 return AnimationTrackEdit::get_key_height();
368 }
369
370 Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
371 int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
372 return int(font->get_height(font_size) * 2);
373}
374
375Rect2 AnimationTrackEditSpriteFrame::get_key_rect(int p_index, float p_pixels_sec) {
376 Object *object = ObjectDB::get_instance(id);
377
378 if (!object) {
379 return AnimationTrackEdit::get_key_rect(p_index, p_pixels_sec);
380 }
381
382 Size2 size;
383
384 if (Object::cast_to<Sprite2D>(object) || Object::cast_to<Sprite3D>(object)) {
385 Ref<Texture2D> texture = object->call("get_texture");
386 if (!texture.is_valid()) {
387 return AnimationTrackEdit::get_key_rect(p_index, p_pixels_sec);
388 }
389
390 size = texture->get_size();
391
392 if (bool(object->call("is_region_enabled"))) {
393 size = Rect2(object->call("get_region_rect")).size;
394 }
395
396 int hframes = object->call("get_hframes");
397 int vframes = object->call("get_vframes");
398
399 if (hframes > 1) {
400 size.x /= hframes;
401 }
402 if (vframes > 1) {
403 size.y /= vframes;
404 }
405 } else if (Object::cast_to<AnimatedSprite2D>(object) || Object::cast_to<AnimatedSprite3D>(object)) {
406 Ref<SpriteFrames> sf = object->call("get_sprite_frames");
407 if (sf.is_null()) {
408 return AnimationTrackEdit::get_key_rect(p_index, p_pixels_sec);
409 }
410
411 List<StringName> animations;
412 sf->get_animation_list(&animations);
413
414 int frame = get_animation()->track_get_key_value(get_track(), p_index);
415 String animation_name;
416 if (animations.size() == 1) {
417 animation_name = animations.front()->get();
418 } else {
419 // Go through other track to find if animation is set
420 String animation_path = get_animation()->track_get_path(get_track());
421 animation_path = animation_path.replace(":frame", ":animation");
422 int animation_track = get_animation()->find_track(animation_path, get_animation()->track_get_type(get_track()));
423 float track_time = get_animation()->track_get_key_time(get_track(), p_index);
424 int animaiton_index = get_animation()->track_find_key(animation_track, track_time);
425 animation_name = get_animation()->track_get_key_value(animation_track, animaiton_index);
426 }
427
428 Ref<Texture2D> texture = sf->get_frame_texture(animation_name, frame);
429 if (!texture.is_valid()) {
430 return AnimationTrackEdit::get_key_rect(p_index, p_pixels_sec);
431 }
432
433 size = texture->get_size();
434 }
435
436 size = size.floor();
437
438 Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
439 int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
440 int height = int(font->get_height(font_size) * 2);
441 int width = height * size.width / size.height;
442
443 return Rect2(0, 0, width, get_size().height);
444}
445
446bool AnimationTrackEditSpriteFrame::is_key_selectable_by_distance() const {
447 return false;
448}
449
450void AnimationTrackEditSpriteFrame::draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) {
451 Object *object = ObjectDB::get_instance(id);
452
453 if (!object) {
454 AnimationTrackEdit::draw_key(p_index, p_pixels_sec, p_x, p_selected, p_clip_left, p_clip_right);
455 return;
456 }
457
458 Ref<Texture2D> texture;
459 Rect2 region;
460
461 if (Object::cast_to<Sprite2D>(object) || Object::cast_to<Sprite3D>(object)) {
462 texture = object->call("get_texture");
463 if (!texture.is_valid()) {
464 AnimationTrackEdit::draw_key(p_index, p_pixels_sec, p_x, p_selected, p_clip_left, p_clip_right);
465 return;
466 }
467
468 int hframes = object->call("get_hframes");
469 int vframes = object->call("get_vframes");
470
471 Vector2 coords;
472 if (is_coords) {
473 coords = get_animation()->track_get_key_value(get_track(), p_index);
474 } else {
475 int frame = get_animation()->track_get_key_value(get_track(), p_index);
476 coords.x = frame % hframes;
477 coords.y = frame / hframes;
478 }
479
480 region.size = texture->get_size();
481
482 if (bool(object->call("is_region_enabled"))) {
483 region = Rect2(object->call("get_region_rect"));
484 }
485
486 if (hframes > 1) {
487 region.size.x /= hframes;
488 }
489 if (vframes > 1) {
490 region.size.y /= vframes;
491 }
492
493 region.position.x += region.size.x * coords.x;
494 region.position.y += region.size.y * coords.y;
495
496 } else if (Object::cast_to<AnimatedSprite2D>(object) || Object::cast_to<AnimatedSprite3D>(object)) {
497 Ref<SpriteFrames> sf = object->call("get_sprite_frames");
498 if (sf.is_null()) {
499 AnimationTrackEdit::draw_key(p_index, p_pixels_sec, p_x, p_selected, p_clip_left, p_clip_right);
500 return;
501 }
502
503 List<StringName> animations;
504 sf->get_animation_list(&animations);
505
506 int frame = get_animation()->track_get_key_value(get_track(), p_index);
507 String animation_name;
508 if (animations.size() == 1) {
509 animation_name = animations.front()->get();
510 } else {
511 // Go through other track to find if animation is set
512 String animation_path = get_animation()->track_get_path(get_track());
513 animation_path = animation_path.replace(":frame", ":animation");
514 int animation_track = get_animation()->find_track(animation_path, get_animation()->track_get_type(get_track()));
515 float track_time = get_animation()->track_get_key_time(get_track(), p_index);
516 int animaiton_index = get_animation()->track_find_key(animation_track, track_time);
517 animation_name = get_animation()->track_get_key_value(animation_track, animaiton_index);
518 }
519
520 texture = sf->get_frame_texture(animation_name, frame);
521 if (!texture.is_valid()) {
522 AnimationTrackEdit::draw_key(p_index, p_pixels_sec, p_x, p_selected, p_clip_left, p_clip_right);
523 return;
524 }
525
526 region.size = texture->get_size();
527 }
528
529 Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
530 int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
531 int height = int(font->get_height(font_size) * 2);
532
533 int width = height * region.size.width / region.size.height;
534
535 Rect2 rect(p_x, int(get_size().height - height) / 2, width, height);
536
537 if (rect.position.x + rect.size.x < p_clip_left) {
538 return;
539 }
540
541 if (rect.position.x > p_clip_right) {
542 return;
543 }
544
545 Color accent = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
546 Color bg = accent;
547 bg.a = 0.15;
548
549 draw_rect_clipped(rect, bg);
550
551 draw_texture_region_clipped(texture, rect, region);
552
553 if (p_selected) {
554 draw_rect_clipped(rect, accent, false);
555 }
556}
557
558void AnimationTrackEditSpriteFrame::set_node(Object *p_object) {
559 id = p_object->get_instance_id();
560}
561
562void AnimationTrackEditSpriteFrame::set_as_coords() {
563 is_coords = true;
564}
565
566/// SUB ANIMATION ///
567
568int AnimationTrackEditSubAnim::get_key_height() const {
569 if (!ObjectDB::get_instance(id)) {
570 return AnimationTrackEdit::get_key_height();
571 }
572
573 Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
574 int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
575 return int(font->get_height(font_size) * 1.5);
576}
577
578Rect2 AnimationTrackEditSubAnim::get_key_rect(int p_index, float p_pixels_sec) {
579 Object *object = ObjectDB::get_instance(id);
580
581 if (!object) {
582 return AnimationTrackEdit::get_key_rect(p_index, p_pixels_sec);
583 }
584
585 AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(object);
586
587 if (!ap) {
588 return AnimationTrackEdit::get_key_rect(p_index, p_pixels_sec);
589 }
590
591 String anim = get_animation()->track_get_key_value(get_track(), p_index);
592
593 if (anim != "[stop]" && ap->has_animation(anim)) {
594 float len = ap->get_animation(anim)->get_length();
595
596 if (get_animation()->track_get_key_count(get_track()) > p_index + 1) {
597 len = MIN(len, get_animation()->track_get_key_time(get_track(), p_index + 1) - get_animation()->track_get_key_time(get_track(), p_index));
598 }
599
600 return Rect2(0, 0, len * p_pixels_sec, get_size().height);
601 } else {
602 Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
603 int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
604 int fh = font->get_height(font_size) * 0.8;
605 return Rect2(0, 0, fh, get_size().height);
606 }
607}
608
609bool AnimationTrackEditSubAnim::is_key_selectable_by_distance() const {
610 return false;
611}
612
613void AnimationTrackEditSubAnim::draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) {
614 Object *object = ObjectDB::get_instance(id);
615
616 if (!object) {
617 AnimationTrackEdit::draw_key(p_index, p_pixels_sec, p_x, p_selected, p_clip_left, p_clip_right);
618 return;
619 }
620
621 AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(object);
622
623 if (!ap) {
624 AnimationTrackEdit::draw_key(p_index, p_pixels_sec, p_x, p_selected, p_clip_left, p_clip_right);
625 return;
626 }
627
628 String anim = get_animation()->track_get_key_value(get_track(), p_index);
629
630 if (anim != "[stop]" && ap->has_animation(anim)) {
631 float len = ap->get_animation(anim)->get_length();
632
633 if (get_animation()->track_get_key_count(get_track()) > p_index + 1) {
634 len = MIN(len, get_animation()->track_get_key_time(get_track(), p_index + 1) - get_animation()->track_get_key_time(get_track(), p_index));
635 }
636
637 int pixel_len = len * p_pixels_sec;
638
639 int pixel_begin = p_x;
640 int pixel_end = p_x + pixel_len;
641
642 if (pixel_end < p_clip_left) {
643 return;
644 }
645
646 if (pixel_begin > p_clip_right) {
647 return;
648 }
649
650 int from_x = MAX(pixel_begin, p_clip_left);
651 int to_x = MIN(pixel_end, p_clip_right);
652
653 if (to_x <= from_x) {
654 return;
655 }
656
657 Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
658 int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
659 int fh = font->get_height(font_size) * 1.5;
660
661 Rect2 rect(from_x, int(get_size().height - fh) / 2, to_x - from_x, fh);
662
663 Color color = get_theme_color(SNAME("font_color"), SNAME("Label"));
664 Color bg = color;
665 bg.r = 1 - color.r;
666 bg.g = 1 - color.g;
667 bg.b = 1 - color.b;
668 draw_rect(rect, bg);
669
670 Vector<Vector2> points;
671 Vector<Color> colors = { color };
672 {
673 Ref<Animation> ap_anim = ap->get_animation(anim);
674
675 for (int i = 0; i < ap_anim->get_track_count(); i++) {
676 float h = (rect.size.height - 2) / ap_anim->get_track_count();
677
678 int y = 2 + h * i + h / 2;
679
680 for (int j = 0; j < ap_anim->track_get_key_count(i); j++) {
681 float ofs = ap_anim->track_get_key_time(i, j);
682 int x = p_x + ofs * p_pixels_sec + 2;
683
684 if (x < from_x || x >= (to_x - 4)) {
685 continue;
686 }
687
688 points.push_back(Point2(x, y));
689 points.push_back(Point2(x + 1, y));
690 }
691 }
692 }
693
694 if (points.size() > 2) {
695 RS::get_singleton()->canvas_item_add_multiline(get_canvas_item(), points, colors);
696 }
697
698 int limit = to_x - from_x - 4;
699 if (limit > 0) {
700 draw_string(font, Point2(from_x + 2, int(get_size().height - font->get_height(font_size)) / 2 + font->get_ascent(font_size)), anim, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, color);
701 }
702
703 if (p_selected) {
704 Color accent = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
705 draw_rect(rect, accent, false);
706 }
707 } else {
708 Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
709 int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
710 int fh = font->get_height(font_size) * 0.8;
711 Rect2 rect(Vector2(p_x, int(get_size().height - fh) / 2), Size2(fh, fh));
712
713 Color color = get_theme_color(SNAME("font_color"), SNAME("Label"));
714 draw_rect_clipped(rect, color);
715
716 if (p_selected) {
717 Color accent = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
718 draw_rect_clipped(rect, accent, false);
719 }
720 }
721}
722
723void AnimationTrackEditSubAnim::set_node(Object *p_object) {
724 id = p_object->get_instance_id();
725}
726
727//// VOLUME DB ////
728
729int AnimationTrackEditVolumeDB::get_key_height() const {
730 Ref<Texture2D> volume_texture = get_editor_theme_icon(SNAME("ColorTrackVu"));
731 return volume_texture->get_height() * 1.2;
732}
733
734void AnimationTrackEditVolumeDB::draw_bg(int p_clip_left, int p_clip_right) {
735 Ref<Texture2D> volume_texture = get_editor_theme_icon(SNAME("ColorTrackVu"));
736 int tex_h = volume_texture->get_height();
737
738 int y_from = (get_size().height - tex_h) / 2;
739 int y_size = tex_h;
740
741 Color color(1, 1, 1, 0.3);
742 draw_texture_rect(volume_texture, Rect2(p_clip_left, y_from, p_clip_right - p_clip_left, y_from + y_size), false, color);
743}
744
745void AnimationTrackEditVolumeDB::draw_fg(int p_clip_left, int p_clip_right) {
746 Ref<Texture2D> volume_texture = get_editor_theme_icon(SNAME("ColorTrackVu"));
747 int tex_h = volume_texture->get_height();
748 int y_from = (get_size().height - tex_h) / 2;
749 int db0 = y_from + (24 / 80.0) * tex_h;
750
751 draw_line(Vector2(p_clip_left, db0), Vector2(p_clip_right, db0), Color(1, 1, 1, 0.3));
752}
753
754void AnimationTrackEditVolumeDB::draw_key_link(int p_index, float p_pixels_sec, int p_x, int p_next_x, int p_clip_left, int p_clip_right) {
755 if (p_x > p_clip_right || p_next_x < p_clip_left) {
756 return;
757 }
758
759 float db = get_animation()->track_get_key_value(get_track(), p_index);
760 float db_n = get_animation()->track_get_key_value(get_track(), p_index + 1);
761
762 db = CLAMP(db, -60, 24);
763 db_n = CLAMP(db_n, -60, 24);
764
765 float h = 1.0 - ((db + 60) / 84.0);
766 float h_n = 1.0 - ((db_n + 60) / 84.0);
767
768 int from_x = p_x;
769 int to_x = p_next_x;
770
771 if (from_x < p_clip_left) {
772 h = Math::lerp(h, h_n, float(p_clip_left - from_x) / float(to_x - from_x));
773 from_x = p_clip_left;
774 }
775
776 if (to_x > p_clip_right) {
777 h_n = Math::lerp(h, h_n, float(p_clip_right - from_x) / float(to_x - from_x));
778 to_x = p_clip_right;
779 }
780
781 Ref<Texture2D> volume_texture = get_editor_theme_icon(SNAME("ColorTrackVu"));
782 int tex_h = volume_texture->get_height();
783
784 int y_from = (get_size().height - tex_h) / 2;
785
786 Color color = get_theme_color(SNAME("font_color"), SNAME("Label"));
787 color.a *= 0.7;
788
789 draw_line(Point2(from_x, y_from + h * tex_h), Point2(to_x, y_from + h_n * tex_h), color, 2);
790}
791
792////////////////////////
793
794/// AUDIO ///
795
796void AnimationTrackEditTypeAudio::_preview_changed(ObjectID p_which) {
797 for (int i = 0; i < get_animation()->track_get_key_count(get_track()); i++) {
798 Ref<AudioStream> stream = get_animation()->audio_track_get_key_stream(get_track(), i);
799 if (stream.is_valid() && stream->get_instance_id() == p_which) {
800 queue_redraw();
801 return;
802 }
803 }
804}
805
806int AnimationTrackEditTypeAudio::get_key_height() const {
807 Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
808 int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
809 return int(font->get_height(font_size) * 1.5);
810}
811
812Rect2 AnimationTrackEditTypeAudio::get_key_rect(int p_index, float p_pixels_sec) {
813 Ref<AudioStream> stream = get_animation()->audio_track_get_key_stream(get_track(), p_index);
814
815 if (!stream.is_valid()) {
816 return AnimationTrackEdit::get_key_rect(p_index, p_pixels_sec);
817 }
818
819 float start_ofs = get_animation()->audio_track_get_key_start_offset(get_track(), p_index);
820 float end_ofs = get_animation()->audio_track_get_key_end_offset(get_track(), p_index);
821
822 float len = stream->get_length();
823
824 if (len == 0) {
825 Ref<AudioStreamPreview> preview = AudioStreamPreviewGenerator::get_singleton()->generate_preview(stream);
826 len = preview->get_length();
827 }
828
829 len -= end_ofs;
830 len -= start_ofs;
831 if (len <= 0.0001) {
832 len = 0.0001;
833 }
834
835 if (get_animation()->track_get_key_count(get_track()) > p_index + 1) {
836 len = MIN(len, get_animation()->track_get_key_time(get_track(), p_index + 1) - get_animation()->track_get_key_time(get_track(), p_index));
837 }
838
839 return Rect2(0, 0, len * p_pixels_sec, get_size().height);
840}
841
842bool AnimationTrackEditTypeAudio::is_key_selectable_by_distance() const {
843 return false;
844}
845
846void AnimationTrackEditTypeAudio::draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) {
847 Ref<AudioStream> stream = get_animation()->audio_track_get_key_stream(get_track(), p_index);
848 if (!stream.is_valid()) {
849 AnimationTrackEdit::draw_key(p_index, p_pixels_sec, p_x, p_selected, p_clip_left, p_clip_right); // Draw diamond.
850 return;
851 }
852
853 float len = stream->get_length();
854 if (len == 0) {
855 AnimationTrackEdit::draw_key(p_index, p_pixels_sec, p_x, p_selected, p_clip_left, p_clip_right); // Draw diamond.
856 return;
857 }
858
859 float start_ofs = get_animation()->audio_track_get_key_start_offset(get_track(), p_index);
860 float end_ofs = get_animation()->audio_track_get_key_end_offset(get_track(), p_index);
861
862 int px_offset = 0;
863 if (len_resizing && p_index == len_resizing_index) {
864 float ofs_local = len_resizing_rel / get_timeline()->get_zoom_scale();
865 if (len_resizing_start) {
866 start_ofs += ofs_local;
867 px_offset = ofs_local * p_pixels_sec;
868 } else {
869 end_ofs -= ofs_local;
870 }
871 }
872
873 Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
874 int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
875 float fh = int(font->get_height(font_size) * 1.5);
876
877 Ref<AudioStreamPreview> preview = AudioStreamPreviewGenerator::get_singleton()->generate_preview(stream);
878
879 int pixel_total_len = len * p_pixels_sec;
880
881 len -= end_ofs;
882 len -= start_ofs;
883
884 if (len <= 0.0001) {
885 len = 0.0001;
886 }
887
888 int pixel_len = len * p_pixels_sec;
889
890 int pixel_begin = px_offset + p_x;
891 int pixel_end = px_offset + p_x + pixel_len;
892
893 if (pixel_end < p_clip_left) {
894 return;
895 }
896
897 if (pixel_begin > p_clip_right) {
898 return;
899 }
900
901 int from_x = MAX(pixel_begin, p_clip_left);
902 int to_x = MIN(pixel_end, p_clip_right);
903
904 if (get_animation()->track_get_key_count(get_track()) > p_index + 1) {
905 float limit = MIN(len, get_animation()->track_get_key_time(get_track(), p_index + 1) - get_animation()->track_get_key_time(get_track(), p_index));
906 int limit_x = pixel_begin + limit * p_pixels_sec;
907 to_x = MIN(limit_x, to_x);
908 }
909
910 if (to_x <= from_x) {
911 to_x = from_x + 1;
912 }
913
914 int h = get_size().height;
915 Rect2 rect = Rect2(from_x, (h - fh) / 2, to_x - from_x, fh);
916 draw_rect(rect, Color(0.25, 0.25, 0.25));
917
918 Vector<Vector2> points;
919 points.resize((to_x - from_x) * 2);
920 float preview_len = preview->get_length();
921
922 for (int i = from_x; i < to_x; i++) {
923 float ofs = (i - pixel_begin) * preview_len / pixel_total_len;
924 float ofs_n = ((i + 1) - pixel_begin) * preview_len / pixel_total_len;
925 ofs += start_ofs;
926 ofs_n += start_ofs;
927
928 float max = preview->get_max(ofs, ofs_n) * 0.5 + 0.5;
929 float min = preview->get_min(ofs, ofs_n) * 0.5 + 0.5;
930
931 int idx = i - from_x;
932 points.write[idx * 2 + 0] = Vector2(i, rect.position.y + min * rect.size.y);
933 points.write[idx * 2 + 1] = Vector2(i, rect.position.y + max * rect.size.y);
934 }
935
936 Vector<Color> colors = { Color(0.75, 0.75, 0.75) };
937
938 RS::get_singleton()->canvas_item_add_multiline(get_canvas_item(), points, colors);
939
940 Color cut_color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
941 cut_color.a = 0.7;
942 if (start_ofs > 0 && pixel_begin > p_clip_left) {
943 draw_rect(Rect2(pixel_begin, rect.position.y, 1, rect.size.y), cut_color);
944 }
945 if (end_ofs > 0 && pixel_end < p_clip_right) {
946 draw_rect(Rect2(pixel_end, rect.position.y, 1, rect.size.y), cut_color);
947 }
948
949 if (p_selected) {
950 Color accent = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
951 draw_rect(rect, accent, false);
952 }
953}
954
955void AnimationTrackEditTypeAudio::_bind_methods() {
956}
957
958AnimationTrackEditTypeAudio::AnimationTrackEditTypeAudio() {
959 AudioStreamPreviewGenerator::get_singleton()->connect("preview_updated", callable_mp(this, &AnimationTrackEditTypeAudio::_preview_changed));
960}
961
962bool AnimationTrackEditTypeAudio::can_drop_data(const Point2 &p_point, const Variant &p_data) const {
963 if (p_point.x > get_timeline()->get_name_limit() && p_point.x < get_size().width - get_timeline()->get_buttons_width()) {
964 Dictionary drag_data = p_data;
965 if (drag_data.has("type") && String(drag_data["type"]) == "resource") {
966 Ref<AudioStream> res = drag_data["resource"];
967 if (res.is_valid()) {
968 return true;
969 }
970 }
971
972 if (drag_data.has("type") && String(drag_data["type"]) == "files") {
973 Vector<String> files = drag_data["files"];
974
975 if (files.size() == 1) {
976 String file = files[0];
977 Ref<AudioStream> res = ResourceLoader::load(file);
978 if (res.is_valid()) {
979 return true;
980 }
981 }
982 }
983 }
984
985 return AnimationTrackEdit::can_drop_data(p_point, p_data);
986}
987
988void AnimationTrackEditTypeAudio::drop_data(const Point2 &p_point, const Variant &p_data) {
989 if (p_point.x > get_timeline()->get_name_limit() && p_point.x < get_size().width - get_timeline()->get_buttons_width()) {
990 Ref<AudioStream> stream;
991 Dictionary drag_data = p_data;
992 if (drag_data.has("type") && String(drag_data["type"]) == "resource") {
993 stream = drag_data["resource"];
994 } else if (drag_data.has("type") && String(drag_data["type"]) == "files") {
995 Vector<String> files = drag_data["files"];
996
997 if (files.size() == 1) {
998 String file = files[0];
999 stream = ResourceLoader::load(file);
1000 }
1001 }
1002
1003 if (stream.is_valid()) {
1004 int x = p_point.x - get_timeline()->get_name_limit();
1005 float ofs = x / get_timeline()->get_zoom_scale();
1006 ofs += get_timeline()->get_value();
1007
1008 ofs = get_editor()->snap_time(ofs);
1009
1010 while (get_animation()->track_find_key(get_track(), ofs, Animation::FIND_MODE_APPROX) != -1) { //make sure insertion point is valid
1011 ofs += 0.0001;
1012 }
1013
1014 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
1015 undo_redo->create_action(TTR("Add Audio Track Clip"));
1016 undo_redo->add_do_method(get_animation().ptr(), "audio_track_insert_key", get_track(), ofs, stream);
1017 undo_redo->add_undo_method(get_animation().ptr(), "track_remove_key_at_time", get_track(), ofs);
1018 undo_redo->commit_action();
1019
1020 queue_redraw();
1021 return;
1022 }
1023 }
1024
1025 AnimationTrackEdit::drop_data(p_point, p_data);
1026}
1027
1028void AnimationTrackEditTypeAudio::gui_input(const Ref<InputEvent> &p_event) {
1029 ERR_FAIL_COND(p_event.is_null());
1030
1031 Ref<InputEventMouseMotion> mm = p_event;
1032 if (!len_resizing && mm.is_valid()) {
1033 bool use_hsize_cursor = false;
1034 for (int i = 0; i < get_animation()->track_get_key_count(get_track()); i++) {
1035 Ref<AudioStream> stream = get_animation()->audio_track_get_key_stream(get_track(), i);
1036
1037 if (!stream.is_valid()) {
1038 continue;
1039 }
1040
1041 float len = stream->get_length();
1042 if (len == 0) {
1043 continue;
1044 }
1045
1046 float start_ofs = get_animation()->audio_track_get_key_start_offset(get_track(), i);
1047 float end_ofs = get_animation()->audio_track_get_key_end_offset(get_track(), i);
1048 len -= end_ofs;
1049 len -= start_ofs;
1050
1051 if (get_animation()->track_get_key_count(get_track()) > i + 1) {
1052 len = MIN(len, get_animation()->track_get_key_time(get_track(), i + 1) - get_animation()->track_get_key_time(get_track(), i));
1053 }
1054
1055 float ofs = get_animation()->track_get_key_time(get_track(), i);
1056
1057 ofs -= get_timeline()->get_value();
1058 ofs *= get_timeline()->get_zoom_scale();
1059 ofs += get_timeline()->get_name_limit();
1060
1061 int end = ofs + len * get_timeline()->get_zoom_scale();
1062
1063 if (end >= get_timeline()->get_name_limit() && end <= get_size().width - get_timeline()->get_buttons_width() && ABS(mm->get_position().x - end) < 5 * EDSCALE) {
1064 len_resizing_start = false;
1065 use_hsize_cursor = true;
1066 len_resizing_index = i;
1067 }
1068
1069 if (ofs >= get_timeline()->get_name_limit() && ofs <= get_size().width - get_timeline()->get_buttons_width() && ABS(mm->get_position().x - ofs) < 5 * EDSCALE) {
1070 len_resizing_start = true;
1071 use_hsize_cursor = true;
1072 len_resizing_index = i;
1073 }
1074 }
1075 over_drag_position = use_hsize_cursor;
1076 }
1077
1078 if (len_resizing && mm.is_valid()) {
1079 // Rezising index is some.
1080 len_resizing_rel += mm->get_relative().x;
1081 float ofs_local = len_resizing_rel / get_timeline()->get_zoom_scale();
1082 float prev_ofs_start = get_animation()->audio_track_get_key_start_offset(get_track(), len_resizing_index);
1083 float prev_ofs_end = get_animation()->audio_track_get_key_end_offset(get_track(), len_resizing_index);
1084 Ref<AudioStream> stream = get_animation()->audio_track_get_key_stream(get_track(), len_resizing_index);
1085 float len = stream->get_length();
1086 if (len == 0) {
1087 Ref<AudioStreamPreview> preview = AudioStreamPreviewGenerator::get_singleton()->generate_preview(stream);
1088 float preview_len = preview->get_length();
1089 len = preview_len;
1090 }
1091
1092 if (len_resizing_start) {
1093 len_resizing_rel = CLAMP(ofs_local, -prev_ofs_start, len - prev_ofs_end - prev_ofs_start) * get_timeline()->get_zoom_scale();
1094 } else {
1095 len_resizing_rel = CLAMP(ofs_local, -(len - prev_ofs_end - prev_ofs_start), prev_ofs_end) * get_timeline()->get_zoom_scale();
1096 }
1097
1098 queue_redraw();
1099 accept_event();
1100 return;
1101 }
1102
1103 Ref<InputEventMouseButton> mb = p_event;
1104 if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT && over_drag_position) {
1105 len_resizing = true;
1106 // In case if resizing index is not set yet reset the flag.
1107 if (len_resizing_index < 0) {
1108 len_resizing = false;
1109 return;
1110 }
1111 len_resizing_from_px = mb->get_position().x;
1112 len_resizing_rel = 0;
1113 queue_redraw();
1114 accept_event();
1115 return;
1116 }
1117
1118 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
1119 if (len_resizing && mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
1120 if (len_resizing_rel == 0 || len_resizing_index < 0) {
1121 len_resizing = false;
1122 return;
1123 }
1124
1125 if (len_resizing_start) {
1126 float ofs_local = len_resizing_rel / get_timeline()->get_zoom_scale();
1127 float prev_ofs = get_animation()->audio_track_get_key_start_offset(get_track(), len_resizing_index);
1128 float prev_time = get_animation()->track_get_key_time(get_track(), len_resizing_index);
1129 float new_ofs = prev_ofs + ofs_local;
1130 float new_time = prev_time + ofs_local;
1131 if (prev_time != new_time) {
1132 undo_redo->create_action(TTR("Change Audio Track Clip Start Offset"));
1133
1134 undo_redo->add_do_method(get_animation().ptr(), "track_set_key_time", get_track(), len_resizing_index, new_time);
1135 undo_redo->add_undo_method(get_animation().ptr(), "track_set_key_time", get_track(), len_resizing_index, prev_time);
1136
1137 undo_redo->add_do_method(get_animation().ptr(), "audio_track_set_key_start_offset", get_track(), len_resizing_index, new_ofs);
1138 undo_redo->add_undo_method(get_animation().ptr(), "audio_track_set_key_start_offset", get_track(), len_resizing_index, prev_ofs);
1139
1140 undo_redo->commit_action();
1141 }
1142 } else {
1143 float ofs_local = -len_resizing_rel / get_timeline()->get_zoom_scale();
1144 float prev_ofs = get_animation()->audio_track_get_key_end_offset(get_track(), len_resizing_index);
1145 float new_ofs = prev_ofs + ofs_local;
1146 if (prev_ofs != new_ofs) {
1147 undo_redo->create_action(TTR("Change Audio Track Clip End Offset"));
1148 undo_redo->add_do_method(get_animation().ptr(), "audio_track_set_key_end_offset", get_track(), len_resizing_index, new_ofs);
1149 undo_redo->add_undo_method(get_animation().ptr(), "audio_track_set_key_end_offset", get_track(), len_resizing_index, prev_ofs);
1150 undo_redo->commit_action();
1151 }
1152 }
1153
1154 len_resizing_index = -1;
1155 len_resizing = false;
1156 queue_redraw();
1157 accept_event();
1158 return;
1159 }
1160
1161 AnimationTrackEdit::gui_input(p_event);
1162}
1163
1164Control::CursorShape AnimationTrackEditTypeAudio::get_cursor_shape(const Point2 &p_pos) const {
1165 if (over_drag_position || len_resizing) {
1166 return Control::CURSOR_HSIZE;
1167 } else {
1168 return get_default_cursor_shape();
1169 }
1170}
1171
1172////////////////////
1173/// SUB ANIMATION ///
1174
1175int AnimationTrackEditTypeAnimation::get_key_height() const {
1176 if (!ObjectDB::get_instance(id)) {
1177 return AnimationTrackEdit::get_key_height();
1178 }
1179
1180 Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
1181 int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
1182 return int(font->get_height(font_size) * 1.5);
1183}
1184
1185Rect2 AnimationTrackEditTypeAnimation::get_key_rect(int p_index, float p_pixels_sec) {
1186 Object *object = ObjectDB::get_instance(id);
1187
1188 if (!object) {
1189 return AnimationTrackEdit::get_key_rect(p_index, p_pixels_sec);
1190 }
1191
1192 AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(object);
1193
1194 if (!ap) {
1195 return AnimationTrackEdit::get_key_rect(p_index, p_pixels_sec);
1196 }
1197
1198 String anim = get_animation()->animation_track_get_key_animation(get_track(), p_index);
1199
1200 if (anim != "[stop]" && ap->has_animation(anim)) {
1201 float len = ap->get_animation(anim)->get_length();
1202
1203 if (get_animation()->track_get_key_count(get_track()) > p_index + 1) {
1204 len = MIN(len, get_animation()->track_get_key_time(get_track(), p_index + 1) - get_animation()->track_get_key_time(get_track(), p_index));
1205 }
1206
1207 return Rect2(0, 0, len * p_pixels_sec, get_size().height);
1208 } else {
1209 Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
1210 int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
1211 int fh = font->get_height(font_size) * 0.8;
1212 return Rect2(0, 0, fh, get_size().height);
1213 }
1214}
1215
1216bool AnimationTrackEditTypeAnimation::is_key_selectable_by_distance() const {
1217 return false;
1218}
1219
1220void AnimationTrackEditTypeAnimation::draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) {
1221 Object *object = ObjectDB::get_instance(id);
1222
1223 if (!object) {
1224 AnimationTrackEdit::draw_key(p_index, p_pixels_sec, p_x, p_selected, p_clip_left, p_clip_right);
1225 return;
1226 }
1227
1228 AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(object);
1229
1230 if (!ap) {
1231 AnimationTrackEdit::draw_key(p_index, p_pixels_sec, p_x, p_selected, p_clip_left, p_clip_right);
1232 return;
1233 }
1234
1235 String anim = get_animation()->animation_track_get_key_animation(get_track(), p_index);
1236
1237 if (anim != "[stop]" && ap->has_animation(anim)) {
1238 float len = ap->get_animation(anim)->get_length();
1239
1240 if (get_animation()->track_get_key_count(get_track()) > p_index + 1) {
1241 len = MIN(len, get_animation()->track_get_key_time(get_track(), p_index + 1) - get_animation()->track_get_key_time(get_track(), p_index));
1242 }
1243
1244 int pixel_len = len * p_pixels_sec;
1245
1246 int pixel_begin = p_x;
1247 int pixel_end = p_x + pixel_len;
1248
1249 if (pixel_end < p_clip_left) {
1250 return;
1251 }
1252
1253 if (pixel_begin > p_clip_right) {
1254 return;
1255 }
1256
1257 int from_x = MAX(pixel_begin, p_clip_left);
1258 int to_x = MIN(pixel_end, p_clip_right);
1259
1260 if (to_x <= from_x) {
1261 return;
1262 }
1263
1264 Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
1265 int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
1266 int fh = font->get_height(font_size) * 1.5;
1267
1268 Rect2 rect(from_x, int(get_size().height - fh) / 2, to_x - from_x, fh);
1269
1270 Color color = get_theme_color(SNAME("font_color"), SNAME("Label"));
1271 Color bg = color;
1272 bg.r = 1 - color.r;
1273 bg.g = 1 - color.g;
1274 bg.b = 1 - color.b;
1275 draw_rect(rect, bg);
1276
1277 Vector<Vector2> points;
1278 Vector<Color> colors = { color };
1279 {
1280 Ref<Animation> ap_anim = ap->get_animation(anim);
1281
1282 for (int i = 0; i < ap_anim->get_track_count(); i++) {
1283 float h = (rect.size.height - 2) / ap_anim->get_track_count();
1284
1285 int y = 2 + h * i + h / 2;
1286
1287 for (int j = 0; j < ap_anim->track_get_key_count(i); j++) {
1288 float ofs = ap_anim->track_get_key_time(i, j);
1289 int x = p_x + ofs * p_pixels_sec + 2;
1290
1291 if (x < from_x || x >= (to_x - 4)) {
1292 continue;
1293 }
1294
1295 points.push_back(Point2(x, y));
1296 points.push_back(Point2(x + 1, y));
1297 }
1298 }
1299 }
1300
1301 if (points.size() > 2) {
1302 RS::get_singleton()->canvas_item_add_multiline(get_canvas_item(), points, colors);
1303 }
1304
1305 int limit = to_x - from_x - 4;
1306 if (limit > 0) {
1307 draw_string(font, Point2(from_x + 2, int(get_size().height - font->get_height(font_size)) / 2 + font->get_ascent(font_size)), anim, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, color);
1308 }
1309
1310 if (p_selected) {
1311 Color accent = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
1312 draw_rect(rect, accent, false);
1313 }
1314 } else {
1315 Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
1316 int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
1317 int fh = font->get_height(font_size) * 0.8;
1318 Rect2 rect(Vector2(p_x, int(get_size().height - fh) / 2), Size2(fh, fh));
1319
1320 Color color = get_theme_color(SNAME("font_color"), SNAME("Label"));
1321 draw_rect_clipped(rect, color);
1322
1323 if (p_selected) {
1324 Color accent = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
1325 draw_rect_clipped(rect, accent, false);
1326 }
1327 }
1328}
1329
1330void AnimationTrackEditTypeAnimation::set_node(Object *p_object) {
1331 id = p_object->get_instance_id();
1332}
1333
1334AnimationTrackEditTypeAnimation::AnimationTrackEditTypeAnimation() {
1335}
1336
1337/////////
1338AnimationTrackEdit *AnimationTrackEditDefaultPlugin::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) {
1339 if (p_property == "playing" && (p_object->is_class("AudioStreamPlayer") || p_object->is_class("AudioStreamPlayer2D") || p_object->is_class("AudioStreamPlayer3D"))) {
1340 AnimationTrackEditAudio *audio = memnew(AnimationTrackEditAudio);
1341 audio->set_node(p_object);
1342 return audio;
1343 }
1344
1345 if (p_property == "frame" && (p_object->is_class("Sprite2D") || p_object->is_class("Sprite3D") || p_object->is_class("AnimatedSprite2D") || p_object->is_class("AnimatedSprite3D"))) {
1346 AnimationTrackEditSpriteFrame *sprite = memnew(AnimationTrackEditSpriteFrame);
1347 sprite->set_node(p_object);
1348 return sprite;
1349 }
1350
1351 if (p_property == "frame_coords" && (p_object->is_class("Sprite2D") || p_object->is_class("Sprite3D"))) {
1352 AnimationTrackEditSpriteFrame *sprite = memnew(AnimationTrackEditSpriteFrame);
1353 sprite->set_as_coords();
1354 sprite->set_node(p_object);
1355 return sprite;
1356 }
1357
1358 if (p_property == "current_animation" && (p_object->is_class("AnimationPlayer"))) {
1359 AnimationTrackEditSubAnim *player = memnew(AnimationTrackEditSubAnim);
1360 player->set_node(p_object);
1361 return player;
1362 }
1363
1364 if (p_property == "volume_db") {
1365 AnimationTrackEditVolumeDB *vu = memnew(AnimationTrackEditVolumeDB);
1366 return vu;
1367 }
1368
1369 if (p_type == Variant::BOOL) {
1370 return memnew(AnimationTrackEditBool);
1371 }
1372 if (p_type == Variant::COLOR) {
1373 return memnew(AnimationTrackEditColor);
1374 }
1375
1376 return nullptr;
1377}
1378
1379AnimationTrackEdit *AnimationTrackEditDefaultPlugin::create_audio_track_edit() {
1380 return memnew(AnimationTrackEditTypeAudio);
1381}
1382
1383AnimationTrackEdit *AnimationTrackEditDefaultPlugin::create_animation_track_edit(Object *p_object) {
1384 AnimationTrackEditTypeAnimation *an = memnew(AnimationTrackEditTypeAnimation);
1385 an->set_node(p_object);
1386 return an;
1387}
1388