diff options
Diffstat (limited to 'editor/animation_track_editor.cpp')
-rw-r--r-- | editor/animation_track_editor.cpp | 1423 |
1 files changed, 786 insertions, 637 deletions
diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index dbbdd85706..d81d629780 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -33,9 +33,9 @@ #include "animation_track_editor_plugins.h" #include "core/input/input.h" #include "editor/animation_bezier_editor.h" +#include "editor/editor_node.h" +#include "editor/editor_scale.h" #include "editor/plugins/animation_player_editor_plugin.h" -#include "editor_node.h" -#include "editor_scale.h" #include "scene/animation/animation_player.h" #include "scene/gui/view_panner.h" #include "scene/main/window.h" @@ -358,7 +358,7 @@ public: setting = true; undo_redo->create_action(TTR("Anim Change Keyframe Value"), UndoRedo::MERGE_ENDS); - RES prev = animation->audio_track_get_key_stream(track, key); + Ref<Resource> prev = animation->audio_track_get_key_stream(track, key); undo_redo->add_do_method(animation.ptr(), "audio_track_set_key_stream", track, key, stream); undo_redo->add_undo_method(animation.ptr(), "audio_track_set_key_stream", track, key, prev); undo_redo->add_do_method(this, "_update_obj", animation); @@ -560,54 +560,53 @@ public: if (use_fps && animation->get_step() > 0) { float max_frame = animation->get_length() / animation->get_step(); - p_list->push_back(PropertyInfo(Variant::FLOAT, "frame", PROPERTY_HINT_RANGE, "0," + rtos(max_frame) + ",1")); + p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("frame"), PROPERTY_HINT_RANGE, "0," + rtos(max_frame) + ",1")); } else { - p_list->push_back(PropertyInfo(Variant::FLOAT, "time", PROPERTY_HINT_RANGE, "0," + rtos(animation->get_length()) + ",0.01")); + p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("time"), PROPERTY_HINT_RANGE, "0," + rtos(animation->get_length()) + ",0.01")); } switch (animation->track_get_type(track)) { case Animation::TYPE_POSITION_3D: { - p_list->push_back(PropertyInfo(Variant::VECTOR3, "position")); + p_list->push_back(PropertyInfo(Variant::VECTOR3, PNAME("position"))); } break; case Animation::TYPE_ROTATION_3D: { - p_list->push_back(PropertyInfo(Variant::VECTOR3, "rotation")); + p_list->push_back(PropertyInfo(Variant::QUATERNION, PNAME("rotation"))); } break; case Animation::TYPE_SCALE_3D: { - p_list->push_back(PropertyInfo(Variant::VECTOR3, "scale")); + p_list->push_back(PropertyInfo(Variant::VECTOR3, PNAME("scale"))); } break; case Animation::TYPE_BLEND_SHAPE: { - p_list->push_back(PropertyInfo(Variant::FLOAT, "value")); + p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("value"))); } break; case Animation::TYPE_VALUE: { Variant v = animation->track_get_key_value(track, key); if (hint.type != Variant::NIL) { PropertyInfo pi = hint; - pi.name = "value"; + pi.name = PNAME("value"); p_list->push_back(pi); } else { - PropertyHint hint = PROPERTY_HINT_NONE; - String hint_string; + PropertyHint val_hint = PROPERTY_HINT_NONE; + String val_hint_string; if (v.get_type() == Variant::OBJECT) { // Could actually check the object property if exists..? Yes I will! Ref<Resource> res = v; if (res.is_valid()) { - hint = PROPERTY_HINT_RESOURCE_TYPE; - hint_string = res->get_class(); + val_hint = PROPERTY_HINT_RESOURCE_TYPE; + val_hint_string = res->get_class(); } } if (v.get_type() != Variant::NIL) { - p_list->push_back(PropertyInfo(v.get_type(), "value", hint, hint_string)); + p_list->push_back(PropertyInfo(v.get_type(), PNAME("value"), val_hint, val_hint_string)); } } } break; case Animation::TYPE_METHOD: { - p_list->push_back(PropertyInfo(Variant::STRING_NAME, "name")); - static_assert(VARIANT_ARG_MAX == 8, "PROPERTY_HINT_RANGE needs to be updated if VARIANT_ARG_MAX != 8"); - p_list->push_back(PropertyInfo(Variant::INT, "arg_count", PROPERTY_HINT_RANGE, "0,8,1")); + p_list->push_back(PropertyInfo(Variant::STRING_NAME, PNAME("name"))); + p_list->push_back(PropertyInfo(Variant::INT, PNAME("arg_count"), PROPERTY_HINT_RANGE, "0,32,1,or_greater")); Dictionary d = animation->track_get_key_value(track, key); ERR_FAIL_COND(!d.has("args")); @@ -621,24 +620,24 @@ public: } for (int i = 0; i < args.size(); i++) { - p_list->push_back(PropertyInfo(Variant::INT, "args/" + itos(i) + "/type", PROPERTY_HINT_ENUM, vtypes)); + p_list->push_back(PropertyInfo(Variant::INT, vformat("%s/%d/%s", PNAME("args"), i, PNAME("type")), PROPERTY_HINT_ENUM, vtypes)); if (args[i].get_type() != Variant::NIL) { - p_list->push_back(PropertyInfo(args[i].get_type(), "args/" + itos(i) + "/value")); + p_list->push_back(PropertyInfo(args[i].get_type(), vformat("%s/%d/%s", PNAME("args"), i, PNAME("value")))); } } } break; case Animation::TYPE_BEZIER: { - p_list->push_back(PropertyInfo(Variant::FLOAT, "value")); - p_list->push_back(PropertyInfo(Variant::VECTOR2, "in_handle")); - p_list->push_back(PropertyInfo(Variant::VECTOR2, "out_handle")); - p_list->push_back(PropertyInfo(Variant::INT, "handle_mode", PROPERTY_HINT_ENUM, "Free,Balanced")); + p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("value"))); + p_list->push_back(PropertyInfo(Variant::VECTOR2, PNAME("in_handle"))); + p_list->push_back(PropertyInfo(Variant::VECTOR2, PNAME("out_handle"))); + p_list->push_back(PropertyInfo(Variant::INT, PNAME("handle_mode"), PROPERTY_HINT_ENUM, "Free,Balanced")); } break; case Animation::TYPE_AUDIO: { - p_list->push_back(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "start_offset", PROPERTY_HINT_RANGE, "0,3600,0.01,or_greater")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "end_offset", PROPERTY_HINT_RANGE, "0,3600,0.01,or_greater")); + p_list->push_back(PropertyInfo(Variant::OBJECT, PNAME("stream"), PROPERTY_HINT_RESOURCE_TYPE, "AudioStream")); + p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("start_offset"), PROPERTY_HINT_RANGE, "0,3600,0.01,or_greater")); + p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("end_offset"), PROPERTY_HINT_RANGE, "0,3600,0.01,or_greater")); } break; case Animation::TYPE_ANIMATION: { @@ -664,13 +663,13 @@ public: } animations += "[stop]"; - p_list->push_back(PropertyInfo(Variant::STRING_NAME, "animation", PROPERTY_HINT_ENUM, animations)); + p_list->push_back(PropertyInfo(Variant::STRING_NAME, PNAME("animation"), PROPERTY_HINT_ENUM, animations)); } break; } if (animation->track_get_type(track) == Animation::TYPE_VALUE) { - p_list->push_back(PropertyInfo(Variant::FLOAT, "easing", PROPERTY_HINT_EXP_EASING)); + p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("easing"), PROPERTY_HINT_EXP_EASING)); } } @@ -993,7 +992,7 @@ public: setting = true; undo_redo->create_action(TTR("Anim Multi Change Keyframe Value"), UndoRedo::MERGE_ENDS); } - RES prev = animation->audio_track_get_key_stream(track, key); + Ref<Resource> prev = animation->audio_track_get_key_stream(track, key); undo_redo->add_do_method(animation.ptr(), "audio_track_set_key_stream", track, key, stream); undo_redo->add_undo_method(animation.ptr(), "audio_track_set_key_stream", track, key, prev); update_obj = true; @@ -1265,20 +1264,20 @@ public: pi.name = "value"; p_list->push_back(pi); } else { - PropertyHint hint = PROPERTY_HINT_NONE; - String hint_string; + PropertyHint val_hint = PROPERTY_HINT_NONE; + String val_hint_string; if (v.get_type() == Variant::OBJECT) { // Could actually check the object property if exists..? Yes I will! Ref<Resource> res = v; if (res.is_valid()) { - hint = PROPERTY_HINT_RESOURCE_TYPE; - hint_string = res->get_class(); + val_hint = PROPERTY_HINT_RESOURCE_TYPE; + val_hint_string = res->get_class(); } } if (v.get_type() != Variant::NIL) { - p_list->push_back(PropertyInfo(v.get_type(), "value", hint, hint_string)); + p_list->push_back(PropertyInfo(v.get_type(), "value", val_hint, val_hint_string)); } } } @@ -1287,8 +1286,8 @@ public: } break; case Animation::TYPE_METHOD: { p_list->push_back(PropertyInfo(Variant::STRING_NAME, "name")); - static_assert(VARIANT_ARG_MAX == 8, "PROPERTY_HINT_RANGE needs to be updated if VARIANT_ARG_MAX != 8"); - p_list->push_back(PropertyInfo(Variant::INT, "arg_count", PROPERTY_HINT_RANGE, "0,8,1")); + + p_list->push_back(PropertyInfo(Variant::INT, "arg_count", PROPERTY_HINT_RANGE, "0,32,1,or_greater")); Dictionary d = animation->track_get_key_value(first_track, first_key); ERR_FAIL_COND(!d.has("args")); @@ -1354,8 +1353,8 @@ public: Ref<Animation> animation; - Map<int, List<float>> key_ofs_map; - Map<int, NodePath> base_map; + RBMap<int, List<float>> key_ofs_map; + RBMap<int, NodePath> base_map; PropertyInfo hint; Node *root_path = nullptr; @@ -1418,14 +1417,14 @@ void AnimationTimelineEdit::_anim_length_changed(double p_new_len) { void AnimationTimelineEdit::_anim_loop_pressed() { undo_redo->create_action(TTR("Change Animation Loop")); switch (animation->get_loop_mode()) { - case Animation::LoopMode::LOOP_NONE: { - undo_redo->add_do_method(animation.ptr(), "set_loop_mode", Animation::LoopMode::LOOP_LINEAR); + case Animation::LOOP_NONE: { + undo_redo->add_do_method(animation.ptr(), "set_loop_mode", Animation::LOOP_LINEAR); } break; - case Animation::LoopMode::LOOP_LINEAR: { - undo_redo->add_do_method(animation.ptr(), "set_loop_mode", Animation::LoopMode::LOOP_PINGPONG); + case Animation::LOOP_LINEAR: { + undo_redo->add_do_method(animation.ptr(), "set_loop_mode", Animation::LOOP_PINGPONG); } break; - case Animation::LoopMode::LOOP_PINGPONG: { - undo_redo->add_do_method(animation.ptr(), "set_loop_mode", Animation::LoopMode::LOOP_NONE); + case Animation::LOOP_PINGPONG: { + undo_redo->add_do_method(animation.ptr(), "set_loop_mode", Animation::LOOP_NONE); } break; default: break; @@ -1458,198 +1457,201 @@ int AnimationTimelineEdit::get_name_limit() const { } void AnimationTimelineEdit::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE || p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) { - panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/animation_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EditorSettings::get_singleton()->get("editors/panning/simple_panning"))); - } - - if (p_what == NOTIFICATION_ENTER_TREE) { - add_track->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); - loop->set_icon(get_theme_icon(SNAME("Loop"), SNAME("EditorIcons"))); - time_icon->set_texture(get_theme_icon(SNAME("Time"), SNAME("EditorIcons"))); - - add_track->get_popup()->clear(); - add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyValue"), SNAME("EditorIcons")), TTR("Property Track")); - add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyXPosition"), SNAME("EditorIcons")), TTR("3D Position Track")); - add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyXRotation"), SNAME("EditorIcons")), TTR("3D Rotation Track")); - add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyXScale"), SNAME("EditorIcons")), TTR("3D Scale Track")); - add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyBlendShape"), SNAME("EditorIcons")), TTR("Blend Shape Track")); - add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyCall"), SNAME("EditorIcons")), TTR("Call Method Track")); - add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyBezier"), SNAME("EditorIcons")), TTR("Bezier Curve Track")); - add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyAudio"), SNAME("EditorIcons")), TTR("Audio Playback Track")); - add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyAnimation"), SNAME("EditorIcons")), TTR("Animation Playback Track")); - } + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/animation_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EditorSettings::get_singleton()->get("editors/panning/simple_panning"))); + add_track->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); + loop->set_icon(get_theme_icon(SNAME("Loop"), SNAME("EditorIcons"))); + time_icon->set_texture(get_theme_icon(SNAME("Time"), SNAME("EditorIcons"))); + + add_track->get_popup()->clear(); + add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyValue"), SNAME("EditorIcons")), TTR("Property Track")); + add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyXPosition"), SNAME("EditorIcons")), TTR("3D Position Track")); + add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyXRotation"), SNAME("EditorIcons")), TTR("3D Rotation Track")); + add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyXScale"), SNAME("EditorIcons")), TTR("3D Scale Track")); + add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyBlendShape"), SNAME("EditorIcons")), TTR("Blend Shape Track")); + add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyCall"), SNAME("EditorIcons")), TTR("Call Method Track")); + add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyBezier"), SNAME("EditorIcons")), TTR("Bezier Curve Track")); + add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyAudio"), SNAME("EditorIcons")), TTR("Audio Playback Track")); + add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyAnimation"), SNAME("EditorIcons")), TTR("Animation Playback Track")); + } break; - if (p_what == NOTIFICATION_RESIZED) { - len_hb->set_position(Vector2(get_size().width - get_buttons_width(), 0)); - len_hb->set_size(Size2(get_buttons_width(), get_size().height)); - } + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/animation_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EditorSettings::get_singleton()->get("editors/panning/simple_panning"))); + } break; - if (p_what == NOTIFICATION_DRAW) { - int key_range = get_size().width - get_buttons_width() - get_name_limit(); + case NOTIFICATION_RESIZED: { + len_hb->set_position(Vector2(get_size().width - get_buttons_width(), 0)); + len_hb->set_size(Size2(get_buttons_width(), get_size().height)); + } break; - if (!animation.is_valid()) { - return; - } + case NOTIFICATION_DRAW: { + int key_range = get_size().width - get_buttons_width() - get_name_limit(); - Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label")); - int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label")); - Color color = get_theme_color(SNAME("font_color"), SNAME("Label")); + if (!animation.is_valid()) { + return; + } - int zoomw = key_range; - float scale = get_zoom_scale(); - int h = get_size().height; + Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label")); + int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label")); + Color color = get_theme_color(SNAME("font_color"), SNAME("Label")); - float l = animation->get_length(); - if (l <= 0) { - l = 0.001; // Avoid crashor. - } + int zoomw = key_range; + float scale = get_zoom_scale(); + int h = get_size().height; - Ref<Texture2D> hsize_icon = get_theme_icon(SNAME("Hsize"), SNAME("EditorIcons")); - hsize_rect = Rect2(get_name_limit() - hsize_icon->get_width() - 2 * EDSCALE, (get_size().height - hsize_icon->get_height()) / 2, hsize_icon->get_width(), hsize_icon->get_height()); - draw_texture(hsize_icon, hsize_rect.position); + float l = animation->get_length(); + if (l <= 0) { + l = 0.001; // Avoid crashor. + } - { - float time_min = 0; - float time_max = animation->get_length(); - for (int i = 0; i < animation->get_track_count(); i++) { - if (animation->track_get_key_count(i) > 0) { - float beg = animation->track_get_key_time(i, 0); + Ref<Texture2D> hsize_icon = get_theme_icon(SNAME("Hsize"), SNAME("EditorIcons")); + hsize_rect = Rect2(get_name_limit() - hsize_icon->get_width() - 2 * EDSCALE, (get_size().height - hsize_icon->get_height()) / 2, hsize_icon->get_width(), hsize_icon->get_height()); + draw_texture(hsize_icon, hsize_rect.position); - if (beg < time_min) { - time_min = beg; - } + { + float time_min = 0; + float time_max = animation->get_length(); + for (int i = 0; i < animation->get_track_count(); i++) { + if (animation->track_get_key_count(i) > 0) { + float beg = animation->track_get_key_time(i, 0); + + if (beg < time_min) { + time_min = beg; + } - float end = animation->track_get_key_time(i, animation->track_get_key_count(i) - 1); + float end = animation->track_get_key_time(i, animation->track_get_key_count(i) - 1); - if (end > time_max) { - time_max = end; + if (end > time_max) { + time_max = end; + } } } - } - float extra = (zoomw / scale) * 0.5; + float extra = (zoomw / scale) * 0.5; - time_max += extra; - set_min(time_min); - set_max(time_max); + time_max += extra; + set_min(time_min); + set_max(time_max); - if (zoomw / scale < (time_max - time_min)) { - hscroll->show(); + if (zoomw / scale < (time_max - time_min)) { + hscroll->show(); - } else { - hscroll->hide(); + } else { + hscroll->hide(); + } } - } - set_page(zoomw / scale); + set_page(zoomw / scale); - int end_px = (l - get_value()) * scale; - int begin_px = -get_value() * scale; - Color notimecol = get_theme_color(SNAME("dark_color_2"), SNAME("Editor")); - Color timecolor = color; - timecolor.a = 0.2; - Color linecolor = color; - linecolor.a = 0.2; + int end_px = (l - get_value()) * scale; + int begin_px = -get_value() * scale; + Color notimecol = get_theme_color(SNAME("dark_color_2"), SNAME("Editor")); + Color timecolor = color; + timecolor.a = 0.2; + Color linecolor = color; + linecolor.a = 0.2; - { - draw_rect(Rect2(Point2(get_name_limit(), 0), Point2(zoomw - 1, h)), notimecol); + { + draw_rect(Rect2(Point2(get_name_limit(), 0), Point2(zoomw - 1, h)), notimecol); - if (begin_px < zoomw && end_px > 0) { - if (begin_px < 0) { - begin_px = 0; - } - if (end_px > zoomw) { - end_px = zoomw; - } + if (begin_px < zoomw && end_px > 0) { + if (begin_px < 0) { + begin_px = 0; + } + if (end_px > zoomw) { + end_px = zoomw; + } - draw_rect(Rect2(Point2(get_name_limit() + begin_px, 0), Point2(end_px - begin_px - 1, h)), timecolor); + draw_rect(Rect2(Point2(get_name_limit() + begin_px, 0), Point2(end_px - begin_px - 1, h)), timecolor); + } } - } - Color color_time_sec = color; - Color color_time_dec = color; - color_time_dec.a *= 0.5; + Color color_time_sec = color; + Color color_time_dec = color; + color_time_dec.a *= 0.5; #define SC_ADJ 100 - int min = 30; - int dec = 1; - int step = 1; - int decimals = 2; - bool step_found = false; - - const int period_width = font->get_char_size('.', 0, font_size).width; - int max_digit_width = font->get_char_size('0', 0, font_size).width; - for (int i = 1; i <= 9; i++) { - const int digit_width = font->get_char_size('0' + i, 0, font_size).width; - max_digit_width = MAX(digit_width, max_digit_width); - } - const int max_sc = int(Math::ceil(zoomw / scale)); - const int max_sc_width = String::num(max_sc).length() * max_digit_width; - - while (!step_found) { - min = max_sc_width; - if (decimals > 0) { - min += period_width + max_digit_width * decimals; + int dec = 1; + int step = 1; + int decimals = 2; + bool step_found = false; + + const float period_width = font->get_char_size('.', 0, font_size).width; + float max_digit_width = font->get_char_size('0', 0, font_size).width; + for (int i = 1; i <= 9; i++) { + const float digit_width = font->get_char_size('0' + i, 0, font_size).width; + max_digit_width = MAX(digit_width, max_digit_width); } + const int max_sc = int(Math::ceil(zoomw / scale)); + const int max_sc_width = String::num(max_sc).length() * max_digit_width; - static const int _multp[3] = { 1, 2, 5 }; - for (int i = 0; i < 3; i++) { - step = (_multp[i] * dec); - if (step * scale / SC_ADJ > min) { - step_found = true; + while (!step_found) { + int min = max_sc_width; + if (decimals > 0) { + min += period_width + max_digit_width * decimals; + } + + static const int _multp[3] = { 1, 2, 5 }; + for (int i = 0; i < 3; i++) { + step = (_multp[i] * dec); + if (step * scale / SC_ADJ > min) { + step_found = true; + break; + } + } + if (step_found) { break; } + dec *= 10; + decimals--; + if (decimals < 0) { + decimals = 0; + } } - if (step_found) { - break; - } - dec *= 10; - decimals--; - if (decimals < 0) { - decimals = 0; - } - } - if (use_fps) { - float step_size = animation->get_step(); - if (step_size > 0) { - int prev_frame_ofs = -10000000; + if (use_fps) { + float step_size = animation->get_step(); + if (step_size > 0) { + int prev_frame_ofs = -10000000; - for (int i = 0; i < zoomw; i++) { - float pos = get_value() + double(i) / scale; - float prev = get_value() + (double(i) - 1.0) / scale; + for (int i = 0; i < zoomw; i++) { + float pos = get_value() + double(i) / scale; + float prev = get_value() + (double(i) - 1.0) / scale; - int frame = pos / step_size; - int prev_frame = prev / step_size; + int frame = pos / step_size; + int prev_frame = prev / step_size; - bool sub = Math::floor(prev) == Math::floor(pos); + bool sub = Math::floor(prev) == Math::floor(pos); - if (frame != prev_frame && i >= prev_frame_ofs) { - draw_line(Point2(get_name_limit() + i, 0), Point2(get_name_limit() + i, h), linecolor, Math::round(EDSCALE)); + if (frame != prev_frame && i >= prev_frame_ofs) { + draw_line(Point2(get_name_limit() + i, 0), Point2(get_name_limit() + i, h), linecolor, Math::round(EDSCALE)); - draw_string(font, Point2(get_name_limit() + i + 3 * EDSCALE, (h - font->get_height(font_size)) / 2 + font->get_ascent(font_size)).floor(), itos(frame), HORIZONTAL_ALIGNMENT_LEFT, zoomw - i, font_size, sub ? color_time_dec : color_time_sec); - prev_frame_ofs = i + font->get_string_size(itos(frame), font_size).x + 5 * EDSCALE; + draw_string(font, Point2(get_name_limit() + i + 3 * EDSCALE, (h - font->get_height(font_size)) / 2 + font->get_ascent(font_size)).floor(), itos(frame), HORIZONTAL_ALIGNMENT_LEFT, zoomw - i, font_size, sub ? color_time_dec : color_time_sec); + prev_frame_ofs = i + font->get_string_size(itos(frame), font_size).x + 5 * EDSCALE; + } } } - } - } else { - for (int i = 0; i < zoomw; i++) { - float pos = get_value() + double(i) / scale; - float prev = get_value() + (double(i) - 1.0) / scale; - - int sc = int(Math::floor(pos * SC_ADJ)); - int prev_sc = int(Math::floor(prev * SC_ADJ)); - bool sub = (sc % SC_ADJ); - - if ((sc / step) != (prev_sc / step) || (prev_sc < 0 && sc >= 0)) { - int scd = sc < 0 ? prev_sc : sc; - draw_line(Point2(get_name_limit() + i, 0), Point2(get_name_limit() + i, h), linecolor, Math::round(EDSCALE)); - draw_string(font, Point2(get_name_limit() + i + 3, (h - font->get_height(font_size)) / 2 + font->get_ascent(font_size)).floor(), String::num((scd - (scd % step)) / double(SC_ADJ), decimals), HORIZONTAL_ALIGNMENT_LEFT, zoomw - i, font_size, sub ? color_time_dec : color_time_sec); + } else { + for (int i = 0; i < zoomw; i++) { + float pos = get_value() + double(i) / scale; + float prev = get_value() + (double(i) - 1.0) / scale; + + int sc = int(Math::floor(pos * SC_ADJ)); + int prev_sc = int(Math::floor(prev * SC_ADJ)); + bool sub = (sc % SC_ADJ); + + if ((sc / step) != (prev_sc / step) || (prev_sc < 0 && sc >= 0)) { + int scd = sc < 0 ? prev_sc : sc; + draw_line(Point2(get_name_limit() + i, 0), Point2(get_name_limit() + i, h), linecolor, Math::round(EDSCALE)); + draw_string(font, Point2(get_name_limit() + i + 3, (h - font->get_height(font_size)) / 2 + font->get_ascent(font_size)).floor(), String::num((scd - (scd % step)) / double(SC_ADJ), decimals), HORIZONTAL_ALIGNMENT_LEFT, zoomw - i, font_size, sub ? color_time_dec : color_time_sec); + } } } - } - draw_line(Vector2(0, get_size().height), get_size(), linecolor, Math::round(EDSCALE)); + draw_line(Vector2(0, get_size().height), get_size(), linecolor, Math::round(EDSCALE)); + } break; } } @@ -1722,16 +1724,16 @@ void AnimationTimelineEdit::update_values() { } switch (animation->get_loop_mode()) { - case Animation::LoopMode::LOOP_NONE: { - loop->set_icon(get_theme_icon("Loop", "EditorIcons")); + case Animation::LOOP_NONE: { + loop->set_icon(get_theme_icon(SNAME("Loop"), SNAME("EditorIcons"))); loop->set_pressed(false); } break; - case Animation::LoopMode::LOOP_LINEAR: { - loop->set_icon(get_theme_icon("Loop", "EditorIcons")); + case Animation::LOOP_LINEAR: { + loop->set_icon(get_theme_icon(SNAME("Loop"), SNAME("EditorIcons"))); loop->set_pressed(true); } break; - case Animation::LoopMode::LOOP_PINGPONG: { - loop->set_icon(get_theme_icon("PingPongLoop", "EditorIcons")); + case Animation::LOOP_PINGPONG: { + loop->set_icon(get_theme_icon(SNAME("PingPongLoop"), SNAME("EditorIcons"))); loop->set_pressed(true); } break; default: @@ -1811,13 +1813,6 @@ void AnimationTimelineEdit::gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseMotion> mm = p_event; if (mm.is_valid()) { - if (hsize_rect.has_point(mm->get_position())) { - // Change the cursor to indicate that the track name column's width can be adjusted - set_default_cursor_shape(Control::CURSOR_HSIZE); - } else { - set_default_cursor_shape(Control::CURSOR_ARROW); - } - if (dragging_hsize) { int ofs = mm->get_position().x - dragging_hsize_from; name_limit = dragging_hsize_at + ofs; @@ -1833,6 +1828,15 @@ void AnimationTimelineEdit::gui_input(const Ref<InputEvent> &p_event) { } } +Control::CursorShape AnimationTimelineEdit::get_cursor_shape(const Point2 &p_pos) const { + if (dragging_hsize || hsize_rect.has_point(p_pos)) { + // Indicate that the track name column's width can be adjusted + return Control::CURSOR_HSIZE; + } else { + return get_default_cursor_shape(); + } +} + void AnimationTimelineEdit::_scroll_callback(Vector2 p_scroll_vec, bool p_alt) { // Timeline has no vertical scroll, so we change it to horizontal. p_scroll_vec.x += p_scroll_vec.y; @@ -1844,11 +1848,14 @@ void AnimationTimelineEdit::_pan_callback(Vector2 p_scroll_vec) { } void AnimationTimelineEdit::_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt) { - if (p_scroll_vec.y < 0) { - get_zoom()->set_value(get_zoom()->get_value() * 1.05); + double new_zoom_value; + double current_zoom_value = get_zoom()->get_value(); + if (current_zoom_value <= 0.1) { + new_zoom_value = MAX(0.01, current_zoom_value - 0.01 * SIGN(p_scroll_vec.y)); } else { - get_zoom()->set_value(get_zoom()->get_value() / 1.05); + new_zoom_value = p_scroll_vec.y > 0 ? MAX(0.01, current_zoom_value / 1.05) : current_zoom_value * 1.05; } + get_zoom()->set_value(new_zoom_value); } void AnimationTimelineEdit::set_use_fps(bool p_use_fps) { @@ -1878,13 +1885,8 @@ void AnimationTimelineEdit::_bind_methods() { } AnimationTimelineEdit::AnimationTimelineEdit() { - use_fps = false; - editing = false; name_limit = 150 * EDSCALE; - zoom = nullptr; - track_edit = nullptr; - play_position_pos = 0; play_position = memnew(Control); play_position->set_mouse_filter(MOUSE_FILTER_PASS); add_child(play_position); @@ -1927,9 +1929,6 @@ AnimationTimelineEdit::AnimationTimelineEdit() { add_track->get_popup()->connect("index_pressed", callable_mp(this, &AnimationTimelineEdit::_track_added)); len_hb->hide(); - dragging_timeline = false; - dragging_hsize = false; - panner.instantiate(); panner->set_callbacks(callable_mp(this, &AnimationTimelineEdit::_scroll_callback), callable_mp(this, &AnimationTimelineEdit::_pan_callback), callable_mp(this, &AnimationTimelineEdit::_zoom_callback)); @@ -1939,306 +1938,323 @@ AnimationTimelineEdit::AnimationTimelineEdit() { //////////////////////////////////// void AnimationTrackEdit::_notification(int p_what) { - if (p_what == NOTIFICATION_DRAW) { - if (animation.is_null()) { - return; - } - ERR_FAIL_INDEX(track, animation->get_track_count()); + switch (p_what) { + case NOTIFICATION_THEME_CHANGED: { + if (animation.is_null()) { + return; + } + ERR_FAIL_INDEX(track, animation->get_track_count()); - int limit = timeline->get_name_limit(); + type_icon = _get_key_type_icon(); + selected_icon = get_theme_icon(SNAME("KeySelected"), SNAME("EditorIcons")); + } break; - if (has_focus()) { - Color accent = get_theme_color(SNAME("accent_color"), SNAME("Editor")); - accent.a *= 0.7; - // Offside so the horizontal sides aren't cutoff. - draw_rect(Rect2(Point2(1 * EDSCALE, 0), get_size() - Size2(1 * EDSCALE, 0)), accent, false); - } + case NOTIFICATION_DRAW: { + if (animation.is_null()) { + return; + } + ERR_FAIL_INDEX(track, animation->get_track_count()); - Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label")); - int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label")); - Color color = get_theme_color(SNAME("font_color"), SNAME("Label")); - Ref<Texture2D> type_icons[9] = { - get_theme_icon(SNAME("KeyValue"), SNAME("EditorIcons")), - get_theme_icon(SNAME("KeyTrackPosition"), SNAME("EditorIcons")), - get_theme_icon(SNAME("KeyTrackRotation"), SNAME("EditorIcons")), - get_theme_icon(SNAME("KeyTrackScale"), SNAME("EditorIcons")), - get_theme_icon(SNAME("KeyTrackBlendShape"), SNAME("EditorIcons")), - get_theme_icon(SNAME("KeyCall"), SNAME("EditorIcons")), - get_theme_icon(SNAME("KeyBezier"), SNAME("EditorIcons")), - get_theme_icon(SNAME("KeyAudio"), SNAME("EditorIcons")), - get_theme_icon(SNAME("KeyAnimation"), SNAME("EditorIcons")) - }; - int hsep = get_theme_constant(SNAME("hseparation"), SNAME("ItemList")); - Color linecolor = color; - linecolor.a = 0.2; + int limit = timeline->get_name_limit(); - // NAMES AND ICONS // + if (track % 2 == 1) { + // Draw a background over odd lines to make long lists of tracks easier to read. + draw_rect(Rect2(Point2(1 * EDSCALE, 0), get_size() - Size2(1 * EDSCALE, 0)), Color(0.5, 0.5, 0.5, 0.05)); + } - { - Ref<Texture2D> check = animation->track_is_enabled(track) ? get_theme_icon(SNAME("checked"), SNAME("CheckBox")) : get_theme_icon(SNAME("unchecked"), SNAME("CheckBox")); + if (hovered) { + // Draw hover feedback. + draw_rect(Rect2(Point2(1 * EDSCALE, 0), get_size() - Size2(1 * EDSCALE, 0)), Color(0.5, 0.5, 0.5, 0.1)); + } - int ofs = in_group ? check->get_width() : 0; // Not the best reference for margin but.. + if (has_focus()) { + Color accent = get_theme_color(SNAME("accent_color"), SNAME("Editor")); + accent.a *= 0.7; + // Offside so the horizontal sides aren't cutoff. + draw_style_box(get_theme_stylebox(SNAME("Focus"), SNAME("EditorStyles")), Rect2(Point2(1 * EDSCALE, 0), get_size() - Size2(1 * EDSCALE, 0))); + } - check_rect = Rect2(Point2(ofs, int(get_size().height - check->get_height()) / 2), check->get_size()); - draw_texture(check, check_rect.position); - ofs += check->get_width() + hsep; + Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label")); + int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label")); + Color color = get_theme_color(SNAME("font_color"), SNAME("Label")); + int hsep = get_theme_constant(SNAME("h_separation"), SNAME("ItemList")); + Color linecolor = color; + linecolor.a = 0.2; - Ref<Texture2D> type_icon = type_icons[animation->track_get_type(track)]; - draw_texture(type_icon, Point2(ofs, int(get_size().height - type_icon->get_height()) / 2)); - ofs += type_icon->get_width() + hsep; + // NAMES AND ICONS // - NodePath path = animation->track_get_path(track); - Node *node = nullptr; - if (root && root->has_node(path)) { - node = root->get_node(path); - } + { + Ref<Texture2D> check = animation->track_is_enabled(track) ? get_theme_icon(SNAME("checked"), SNAME("CheckBox")) : get_theme_icon(SNAME("unchecked"), SNAME("CheckBox")); - String text; - Color text_color = color; - if (node && EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) { - text_color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); - } + int ofs = in_group ? check->get_width() : 0; // Not the best reference for margin but.. - if (in_group) { - if (animation->track_get_type(track) == Animation::TYPE_METHOD) { - text = TTR("Functions:"); - } else if (animation->track_get_type(track) == Animation::TYPE_AUDIO) { - text = TTR("Audio Clips:"); - } else if (animation->track_get_type(track) == Animation::TYPE_ANIMATION) { - text = TTR("Anim Clips:"); - } else { - text += path.get_concatenated_subnames(); + check_rect = Rect2(Point2(ofs, int(get_size().height - check->get_height()) / 2), check->get_size()); + draw_texture(check, check_rect.position); + ofs += check->get_width() + hsep; + + Ref<Texture2D> type_icon = _get_key_type_icon(); + draw_texture(type_icon, Point2(ofs, int(get_size().height - type_icon->get_height()) / 2)); + ofs += type_icon->get_width() + hsep; + + NodePath path = animation->track_get_path(track); + Node *node = nullptr; + if (root && root->has_node(path)) { + node = root->get_node(path); } - text_color.a *= 0.7; - } else if (node) { - Ref<Texture2D> icon = EditorNode::get_singleton()->get_object_icon(node, "Node"); - draw_texture(icon, Point2(ofs, int(get_size().height - icon->get_height()) / 2)); - icon_cache = icon; + String text; + Color text_color = color; + if (node && EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) { + text_color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); + } - text = String() + node->get_name() + ":" + path.get_concatenated_subnames(); - ofs += hsep; - ofs += icon->get_width(); + if (in_group) { + if (animation->track_get_type(track) == Animation::TYPE_METHOD) { + text = TTR("Functions:"); + } else if (animation->track_get_type(track) == Animation::TYPE_AUDIO) { + text = TTR("Audio Clips:"); + } else if (animation->track_get_type(track) == Animation::TYPE_ANIMATION) { + text = TTR("Anim Clips:"); + } else { + text += path.get_concatenated_subnames(); + } + text_color.a *= 0.7; + } else if (node) { + Ref<Texture2D> icon = EditorNode::get_singleton()->get_object_icon(node, "Node"); - } else { - icon_cache = type_icon; + draw_texture(icon, Point2(ofs, int(get_size().height - icon->get_height()) / 2)); + icon_cache = icon; - text = path; - } + text = String() + node->get_name() + ":" + path.get_concatenated_subnames(); + ofs += hsep; + ofs += icon->get_width(); + + } else { + icon_cache = type_icon; - path_cache = text; + text = path; + } - path_rect = Rect2(ofs, 0, limit - ofs - hsep, get_size().height); + path_cache = text; - Vector2 string_pos = Point2(ofs, (get_size().height - font->get_height(font_size)) / 2 + font->get_ascent(font_size)); - string_pos = string_pos.floor(); - draw_string(font, string_pos, text, HORIZONTAL_ALIGNMENT_LEFT, limit - ofs - hsep, font_size, text_color); + path_rect = Rect2(ofs, 0, limit - ofs - hsep, get_size().height); - draw_line(Point2(limit, 0), Point2(limit, get_size().height), linecolor, Math::round(EDSCALE)); - } + Vector2 string_pos = Point2(ofs, (get_size().height - font->get_height(font_size)) / 2 + font->get_ascent(font_size)); + string_pos = string_pos.floor(); + draw_string(font, string_pos, text, HORIZONTAL_ALIGNMENT_LEFT, limit - ofs - hsep, font_size, text_color); - // KEYFRAMES // + draw_line(Point2(limit, 0), Point2(limit, get_size().height), linecolor, Math::round(EDSCALE)); + } - draw_bg(limit, get_size().width - timeline->get_buttons_width()); + // KEYFRAMES // - { - float scale = timeline->get_zoom_scale(); - int limit_end = get_size().width - timeline->get_buttons_width(); + draw_bg(limit, get_size().width - timeline->get_buttons_width()); - for (int i = 0; i < animation->track_get_key_count(track); i++) { - float offset = animation->track_get_key_time(track, i) - timeline->get_value(); - if (editor->is_key_selected(track, i) && editor->is_moving_selection()) { - offset = editor->snap_time(offset + editor->get_moving_selection_offset(), true); - } - offset = offset * scale + limit; - if (i < animation->track_get_key_count(track) - 1) { - float offset_n = animation->track_get_key_time(track, i + 1) - timeline->get_value(); - if (editor->is_key_selected(track, i + 1) && editor->is_moving_selection()) { - offset_n = editor->snap_time(offset_n + editor->get_moving_selection_offset()); + { + float scale = timeline->get_zoom_scale(); + int limit_end = get_size().width - timeline->get_buttons_width(); + + for (int i = 0; i < animation->track_get_key_count(track); i++) { + float offset = animation->track_get_key_time(track, i) - timeline->get_value(); + if (editor->is_key_selected(track, i) && editor->is_moving_selection()) { + offset = editor->snap_time(offset + editor->get_moving_selection_offset(), true); } - offset_n = offset_n * scale + limit; + offset = offset * scale + limit; + if (i < animation->track_get_key_count(track) - 1) { + float offset_n = animation->track_get_key_time(track, i + 1) - timeline->get_value(); + if (editor->is_key_selected(track, i + 1) && editor->is_moving_selection()) { + offset_n = editor->snap_time(offset_n + editor->get_moving_selection_offset()); + } + offset_n = offset_n * scale + limit; - draw_key_link(i, scale, int(offset), int(offset_n), limit, limit_end); - } + draw_key_link(i, scale, int(offset), int(offset_n), limit, limit_end); + } - draw_key(i, scale, int(offset), editor->is_key_selected(track, i), limit, limit_end); + draw_key(i, scale, int(offset), editor->is_key_selected(track, i), limit, limit_end); + } } - } - draw_fg(limit, get_size().width - timeline->get_buttons_width()); + draw_fg(limit, get_size().width - timeline->get_buttons_width()); - // BUTTONS // + // BUTTONS // - { - Ref<Texture2D> wrap_icon[2] = { - get_theme_icon(SNAME("InterpWrapClamp"), SNAME("EditorIcons")), - get_theme_icon(SNAME("InterpWrapLoop"), SNAME("EditorIcons")), - }; - - Ref<Texture2D> interp_icon[3] = { - get_theme_icon(SNAME("InterpRaw"), SNAME("EditorIcons")), - get_theme_icon(SNAME("InterpLinear"), SNAME("EditorIcons")), - get_theme_icon(SNAME("InterpCubic"), SNAME("EditorIcons")) - }; - Ref<Texture2D> cont_icon[4] = { - get_theme_icon(SNAME("TrackContinuous"), SNAME("EditorIcons")), - get_theme_icon(SNAME("TrackDiscrete"), SNAME("EditorIcons")), - get_theme_icon(SNAME("TrackTrigger"), SNAME("EditorIcons")), - get_theme_icon(SNAME("TrackCapture"), SNAME("EditorIcons")) - }; - - int ofs = get_size().width - timeline->get_buttons_width(); - - Ref<Texture2D> down_icon = get_theme_icon(SNAME("select_arrow"), SNAME("Tree")); - - draw_line(Point2(ofs, 0), Point2(ofs, get_size().height), linecolor, Math::round(EDSCALE)); - - ofs += hsep; { - // Callmode. + Ref<Texture2D> wrap_icon[2] = { + get_theme_icon(SNAME("InterpWrapClamp"), SNAME("EditorIcons")), + get_theme_icon(SNAME("InterpWrapLoop"), SNAME("EditorIcons")), + }; - Animation::UpdateMode update_mode; + Ref<Texture2D> interp_icon[3] = { + get_theme_icon(SNAME("InterpRaw"), SNAME("EditorIcons")), + get_theme_icon(SNAME("InterpLinear"), SNAME("EditorIcons")), + get_theme_icon(SNAME("InterpCubic"), SNAME("EditorIcons")) + }; + Ref<Texture2D> cont_icon[4] = { + get_theme_icon(SNAME("TrackContinuous"), SNAME("EditorIcons")), + get_theme_icon(SNAME("TrackDiscrete"), SNAME("EditorIcons")), + get_theme_icon(SNAME("TrackTrigger"), SNAME("EditorIcons")), + get_theme_icon(SNAME("TrackCapture"), SNAME("EditorIcons")) + }; - if (animation->track_get_type(track) == Animation::TYPE_VALUE) { - update_mode = animation->value_track_get_update_mode(track); - } else { - update_mode = Animation::UPDATE_CONTINUOUS; - } - - Ref<Texture2D> update_icon = cont_icon[update_mode]; + int ofs = get_size().width - timeline->get_buttons_width(); - update_mode_rect.position.x = ofs; - update_mode_rect.position.y = int(get_size().height - update_icon->get_height()) / 2; - update_mode_rect.size = update_icon->get_size(); + Ref<Texture2D> down_icon = get_theme_icon(SNAME("select_arrow"), SNAME("Tree")); - if (!animation->track_is_compressed(track) && animation->track_get_type(track) == Animation::TYPE_VALUE) { - draw_texture(update_icon, update_mode_rect.position); - } - // Make it easier to click. - update_mode_rect.position.y = 0; - update_mode_rect.size.y = get_size().height; - - ofs += update_icon->get_width() + hsep; - update_mode_rect.size.x += hsep; - - if (animation->track_get_type(track) == Animation::TYPE_VALUE) { - draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2)); - update_mode_rect.size.x += down_icon->get_width(); - bezier_edit_rect = Rect2(); - } else if (animation->track_get_type(track) == Animation::TYPE_BEZIER) { - Ref<Texture2D> bezier_icon = get_theme_icon(SNAME("EditBezier"), SNAME("EditorIcons")); - update_mode_rect.size.x += down_icon->get_width(); - bezier_edit_rect.position = update_mode_rect.position + (update_mode_rect.size - bezier_icon->get_size()) / 2; - bezier_edit_rect.size = bezier_icon->get_size(); - draw_texture(bezier_icon, bezier_edit_rect.position); - update_mode_rect = Rect2(); - } else { - update_mode_rect = Rect2(); - bezier_edit_rect = Rect2(); - } + draw_line(Point2(ofs, 0), Point2(ofs, get_size().height), linecolor, Math::round(EDSCALE)); - ofs += down_icon->get_width(); - draw_line(Point2(ofs + hsep * 0.5, 0), Point2(ofs + hsep * 0.5, get_size().height), linecolor, Math::round(EDSCALE)); ofs += hsep; - } + { + // Callmode. - { - // Interp. + Animation::UpdateMode update_mode; - Animation::InterpolationType interp_mode = animation->track_get_interpolation_type(track); + if (animation->track_get_type(track) == Animation::TYPE_VALUE) { + update_mode = animation->value_track_get_update_mode(track); + } else { + update_mode = Animation::UPDATE_CONTINUOUS; + } - Ref<Texture2D> icon = interp_icon[interp_mode]; + Ref<Texture2D> update_icon = cont_icon[update_mode]; - interp_mode_rect.position.x = ofs; - interp_mode_rect.position.y = int(get_size().height - icon->get_height()) / 2; - interp_mode_rect.size = icon->get_size(); + update_mode_rect.position.x = ofs; + update_mode_rect.position.y = int(get_size().height - update_icon->get_height()) / 2; + update_mode_rect.size = update_icon->get_size(); - 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)) { - draw_texture(icon, interp_mode_rect.position); - } - // Make it easier to click. - interp_mode_rect.position.y = 0; - interp_mode_rect.size.y = get_size().height; + if (!animation->track_is_compressed(track) && animation->track_get_type(track) == Animation::TYPE_VALUE) { + draw_texture(update_icon, update_mode_rect.position); + } + // Make it easier to click. + update_mode_rect.position.y = 0; + update_mode_rect.size.y = get_size().height; - ofs += icon->get_width() + hsep; - interp_mode_rect.size.x += hsep; + ofs += update_icon->get_width() + hsep / 2; + update_mode_rect.size.x += hsep / 2; - 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)) { - draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2)); - interp_mode_rect.size.x += down_icon->get_width(); - } else { - interp_mode_rect = Rect2(); + if (animation->track_get_type(track) == Animation::TYPE_VALUE) { + draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2)); + update_mode_rect.size.x += down_icon->get_width(); + } else if (animation->track_get_type(track) == Animation::TYPE_BEZIER) { + Ref<Texture2D> bezier_icon = get_theme_icon(SNAME("EditBezier"), SNAME("EditorIcons")); + update_mode_rect.size.x += down_icon->get_width(); + + update_mode_rect = Rect2(); + } else { + update_mode_rect = Rect2(); + } + + ofs += down_icon->get_width(); + draw_line(Point2(ofs + hsep * 0.5, 0), Point2(ofs + hsep * 0.5, get_size().height), linecolor, Math::round(EDSCALE)); + ofs += hsep; } - ofs += down_icon->get_width(); - draw_line(Point2(ofs + hsep * 0.5, 0), Point2(ofs + hsep * 0.5, get_size().height), linecolor, Math::round(EDSCALE)); - ofs += hsep; - } + { + // Interp. - { - // Loop. + Animation::InterpolationType interp_mode = animation->track_get_interpolation_type(track); - bool loop_wrap = animation->track_get_interpolation_loop_wrap(track); + Ref<Texture2D> icon = interp_icon[interp_mode]; - Ref<Texture2D> icon = wrap_icon[loop_wrap ? 1 : 0]; + interp_mode_rect.position.x = ofs; + interp_mode_rect.position.y = int(get_size().height - icon->get_height()) / 2; + interp_mode_rect.size = icon->get_size(); - loop_wrap_rect.position.x = ofs; - loop_wrap_rect.position.y = int(get_size().height - icon->get_height()) / 2; - loop_wrap_rect.size = icon->get_size(); + 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)) { + draw_texture(icon, interp_mode_rect.position); + } + // Make it easier to click. + interp_mode_rect.position.y = 0; + interp_mode_rect.size.y = get_size().height; + + ofs += icon->get_width() + hsep / 2; + interp_mode_rect.size.x += hsep / 2; + + 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)) { + draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2)); + interp_mode_rect.size.x += down_icon->get_width(); + } else { + interp_mode_rect = Rect2(); + } - 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)) { - draw_texture(icon, loop_wrap_rect.position); + ofs += down_icon->get_width(); + draw_line(Point2(ofs + hsep * 0.5, 0), Point2(ofs + hsep * 0.5, get_size().height), linecolor, Math::round(EDSCALE)); + ofs += hsep; } - loop_wrap_rect.position.y = 0; - loop_wrap_rect.size.y = get_size().height; + { + // Loop. - ofs += icon->get_width() + hsep; - loop_wrap_rect.size.x += hsep; + bool loop_wrap = animation->track_get_interpolation_loop_wrap(track); - 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)) { - draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2)); - loop_wrap_rect.size.x += down_icon->get_width(); - } else { - loop_wrap_rect = Rect2(); - } + Ref<Texture2D> icon = wrap_icon[loop_wrap ? 1 : 0]; - ofs += down_icon->get_width(); - draw_line(Point2(ofs + hsep * 0.5, 0), Point2(ofs + hsep * 0.5, get_size().height), linecolor, Math::round(EDSCALE)); - ofs += hsep; - } + loop_wrap_rect.position.x = ofs; + loop_wrap_rect.position.y = int(get_size().height - icon->get_height()) / 2; + loop_wrap_rect.size = icon->get_size(); - { - // Erase. + 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)) { + draw_texture(icon, loop_wrap_rect.position); + } - Ref<Texture2D> icon = get_theme_icon(animation->track_is_compressed(track) ? SNAME("Lock") : SNAME("Remove"), SNAME("EditorIcons")); + loop_wrap_rect.position.y = 0; + loop_wrap_rect.size.y = get_size().height; - remove_rect.position.x = ofs + ((get_size().width - ofs) - icon->get_width()) / 2; - remove_rect.position.y = int(get_size().height - icon->get_height()) / 2; - remove_rect.size = icon->get_size(); + ofs += icon->get_width() + hsep / 2; + loop_wrap_rect.size.x += hsep / 2; - draw_texture(icon, remove_rect.position); - } - } + 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)) { + draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2)); + loop_wrap_rect.size.x += down_icon->get_width(); + } else { + loop_wrap_rect = Rect2(); + } - if (in_group) { - draw_line(Vector2(timeline->get_name_limit(), get_size().height), get_size(), linecolor, Math::round(EDSCALE)); - } else { - draw_line(Vector2(0, get_size().height), get_size(), linecolor, Math::round(EDSCALE)); - } + ofs += down_icon->get_width(); + draw_line(Point2(ofs + hsep * 0.5, 0), Point2(ofs + hsep * 0.5, get_size().height), linecolor, Math::round(EDSCALE)); + ofs += hsep; + } + + { + // Erase. - if (dropping_at != 0) { - Color drop_color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); - if (dropping_at < 0) { - draw_line(Vector2(0, 0), Vector2(get_size().width, 0), drop_color, Math::round(EDSCALE)); + Ref<Texture2D> icon = get_theme_icon(animation->track_is_compressed(track) ? SNAME("Lock") : SNAME("Remove"), SNAME("EditorIcons")); + + remove_rect.position.x = ofs + ((get_size().width - ofs) - icon->get_width()); + remove_rect.position.y = int(get_size().height - icon->get_height()) / 2; + remove_rect.size = icon->get_size(); + + draw_texture(icon, remove_rect.position); + } + } + + if (in_group) { + draw_line(Vector2(timeline->get_name_limit(), get_size().height), get_size(), linecolor, Math::round(EDSCALE)); } else { - draw_line(Vector2(0, get_size().height), get_size(), drop_color, Math::round(EDSCALE)); + draw_line(Vector2(0, get_size().height), get_size(), linecolor, Math::round(EDSCALE)); } - } - } - if (p_what == NOTIFICATION_MOUSE_EXIT || p_what == NOTIFICATION_DRAG_END) { - cancel_drop(); + if (dropping_at != 0) { + Color drop_color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); + if (dropping_at < 0) { + draw_line(Vector2(0, 0), Vector2(get_size().width, 0), drop_color, Math::round(EDSCALE)); + } else { + draw_line(Vector2(0, get_size().height), get_size(), drop_color, Math::round(EDSCALE)); + } + } + } break; + + case NOTIFICATION_MOUSE_ENTER: + hovered = true; + update(); + break; + case NOTIFICATION_MOUSE_EXIT: + hovered = false; + // When the mouse cursor exits the track, we're no longer hovering any keyframe. + hovering_key_idx = -1; + update(); + [[fallthrough]]; + case NOTIFICATION_DRAG_END: { + cancel_drop(); + } break; } } @@ -2347,7 +2363,13 @@ void AnimationTrackEdit::draw_key(int p_index, float p_pixels_sec, int p_x, bool } } - draw_texture(icon_to_draw, ofs); + // Use a different color for the currently hovered key. + // The color multiplier is chosen to work with both dark and light editor themes, + // and on both unselected and selected key icons. + draw_texture( + icon_to_draw, + ofs, + p_index == hovering_key_idx ? get_theme_color(SNAME("folder_icon_modulate"), SNAME("FileDialog")) : Color(1, 1, 1)); } // Helper. @@ -2421,22 +2443,10 @@ void AnimationTrackEdit::set_animation_and_track(const Ref<Animation> &p_animati track = p_track; update(); - Ref<Texture2D> type_icons[9] = { - get_theme_icon(SNAME("KeyValue"), SNAME("EditorIcons")), - get_theme_icon(SNAME("KeyXPosition"), SNAME("EditorIcons")), - get_theme_icon(SNAME("KeyXRotation"), SNAME("EditorIcons")), - get_theme_icon(SNAME("KeyXScale"), SNAME("EditorIcons")), - get_theme_icon(SNAME("KeyBlendShape"), SNAME("EditorIcons")), - get_theme_icon(SNAME("KeyCall"), SNAME("EditorIcons")), - get_theme_icon(SNAME("KeyBezier"), SNAME("EditorIcons")), - get_theme_icon(SNAME("KeyAudio"), SNAME("EditorIcons")), - get_theme_icon(SNAME("KeyAnimation"), SNAME("EditorIcons")) - }; - ERR_FAIL_INDEX(track, animation->get_track_count()); node_path = animation->track_get_path(p_track); - type_icon = type_icons[animation->track_get_type(track)]; + type_icon = _get_key_type_icon(); selected_icon = get_theme_icon(SNAME("KeySelected"), SNAME("EditorIcons")); } @@ -2448,7 +2458,7 @@ Size2 AnimationTrackEdit::get_minimum_size() const { Ref<Texture2D> texture = get_theme_icon(SNAME("Object"), SNAME("EditorIcons")); Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label")); int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label")); - int separation = get_theme_constant(SNAME("vseparation"), SNAME("ItemList")); + int separation = get_theme_constant(SNAME("v_separation"), SNAME("ItemList")); int max_h = MAX(texture->get_height(), font->get_height(font_size)); max_h = MAX(max_h, get_key_height()); @@ -2518,7 +2528,7 @@ bool AnimationTrackEdit::_is_value_key_valid(const Variant &p_key_value, Variant return false; } - RES res; + Ref<Resource> res; Vector<StringName> leftover_path; Node *node = root->get_node_and_resource(animation->track_get_path(track), res, leftover_path); @@ -2537,6 +2547,21 @@ bool AnimationTrackEdit::_is_value_key_valid(const Variant &p_key_value, Variant return (!prop_exists || Variant::can_convert(p_key_value.get_type(), r_valid_type)); } +Ref<Texture2D> AnimationTrackEdit::_get_key_type_icon() const { + const Ref<Texture2D> type_icons[9] = { + get_theme_icon(SNAME("KeyValue"), SNAME("EditorIcons")), + get_theme_icon(SNAME("KeyTrackPosition"), SNAME("EditorIcons")), + get_theme_icon(SNAME("KeyTrackRotation"), SNAME("EditorIcons")), + get_theme_icon(SNAME("KeyTrackScale"), SNAME("EditorIcons")), + get_theme_icon(SNAME("KeyTrackBlendShape"), SNAME("EditorIcons")), + get_theme_icon(SNAME("KeyCall"), SNAME("EditorIcons")), + get_theme_icon(SNAME("KeyBezier"), SNAME("EditorIcons")), + get_theme_icon(SNAME("KeyAudio"), SNAME("EditorIcons")), + get_theme_icon(SNAME("KeyAnimation"), SNAME("EditorIcons")) + }; + return type_icons[animation->track_get_type(track)]; +} + String AnimationTrackEdit::get_tooltip(const Point2 &p_pos) const { if (check_rect.has_point(p_pos)) { return TTR("Toggle this track on/off."); @@ -2594,34 +2619,33 @@ String AnimationTrackEdit::get_tooltip(const Point2 &p_pos) const { } if (key_idx != -1) { - String text = TTR("Time (s): ") + rtos(animation->track_get_key_time(track, key_idx)) + "\n"; + String text = TTR("Time (s):") + " " + rtos(animation->track_get_key_time(track, key_idx)) + "\n"; switch (animation->track_get_type(track)) { case Animation::TYPE_POSITION_3D: { Vector3 t = animation->track_get_key_value(track, key_idx); - text += "Position: " + String(t) + "\n"; + text += TTR("Position:") + " " + String(t) + "\n"; } break; case Animation::TYPE_ROTATION_3D: { Quaternion t = animation->track_get_key_value(track, key_idx); - text += "Rotation: " + String(t) + "\n"; + text += TTR("Rotation:") + " " + String(t) + "\n"; } break; case Animation::TYPE_SCALE_3D: { Vector3 t = animation->track_get_key_value(track, key_idx); - text += "Scale: " + String(t) + "\n"; + text += TTR("Scale:") + " " + String(t) + "\n"; } break; case Animation::TYPE_BLEND_SHAPE: { float t = animation->track_get_key_value(track, key_idx); - text += "Blend Shape: " + itos(t) + "\n"; + text += TTR("Blend Shape:") + " " + itos(t) + "\n"; } break; case Animation::TYPE_VALUE: { const Variant &v = animation->track_get_key_value(track, key_idx); - text += "Type: " + Variant::get_type_name(v.get_type()) + "\n"; + text += TTR("Type:") + " " + Variant::get_type_name(v.get_type()) + "\n"; Variant::Type valid_type = Variant::NIL; + text += TTR("Value:") + " " + String(v); if (!_is_value_key_valid(v, valid_type)) { - text += "Value: " + String(v) + " (Invalid, expected type: " + Variant::get_type_name(valid_type) + ")\n"; - } else { - text += "Value: " + String(v) + "\n"; + text += " " + vformat(TTR("(Invalid, expected type: %s)"), Variant::get_type_name(valid_type)); } - text += "Easing: " + rtos(animation->track_get_key_transition(track, key_idx)); + text += "\n" + TTR("Easing:") + " " + rtos(animation->track_get_key_transition(track, key_idx)); } break; case Animation::TYPE_METHOD: { @@ -2645,26 +2669,24 @@ String AnimationTrackEdit::get_tooltip(const Point2 &p_pos) const { } break; case Animation::TYPE_BEZIER: { float h = animation->bezier_track_get_key_value(track, key_idx); - text += "Value: " + rtos(h) + "\n"; + text += TTR("Value:") + " " + rtos(h) + "\n"; Vector2 ih = animation->bezier_track_get_key_in_handle(track, key_idx); - text += "In-Handle: " + ih + "\n"; + text += TTR("In-Handle:") + " " + ih + "\n"; Vector2 oh = animation->bezier_track_get_key_out_handle(track, key_idx); - text += "Out-Handle: " + oh + "\n"; + text += TTR("Out-Handle:") + " " + oh + "\n"; int hm = animation->bezier_track_get_key_handle_mode(track, key_idx); - text += "Handle mode: "; switch (hm) { case Animation::HANDLE_MODE_FREE: { - text += "Free"; + text += TTR("Handle mode: Free\n"); } break; case Animation::HANDLE_MODE_BALANCED: { - text += "Balanced"; + text += TTR("Handle mode: Balanced\n"); } break; } - text += "\n"; } break; case Animation::TYPE_AUDIO: { String stream_name = "null"; - RES stream = animation->audio_track_get_key_stream(track, key_idx); + Ref<Resource> stream = animation->audio_track_get_key_stream(track, key_idx); if (stream.is_valid()) { if (stream->get_path().is_resource_file()) { stream_name = stream->get_path().get_file(); @@ -2675,15 +2697,15 @@ String AnimationTrackEdit::get_tooltip(const Point2 &p_pos) const { } } - text += "Stream: " + stream_name + "\n"; + text += TTR("Stream:") + " " + stream_name + "\n"; float so = animation->audio_track_get_key_start_offset(track, key_idx); - text += "Start (s): " + rtos(so) + "\n"; + text += TTR("Start (s):") + " " + rtos(so) + "\n"; float eo = animation->audio_track_get_key_end_offset(track, key_idx); - text += "End (s): " + rtos(eo) + "\n"; + text += TTR("End (s):") + " " + rtos(eo) + "\n"; } break; case Animation::TYPE_ANIMATION: { String name = animation->animation_track_get_key_animation(track, key_idx); - text += "Animation Clip: " + name + "\n"; + text += TTR("Animation Clip:") + " " + name + "\n"; } break; } return text; @@ -2743,7 +2765,7 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) { menu->add_icon_item(get_theme_icon(SNAME("TrackDiscrete"), SNAME("EditorIcons")), TTR("Discrete"), MENU_CALL_MODE_DISCRETE); menu->add_icon_item(get_theme_icon(SNAME("TrackTrigger"), SNAME("EditorIcons")), TTR("Trigger"), MENU_CALL_MODE_TRIGGER); menu->add_icon_item(get_theme_icon(SNAME("TrackCapture"), SNAME("EditorIcons")), TTR("Capture"), MENU_CALL_MODE_CAPTURE); - menu->set_as_minsize(); + menu->reset_size(); Vector2 popup_pos = get_screen_position() + update_mode_rect.position + Vector2(0, update_mode_rect.size.height); menu->set_position(popup_pos); @@ -2761,7 +2783,7 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) { menu->add_icon_item(get_theme_icon(SNAME("InterpRaw"), SNAME("EditorIcons")), TTR("Nearest"), MENU_INTERPOLATION_NEAREST); menu->add_icon_item(get_theme_icon(SNAME("InterpLinear"), SNAME("EditorIcons")), TTR("Linear"), MENU_INTERPOLATION_LINEAR); menu->add_icon_item(get_theme_icon(SNAME("InterpCubic"), SNAME("EditorIcons")), TTR("Cubic"), MENU_INTERPOLATION_CUBIC); - menu->set_as_minsize(); + menu->reset_size(); Vector2 popup_pos = get_screen_position() + interp_mode_rect.position + Vector2(0, interp_mode_rect.size.height); menu->set_position(popup_pos); @@ -2778,7 +2800,7 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) { menu->clear(); menu->add_icon_item(get_theme_icon(SNAME("InterpWrapClamp"), SNAME("EditorIcons")), TTR("Clamp Loop Interp"), MENU_LOOP_CLAMP); menu->add_icon_item(get_theme_icon(SNAME("InterpWrapLoop"), SNAME("EditorIcons")), TTR("Wrap Loop Interp"), MENU_LOOP_WRAP); - menu->set_as_minsize(); + menu->reset_size(); Vector2 popup_pos = get_screen_position() + loop_wrap_rect.position + Vector2(0, loop_wrap_rect.size.height); menu->set_position(popup_pos); @@ -2792,11 +2814,6 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) { return; } - if (bezier_edit_rect.has_point(pos)) { - emit_signal(SNAME("bezier_edit")); - accept_event(); - } - // Check keyframes. if (!animation->track_is_compressed(track)) { // Selecting compressed keyframes for editing is not possible. @@ -2885,7 +2902,7 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) { menu->add_separator(); menu->add_icon_item(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")), TTR("Delete Key(s)"), MENU_KEY_DELETE); } - menu->set_as_minsize(); + menu->reset_size(); menu->set_position(get_screen_position() + get_local_mouse_position()); menu->popup(); @@ -2936,6 +2953,57 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) { } Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid()) { + const int previous_hovering_key_idx = hovering_key_idx; + + // Hovering compressed keyframes for editing is not possible. + if (!animation->track_is_compressed(track)) { + const float scale = timeline->get_zoom_scale(); + const int limit = timeline->get_name_limit(); + const int limit_end = get_size().width - timeline->get_buttons_width(); + // Left Border including space occupied by keyframes on t=0. + const int limit_start_hitbox = limit - type_icon->get_width(); + const Point2 pos = mm->get_position(); + + if (pos.x >= limit_start_hitbox && pos.x <= limit_end) { + // Use the same logic as key selection to ensure that hovering accurately represents + // which key will be selected when clicking. + int key_idx = -1; + float key_distance = 1e20; + + hovering_key_idx = -1; + + // Hovering should happen in the opposite order of drawing for more accurate overlap hovering. + for (int i = animation->track_get_key_count(track) - 1; i >= 0; i--) { + Rect2 rect = get_key_rect(i, scale); + float offset = animation->track_get_key_time(track, i) - timeline->get_value(); + offset = offset * scale + limit; + rect.position.x += offset; + + if (rect.has_point(pos)) { + if (is_key_selectable_by_distance()) { + const float distance = ABS(offset - pos.x); + if (key_idx == -1 || distance < key_distance) { + key_idx = i; + key_distance = distance; + hovering_key_idx = i; + } + } else { + // First one does it. + hovering_key_idx = i; + break; + } + } + } + + if (hovering_key_idx != previous_hovering_key_idx) { + // Required to draw keyframe hover feedback on the correct keyframe. + update(); + } + } + } + } + if (mm.is_valid() && (mm->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE && moving_selection_attempt) { if (!moving_selection) { moving_selection = true; @@ -3124,13 +3192,13 @@ void AnimationTrackEdit::_bind_methods() { ADD_SIGNAL(MethodInfo("timeline_changed", PropertyInfo(Variant::FLOAT, "position"), PropertyInfo(Variant::BOOL, "drag"), PropertyInfo(Variant::BOOL, "timeline_only"))); ADD_SIGNAL(MethodInfo("remove_request", PropertyInfo(Variant::INT, "track"))); ADD_SIGNAL(MethodInfo("dropped", PropertyInfo(Variant::INT, "from_track"), PropertyInfo(Variant::INT, "to_track"))); - ADD_SIGNAL(MethodInfo("insert_key", PropertyInfo(Variant::FLOAT, "ofs"))); + ADD_SIGNAL(MethodInfo("insert_key", PropertyInfo(Variant::FLOAT, "offset"))); ADD_SIGNAL(MethodInfo("select_key", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::BOOL, "single"))); ADD_SIGNAL(MethodInfo("deselect_key", PropertyInfo(Variant::INT, "index"))); ADD_SIGNAL(MethodInfo("bezier_edit")); ADD_SIGNAL(MethodInfo("move_selection_begin")); - ADD_SIGNAL(MethodInfo("move_selection", PropertyInfo(Variant::FLOAT, "ofs"))); + ADD_SIGNAL(MethodInfo("move_selection", PropertyInfo(Variant::FLOAT, "offset"))); ADD_SIGNAL(MethodInfo("move_selection_commit")); ADD_SIGNAL(MethodInfo("move_selection_cancel")); @@ -3141,22 +3209,6 @@ void AnimationTrackEdit::_bind_methods() { } AnimationTrackEdit::AnimationTrackEdit() { - undo_redo = nullptr; - timeline = nullptr; - root = nullptr; - path = nullptr; - path_popup = nullptr; - menu = nullptr; - clicking_on_name = false; - dropping_at = 0; - - in_group = false; - - moving_selection_attempt = false; - moving_selection = false; - select_single_attempt = -1; - - play_position_pos = 0; play_position = memnew(Control); play_position->set_mouse_filter(MOUSE_FILTER_PASS); add_child(play_position); @@ -3189,7 +3241,7 @@ AnimationTrackEdit *AnimationTrackEditPlugin::create_value_track_edit(Object *p_ }; Callable::CallError ce; - return Object::cast_to<AnimationTrackEdit>(get_script_instance()->call("create_value_track_edit", (const Variant **)&argptrs, 6, ce).operator Object *()); + return Object::cast_to<AnimationTrackEdit>(get_script_instance()->callp("create_value_track_edit", (const Variant **)&argptrs, 6, ce).operator Object *()); } return nullptr; } @@ -3211,40 +3263,42 @@ AnimationTrackEdit *AnimationTrackEditPlugin::create_animation_track_edit(Object /////////////////////////////////////// void AnimationTrackEditGroup::_notification(int p_what) { - if (p_what == NOTIFICATION_DRAW) { - Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label")); - int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label")); - int separation = get_theme_constant(SNAME("hseparation"), SNAME("ItemList")); - Color color = get_theme_color(SNAME("font_color"), SNAME("Label")); - - if (root && root->has_node(node)) { - Node *n = root->get_node(node); - if (n && EditorNode::get_singleton()->get_editor_selection()->is_selected(n)) { - color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); + switch (p_what) { + case NOTIFICATION_DRAW: { + Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label")); + int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label")); + int separation = get_theme_constant(SNAME("h_separation"), SNAME("ItemList")); + Color color = get_theme_color(SNAME("font_color"), SNAME("Label")); + + if (root && root->has_node(node)) { + Node *n = root->get_node(node); + if (n && EditorNode::get_singleton()->get_editor_selection()->is_selected(n)) { + color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); + } } - } - Color bgcol = get_theme_color(SNAME("dark_color_2"), SNAME("Editor")); - bgcol.a *= 0.6; - draw_rect(Rect2(Point2(), get_size()), bgcol); - Color linecolor = color; - linecolor.a = 0.2; + Color bgcol = get_theme_color(SNAME("dark_color_2"), SNAME("Editor")); + bgcol.a *= 0.6; + draw_rect(Rect2(Point2(), get_size()), bgcol); + Color linecolor = color; + linecolor.a = 0.2; - draw_line(Point2(), Point2(get_size().width, 0), linecolor, Math::round(EDSCALE)); - draw_line(Point2(timeline->get_name_limit(), 0), Point2(timeline->get_name_limit(), get_size().height), linecolor, Math::round(EDSCALE)); - draw_line(Point2(get_size().width - timeline->get_buttons_width(), 0), Point2(get_size().width - timeline->get_buttons_width(), get_size().height), linecolor, Math::round(EDSCALE)); + draw_line(Point2(), Point2(get_size().width, 0), linecolor, Math::round(EDSCALE)); + draw_line(Point2(timeline->get_name_limit(), 0), Point2(timeline->get_name_limit(), get_size().height), linecolor, Math::round(EDSCALE)); + draw_line(Point2(get_size().width - timeline->get_buttons_width(), 0), Point2(get_size().width - timeline->get_buttons_width(), get_size().height), linecolor, Math::round(EDSCALE)); - int ofs = 0; - draw_texture(icon, Point2(ofs, int(get_size().height - icon->get_height()) / 2)); - ofs += separation + icon->get_width(); - draw_string(font, Point2(ofs, int(get_size().height - font->get_height(font_size)) / 2 + font->get_ascent(font_size)), node_name, HORIZONTAL_ALIGNMENT_LEFT, timeline->get_name_limit() - ofs, font_size, color); + int ofs = 0; + draw_texture(icon, Point2(ofs, int(get_size().height - icon->get_height()) / 2)); + ofs += separation + icon->get_width(); + draw_string(font, Point2(ofs, int(get_size().height - font->get_height(font_size)) / 2 + font->get_ascent(font_size)), node_name, HORIZONTAL_ALIGNMENT_LEFT, timeline->get_name_limit() - ofs, font_size, color); - int px = (-timeline->get_value() + timeline->get_play_position()) * timeline->get_zoom_scale() + timeline->get_name_limit(); + int px = (-timeline->get_value() + timeline->get_play_position()) * timeline->get_zoom_scale() + timeline->get_name_limit(); - if (px >= timeline->get_name_limit() && px < (get_size().width - timeline->get_buttons_width())) { - Color accent = get_theme_color(SNAME("accent_color"), SNAME("Editor")); - draw_line(Point2(px, 0), Point2(px, get_size().height), accent, Math::round(2 * EDSCALE)); - } + if (px >= timeline->get_name_limit() && px < (get_size().width - timeline->get_buttons_width())) { + Color accent = get_theme_color(SNAME("accent_color"), SNAME("Editor")); + draw_line(Point2(px, 0), Point2(px, get_size().height), accent, Math::round(2 * EDSCALE)); + } + } break; } } @@ -3259,7 +3313,7 @@ void AnimationTrackEditGroup::set_type_and_name(const Ref<Texture2D> &p_type, co Size2 AnimationTrackEditGroup::get_minimum_size() const { Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label")); int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label")); - int separation = get_theme_constant(SNAME("vseparation"), SNAME("ItemList")); + int separation = get_theme_constant(SNAME("v_separation"), SNAME("ItemList")); return Vector2(0, MAX(font->get_height(font_size), icon->get_height()) + separation); } @@ -3289,7 +3343,7 @@ AnimationTrackEditGroup::AnimationTrackEditGroup() { ////////////////////////////////////// void AnimationTrackEditor::add_track_edit_plugin(const Ref<AnimationTrackEditPlugin> &p_plugin) { - if (track_edit_plugins.find(p_plugin) != -1) { + if (track_edit_plugins.has(p_plugin)) { return; } track_edit_plugins.push_back(p_plugin); @@ -3326,10 +3380,21 @@ void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim) { snap->set_disabled(false); snap_mode->set_disabled(false); + bezier_edit_icon->set_disabled(true); + imported_anim_warning->hide(); + bool import_warning_done = false; + bool bezier_done = false; for (int i = 0; i < animation->get_track_count(); i++) { if (animation->track_is_imported(i)) { imported_anim_warning->show(); + import_warning_done = true; + } + if (animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) { + bezier_edit_icon->set_disabled(false); + bezier_done = true; + } + if (import_warning_done && bezier_done) { break; } } @@ -3343,6 +3408,7 @@ void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim) { step->set_read_only(true); snap->set_disabled(true); snap_mode->set_disabled(true); + bezier_edit_icon->set_disabled(true); } } @@ -3350,7 +3416,7 @@ Ref<Animation> AnimationTrackEditor::get_current_animation() const { return animation; } -void AnimationTrackEditor::_root_removed(Node *p_root) { +void AnimationTrackEditor::_root_removed() { root = nullptr; } @@ -3375,7 +3441,7 @@ Node *AnimationTrackEditor::get_root() const { void AnimationTrackEditor::update_keying() { bool keying_enabled = false; - EditorHistory *editor_history = EditorNode::get_singleton()->get_editor_history(); + EditorSelectionHistory *editor_history = EditorNode::get_singleton()->get_editor_selection_history(); if (is_visible_in_tree() && animation.is_valid() && editor_history->get_path_size() > 0) { Object *obj = ObjectDB::get_instance(editor_history->get_path_object(0)); keying_enabled = Object::cast_to<Node>(obj) != nullptr; @@ -3448,31 +3514,75 @@ void AnimationTrackEditor::_timeline_changed(float p_new_pos, bool p_drag, bool } void AnimationTrackEditor::_track_remove_request(int p_track) { - if (animation->track_is_compressed(p_track)) { + _animation_track_remove_request(p_track, animation); +} + +void AnimationTrackEditor::_animation_track_remove_request(int p_track, Ref<Animation> p_from_animation) { + if (p_from_animation->track_is_compressed(p_track)) { 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.")); return; } int idx = p_track; - if (idx >= 0 && idx < animation->get_track_count()) { + if (idx >= 0 && idx < p_from_animation->get_track_count()) { undo_redo->create_action(TTR("Remove Anim Track")); + + // Remove corresponding reset tracks if they are no longer needed. + AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player(); + if (player->has_animation(SceneStringNames::get_singleton()->RESET)) { + Ref<Animation> reset = player->get_animation(SceneStringNames::get_singleton()->RESET); + if (reset != p_from_animation) { + for (int i = 0; i < reset->get_track_count(); i++) { + if (reset->track_get_path(i) == p_from_animation->track_get_path(p_track)) { + // Check if the reset track isn't used by other animations. + bool used = false; + List<StringName> animation_list; + player->get_animation_list(&animation_list); + + for (const StringName &anim_name : animation_list) { + Ref<Animation> anim = player->get_animation(anim_name); + if (anim == p_from_animation || anim == reset) { + continue; + } + + for (int j = 0; j < anim->get_track_count(); j++) { + if (anim->track_get_path(j) == reset->track_get_path(i)) { + used = true; + break; + } + } + + if (used) { + break; + } + } + + if (!used) { + _animation_track_remove_request(i, reset); + } + break; + } + } + } + } + undo_redo->add_do_method(this, "_clear_selection", false); - undo_redo->add_do_method(animation.ptr(), "remove_track", idx); - undo_redo->add_undo_method(animation.ptr(), "add_track", animation->track_get_type(idx), idx); - undo_redo->add_undo_method(animation.ptr(), "track_set_path", idx, animation->track_get_path(idx)); + undo_redo->add_do_method(p_from_animation.ptr(), "remove_track", idx); + undo_redo->add_undo_method(p_from_animation.ptr(), "add_track", p_from_animation->track_get_type(idx), idx); + undo_redo->add_undo_method(p_from_animation.ptr(), "track_set_path", idx, p_from_animation->track_get_path(idx)); // TODO interpolation. - for (int i = 0; i < animation->track_get_key_count(idx); i++) { - Variant v = animation->track_get_key_value(idx, i); - float time = animation->track_get_key_time(idx, i); - float trans = animation->track_get_key_transition(idx, i); + for (int i = 0; i < p_from_animation->track_get_key_count(idx); i++) { + Variant v = p_from_animation->track_get_key_value(idx, i); + float time = p_from_animation->track_get_key_time(idx, i); + float trans = p_from_animation->track_get_key_transition(idx, i); - undo_redo->add_undo_method(animation.ptr(), "track_insert_key", idx, time, v); - undo_redo->add_undo_method(animation.ptr(), "track_set_key_transition", idx, i, trans); + undo_redo->add_undo_method(p_from_animation.ptr(), "track_insert_key", idx, time, v); + undo_redo->add_undo_method(p_from_animation.ptr(), "track_set_key_transition", idx, i, trans); } - undo_redo->add_undo_method(animation.ptr(), "track_set_interpolation_type", idx, animation->track_get_interpolation_type(idx)); - if (animation->track_get_type(idx) == Animation::TYPE_VALUE) { - undo_redo->add_undo_method(animation.ptr(), "value_track_set_update_mode", idx, animation->value_track_get_update_mode(idx)); + undo_redo->add_undo_method(p_from_animation.ptr(), "track_set_interpolation_type", idx, p_from_animation->track_get_interpolation_type(idx)); + if (p_from_animation->track_get_type(idx) == Animation::TYPE_VALUE) { + undo_redo->add_undo_method(p_from_animation.ptr(), "value_track_set_update_mode", idx, p_from_animation->value_track_get_update_mode(idx)); } undo_redo->commit_action(); @@ -3481,7 +3591,7 @@ void AnimationTrackEditor::_track_remove_request(int p_track) { void AnimationTrackEditor::_track_grab_focus(int p_track) { // Don't steal focus if not working with the track editor. - if (Object::cast_to<AnimationTrackEdit>(get_focus_owner())) { + if (Object::cast_to<AnimationTrackEdit>(get_viewport()->gui_get_focus_owner())) { track_edits[p_track]->grab_focus(); } } @@ -3574,7 +3684,7 @@ void AnimationTrackEditor::commit_insert_queue() { } } - if (bool(EDITOR_DEF("editors/animation/confirm_insert_track", true)) && num_tracks > 0) { + if (bool(EDITOR_GET("editors/animation/confirm_insert_track")) && num_tracks > 0) { // Potentially a new key, does not exist. if (num_tracks == 1) { // TRANSLATORS: %s will be replaced by a phrase describing the target of track. @@ -3768,7 +3878,7 @@ void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_p return; } - EditorHistory *history = EditorNode::get_singleton()->get_editor_history(); + EditorSelectionHistory *history = EditorNode::get_singleton()->get_editor_selection_history(); for (int i = 1; i < history->get_path_size(); i++) { String prop = history->get_path_property(i); ERR_FAIL_COND(prop.is_empty()); @@ -3848,7 +3958,7 @@ void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_p } void AnimationTrackEditor::insert_value_key(const String &p_property, const Variant &p_value, bool p_advance) { - EditorHistory *history = EditorNode::get_singleton()->get_editor_history(); + EditorSelectionHistory *history = EditorNode::get_singleton()->get_editor_selection_history(); ERR_FAIL_COND(!root); // Let's build a node path. @@ -3947,12 +4057,20 @@ Ref<Animation> AnimationTrackEditor::_create_and_get_reset_animation() { if (player->has_animation(SceneStringNames::get_singleton()->RESET)) { return player->get_animation(SceneStringNames::get_singleton()->RESET); } else { + Ref<AnimationLibrary> al; + if (!player->has_animation_library("")) { + al.instantiate(); + player->add_animation_library("", al); + } else { + al = player->get_animation_library(""); + } + Ref<Animation> reset_anim; reset_anim.instantiate(); reset_anim->set_length(ANIM_MIN_LENGTH); - undo_redo->add_do_method(player, "add_animation", SceneStringNames::get_singleton()->RESET, reset_anim); + undo_redo->add_do_method(al.ptr(), "add_animation", SceneStringNames::get_singleton()->RESET, reset_anim); undo_redo->add_do_method(AnimationPlayerEditor::get_singleton(), "_animation_player_changed", player); - undo_redo->add_undo_method(player, "remove_animation", SceneStringNames::get_singleton()->RESET); + undo_redo->add_undo_method(al.ptr(), "remove_animation", SceneStringNames::get_singleton()->RESET); undo_redo->add_undo_method(AnimationPlayerEditor::get_singleton(), "_animation_player_changed", player); return reset_anim; } @@ -3991,7 +4109,7 @@ PropertyInfo AnimationTrackEditor::_find_hint_for_track(int p_idx, NodePath &r_b return PropertyInfo(); } - RES res; + Ref<Resource> res; Vector<StringName> leftover_path; Node *node = root->get_node_and_resource(path, res, leftover_path, true); @@ -4167,13 +4285,15 @@ AnimationTrackEditor::TrackIndices AnimationTrackEditor::_confirm_insert(InsertD } break; case Animation::TYPE_BEZIER: { Array array; - array.resize(5); + array.resize(6); array[0] = p_id.value; array[1] = -0.25; array[2] = 0; array[3] = 0.25; array[4] = 0; + array[5] = Animation::HANDLE_MODE_BALANCED; value = array; + bezier_edit_icon->set_disabled(false); } break; case Animation::TYPE_ANIMATION: { @@ -4257,7 +4377,7 @@ void AnimationTrackEditor::_update_tracks() { return; } - Map<String, VBoxContainer *> group_sort; + RBMap<String, VBoxContainer *> group_sort; bool use_grouping = !view_group->is_pressed(); bool use_filter = selected_filter->is_pressed(); @@ -4285,7 +4405,7 @@ void AnimationTrackEditor::_update_tracks() { NodePath path = animation->track_get_path(i); if (root && root->has_node_and_resource(path)) { - RES res; + Ref<Resource> res; NodePath base_path; Vector<StringName> leftover_path; Node *node = root->get_node_and_resource(path, res, leftover_path, true); @@ -4399,7 +4519,6 @@ void AnimationTrackEditor::_update_tracks() { track_edit->connect("insert_key", callable_mp(this, &AnimationTrackEditor::_insert_key_from_track), varray(i), CONNECT_DEFERRED); track_edit->connect("select_key", callable_mp(this, &AnimationTrackEditor::_key_selected), varray(i), CONNECT_DEFERRED); track_edit->connect("deselect_key", callable_mp(this, &AnimationTrackEditor::_key_deselected), varray(i), CONNECT_DEFERRED); - track_edit->connect("bezier_edit", callable_mp(this, &AnimationTrackEditor::_bezier_edit), varray(i), CONNECT_DEFERRED); track_edit->connect("move_selection_begin", callable_mp(this, &AnimationTrackEditor::_move_selection_begin)); track_edit->connect("move_selection", callable_mp(this, &AnimationTrackEditor::_move_selection)); track_edit->connect("move_selection_commit", callable_mp(this, &AnimationTrackEditor::_move_selection_commit)); @@ -4509,26 +4628,33 @@ MenuButton *AnimationTrackEditor::get_edit_menu() { } void AnimationTrackEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE || p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) { - panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/animation_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EditorSettings::get_singleton()->get("editors/panning/simple_panning"))); - } + switch (p_what) { + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/animation_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EditorSettings::get_singleton()->get("editors/panning/simple_panning"))); + } break; - if (p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_ENTER_TREE) { - zoom_icon->set_texture(get_theme_icon(SNAME("Zoom"), SNAME("EditorIcons"))); - snap->set_icon(get_theme_icon(SNAME("Snap"), SNAME("EditorIcons"))); - view_group->set_icon(get_theme_icon(view_group->is_pressed() ? "AnimationTrackList" : "AnimationTrackGroup", "EditorIcons")); - selected_filter->set_icon(get_theme_icon(SNAME("AnimationFilter"), SNAME("EditorIcons"))); - imported_anim_warning->set_icon(get_theme_icon(SNAME("NodeWarning"), SNAME("EditorIcons"))); - main_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); - edit->get_popup()->set_item_icon(edit->get_popup()->get_item_index(EDIT_APPLY_RESET), get_theme_icon(SNAME("Reload"), SNAME("EditorIcons"))); - } + case NOTIFICATION_ENTER_TREE: { + panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/animation_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EditorSettings::get_singleton()->get("editors/panning/simple_panning"))); + [[fallthrough]]; + } + case NOTIFICATION_THEME_CHANGED: { + zoom_icon->set_texture(get_theme_icon(SNAME("Zoom"), SNAME("EditorIcons"))); + bezier_edit_icon->set_icon(get_theme_icon(SNAME("EditBezier"), SNAME("EditorIcons"))); + snap->set_icon(get_theme_icon(SNAME("Snap"), SNAME("EditorIcons"))); + view_group->set_icon(get_theme_icon(view_group->is_pressed() ? SNAME("AnimationTrackList") : SNAME("AnimationTrackGroup"), SNAME("EditorIcons"))); + selected_filter->set_icon(get_theme_icon(SNAME("AnimationFilter"), SNAME("EditorIcons"))); + imported_anim_warning->set_icon(get_theme_icon(SNAME("NodeWarning"), SNAME("EditorIcons"))); + main_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); + edit->get_popup()->set_item_icon(edit->get_popup()->get_item_index(EDIT_APPLY_RESET), get_theme_icon(SNAME("Reload"), SNAME("EditorIcons"))); + } break; - if (p_what == NOTIFICATION_READY) { - EditorNode::get_singleton()->get_editor_selection()->connect("selection_changed", callable_mp(this, &AnimationTrackEditor::_selection_changed)); - } + case NOTIFICATION_READY: { + EditorNode::get_singleton()->get_editor_selection()->connect("selection_changed", callable_mp(this, &AnimationTrackEditor::_selection_changed)); + } break; - if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { - update_keying(); + case NOTIFICATION_VISIBILITY_CHANGED: { + update_keying(); + } break; } } @@ -4630,6 +4756,7 @@ void AnimationTrackEditor::_new_track_node_selected(NodePath p_path) { adding_track_path = path_to; prop_selector->set_type_filter(filter); prop_selector->select_property_from_instance(node); + bezier_edit_icon->set_disabled(false); } break; case Animation::TYPE_AUDIO: { if (!node->is_class("AudioStreamPlayer") && !node->is_class("AudioStreamPlayer2D") && !node->is_class("AudioStreamPlayer3D")) { @@ -4884,7 +5011,7 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) { } break; case Animation::TYPE_AUDIO: { Dictionary ak; - ak["stream"] = RES(); + ak["stream"] = Ref<Resource>(); ak["start_offset"] = 0; ak["end_offset"] = 0; @@ -4943,7 +5070,7 @@ void AnimationTrackEditor::_add_method_key(const String &p_method) { } } - EditorNode::get_singleton()->show_warning(TTR("Method not found in object: ") + p_method); + EditorNode::get_singleton()->show_warning(TTR("Method not found in object:") + " " + p_method); } void AnimationTrackEditor::_key_selected(int p_key, bool p_single, int p_track) { @@ -5067,8 +5194,8 @@ void AnimationTrackEditor::_update_key_edit() { multi_key_edit = memnew(AnimationMultiTrackKeyEdit); multi_key_edit->animation = animation; - Map<int, List<float>> key_ofs_map; - Map<int, NodePath> base_map; + RBMap<int, List<float>> key_ofs_map; + RBMap<int, NodePath> base_map; int first_track = -1; for (const KeyValue<SelectedKey, KeyInfo> &E : selection) { int track = E.key.track; @@ -5129,11 +5256,11 @@ void AnimationTrackEditor::_move_selection_commit() { float motion = moving_selection_offset; // 1 - remove the keys. - for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { + for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { undo_redo->add_do_method(animation.ptr(), "track_remove_key", E->key().track, E->key().key); } // 2 - Remove overlapped keys. - for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { + for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { float newtime = snap_time(E->get().pos + motion); int idx = animation->track_find_key(E->key().track, newtime, true); if (idx == -1) { @@ -5158,19 +5285,19 @@ void AnimationTrackEditor::_move_selection_commit() { } // 3 - Move the keys (Reinsert them). - for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { + for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { float newpos = snap_time(E->get().pos + motion); 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)); } // 4 - (Undo) Remove inserted keys. - for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { + for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { float newpos = snap_time(E->get().pos + motion); undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", E->key().track, newpos); } // 5 - (Undo) Reinsert keys. - for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { + for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { 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)); } @@ -5183,7 +5310,7 @@ void AnimationTrackEditor::_move_selection_commit() { undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation); // 7 - Reselect. - for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { + for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { float oldpos = E->get().pos; float newpos = snap_time(oldpos + motion); @@ -5294,6 +5421,20 @@ void AnimationTrackEditor::_scroll_input(const Ref<InputEvent> &p_event) { } } +void AnimationTrackEditor::_toggle_bezier_edit() { + if (bezier_edit->is_visible()) { + _cancel_bezier_edit(); + } else { + int track_count = animation->get_track_count(); + for (int i = 0; i < track_count; ++i) { + if (animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) { + _bezier_edit(i); + return; + } + } + } +} + void AnimationTrackEditor::_scroll_callback(Vector2 p_scroll_vec, bool p_alt) { if (p_alt) { if (p_scroll_vec.x < 0 || p_scroll_vec.y < 0) { @@ -5312,16 +5453,20 @@ void AnimationTrackEditor::_pan_callback(Vector2 p_scroll_vec) { } void AnimationTrackEditor::_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt) { - if (p_scroll_vec.y < 0) { - timeline->get_zoom()->set_value(timeline->get_zoom()->get_value() * 1.05); + double new_zoom_value; + double current_zoom_value = timeline->get_zoom()->get_value(); + if (current_zoom_value <= 0.1) { + new_zoom_value = MAX(0.01, current_zoom_value - 0.01 * SIGN(p_scroll_vec.y)); } else { - timeline->get_zoom()->set_value(timeline->get_zoom()->get_value() / 1.05); + new_zoom_value = p_scroll_vec.y > 0 ? MAX(0.01, current_zoom_value / 1.05) : current_zoom_value * 1.05; } + timeline->get_zoom()->set_value(new_zoom_value); } void AnimationTrackEditor::_cancel_bezier_edit() { bezier_edit->hide(); scroll->show(); + bezier_edit_icon->set_pressed(false); } void AnimationTrackEditor::_bezier_edit(int p_for_track) { @@ -5338,7 +5483,7 @@ void AnimationTrackEditor::_anim_duplicate_keys(bool transpose) { if (selection.size() && animation.is_valid() && (!transpose || (_get_track_selected() >= 0 && _get_track_selected() < animation->get_track_count()))) { int top_track = 0x7FFFFFFF; float top_time = 1e10; - for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { + for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { const SelectedKey &sk = E->key(); float t = animation->track_get_key_time(sk.track, sk.key); @@ -5359,7 +5504,7 @@ void AnimationTrackEditor::_anim_duplicate_keys(bool transpose) { List<Pair<int, float>> new_selection_values; - for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { + for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { const SelectedKey &sk = E->key(); float t = animation->track_get_key_time(sk.track, sk.key); @@ -5394,7 +5539,7 @@ void AnimationTrackEditor::_anim_duplicate_keys(bool transpose) { // Reselect duplicated. - Map<SelectedKey, KeyInfo> new_selection; + RBMap<SelectedKey, KeyInfo> new_selection; for (const Pair<int, float> &E : new_selection_values) { int track = E.first; float time = E.second; @@ -5493,8 +5638,8 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { String text; Ref<Texture2D> icon = get_theme_icon(SNAME("Node"), SNAME("EditorIcons")); if (node) { - if (has_theme_icon(node->get_class(), "EditorIcons")) { - icon = get_theme_icon(node->get_class(), "EditorIcons"); + if (has_theme_icon(node->get_class(), SNAME("EditorIcons"))) { + icon = get_theme_icon(node->get_class(), SNAME("EditorIcons")); } text = node->get_name(); @@ -5513,31 +5658,35 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { } } + String track_type; switch (animation->track_get_type(i)) { case Animation::TYPE_POSITION_3D: - text += " (Position)"; + track_type = TTR("Position"); break; case Animation::TYPE_ROTATION_3D: - text += " (Rotation)"; + track_type = TTR("Rotation"); break; case Animation::TYPE_SCALE_3D: - text += " (Scale)"; + track_type = TTR("Scale"); break; case Animation::TYPE_BLEND_SHAPE: - text += " (BlendShape)"; + track_type = TTR("BlendShape"); break; case Animation::TYPE_METHOD: - text += " (Methods)"; + track_type = TTR("Methods"); break; case Animation::TYPE_BEZIER: - text += " (Bezier)"; + track_type = TTR("Bezier"); break; case Animation::TYPE_AUDIO: - text += " (Audio)"; + track_type = TTR("Audio"); break; default: { }; } + if (!track_type.is_empty()) { + text += vformat(" (%s)", track_type); + } TreeItem *it = track_copy_select->create_item(troot); it->set_editable(0, true); @@ -5668,11 +5817,11 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { List<_AnimMoveRestore> to_restore; // 1 - Remove the keys. - for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { + for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { undo_redo->add_do_method(animation.ptr(), "track_remove_key", E->key().track, E->key().key); } // 2 - Remove overlapped keys. - for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { + for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { float newtime = (E->get().pos - from_t) * s + from_t; int idx = animation->track_find_key(E->key().track, newtime, true); if (idx == -1) { @@ -5698,19 +5847,19 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { #define NEW_POS(m_ofs) (((s > 0) ? m_ofs : from_t + (len - (m_ofs - from_t))) - pivot) * ABS(s) + from_t // 3 - Move the keys (re insert them). - for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { + for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { float newpos = NEW_POS(E->get().pos); 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)); } // 4 - (Undo) Remove inserted keys. - for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { + for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { float newpos = NEW_POS(E->get().pos); undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", E->key().track, newpos); } // 5 - (Undo) Reinsert keys. - for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { + for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { 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)); } @@ -5723,7 +5872,7 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation); // 7-reselect. - for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { + for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { float oldpos = E->get().pos; float newpos = NEW_POS(oldpos); if (newpos >= 0) { @@ -5752,7 +5901,7 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { undo_redo->create_action(TTR("Anim Add RESET Keys")); Ref<Animation> reset = _create_and_get_reset_animation(); int reset_tracks = reset->get_track_count(); - Set<int> tracks_added; + HashSet<int> tracks_added; for (const KeyValue<SelectedKey, KeyInfo> &E : selection) { const SelectedKey &sk = E.key; @@ -5806,7 +5955,7 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { if (selection.size()) { undo_redo->create_action(TTR("Anim Delete Keys")); - for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { + for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { undo_redo->add_do_method(animation.ptr(), "track_remove_key", E->key().track, E->key().key); 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)); } @@ -5861,7 +6010,7 @@ void AnimationTrackEditor::_cleanup_animation(Ref<Animation> p_animation) { Variant::Type valid_type = Variant::NIL; Object *obj = nullptr; - RES res; + Ref<Resource> res; Vector<StringName> leftover_path; Node *node = root->get_node_and_resource(p_animation->track_get_path(i), res, leftover_path); @@ -5907,7 +6056,8 @@ void AnimationTrackEditor::_cleanup_animation(Ref<Animation> p_animation) { void AnimationTrackEditor::_view_group_toggle() { _update_tracks(); - view_group->set_icon(get_theme_icon(view_group->is_pressed() ? "AnimationTrackList" : "AnimationTrackGroup", "EditorIcons")); + view_group->set_icon(get_theme_icon(view_group->is_pressed() ? SNAME("AnimationTrackList") : SNAME("AnimationTrackGroup"), SNAME("EditorIcons"))); + bezier_edit->set_filtered(selected_filter->is_pressed()); } bool AnimationTrackEditor::is_grouping_tracks() { @@ -6069,8 +6219,6 @@ void AnimationTrackEditor::_pick_track_filter_input(const Ref<InputEvent> &p_ie) } AnimationTrackEditor::AnimationTrackEditor() { - root = nullptr; - undo_redo = EditorNode::get_singleton()->get_undo_redo(); main_panel = memnew(PanelContainer); @@ -6153,6 +6301,15 @@ AnimationTrackEditor::AnimationTrackEditor() { bottom_hb->add_spacer(); + bezier_edit_icon = memnew(Button); + bezier_edit_icon->set_flat(true); + bezier_edit_icon->set_disabled(true); + bezier_edit_icon->set_toggle_mode(true); + bezier_edit_icon->connect("pressed", callable_mp(this, &AnimationTrackEditor::_toggle_bezier_edit)); + bezier_edit_icon->set_tooltip(TTR("Toggle between the bezier curve editor and track editor.")); + + bottom_hb->add_child(bezier_edit_icon); + selected_filter = memnew(Button); selected_filter->set_flat(true); selected_filter->connect("pressed", callable_mp(this, &AnimationTrackEditor::_view_group_toggle)); // Same function works the same. @@ -6257,8 +6414,6 @@ AnimationTrackEditor::AnimationTrackEditor() { add_child(method_selector); method_selector->connect("selected", callable_mp(this, &AnimationTrackEditor::_add_method_key)); - insert_queue = false; - insert_confirm = memnew(ConfirmationDialog); add_child(insert_confirm); insert_confirm->connect("confirmed", callable_mp(this, &AnimationTrackEditor::_confirm_insert_list)); @@ -6276,10 +6431,6 @@ AnimationTrackEditor::AnimationTrackEditor() { insert_confirm_reset->set_text(TTR("Create RESET Track(s)", "")); insert_confirm_reset->set_pressed(EDITOR_GET("editors/animation/default_create_reset_tracks")); ichb->add_child(insert_confirm_reset); - keying = false; - moving_selection = false; - key_edit = nullptr; - multi_key_edit = nullptr; box_selection = memnew(Control); add_child(box_selection); @@ -6287,7 +6438,6 @@ AnimationTrackEditor::AnimationTrackEditor() { box_selection->set_mouse_filter(MOUSE_FILTER_IGNORE); box_selection->hide(); box_selection->connect("draw", callable_mp(this, &AnimationTrackEditor::_box_selection_draw)); - box_selecting = false; // Default Plugins. @@ -6384,7 +6534,6 @@ AnimationTrackEditor::AnimationTrackEditor() { track_copy_select->set_hide_root(true); track_vbox->add_child(track_copy_select); track_copy_dialog->connect("confirmed", callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed), varray(EDIT_COPY_TRACKS_CONFIRM)); - animation_changing_awaiting_update = false; } AnimationTrackEditor::~AnimationTrackEditor() { |