summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--editor/animation_bezier_editor.cpp1183
-rw-r--r--editor/animation_bezier_editor.h141
-rw-r--r--editor/animation_editor.cpp4146
-rw-r--r--editor/animation_editor.h348
-rw-r--r--editor/animation_track_editor.cpp5001
-rw-r--r--editor/animation_track_editor.h483
-rw-r--r--editor/animation_track_editor_plugins.cpp1289
-rw-r--r--editor/animation_track_editor_plugins.h139
-rw-r--r--editor/audio_stream_preview.cpp211
-rw-r--r--editor/audio_stream_preview.h56
-rw-r--r--editor/editor_inspector.cpp3
-rw-r--r--editor/editor_node.cpp7
-rw-r--r--editor/editor_node.h3
-rw-r--r--editor/editor_spin_slider.cpp18
-rw-r--r--editor/editor_spin_slider.h5
-rw-r--r--editor/icons/icon_animation_filter.svg63
-rw-r--r--editor/icons/icon_animation_track_group.svg63
-rw-r--r--editor/icons/icon_animation_track_list.svg60
-rw-r--r--editor/icons/icon_bezier_handles_balanced.svg98
-rw-r--r--editor/icons/icon_bezier_handles_free.svg98
-rw-r--r--editor/icons/icon_bezier_handles_mirror.svg98
-rw-r--r--editor/icons/icon_color_track_vu.svg115
-rw-r--r--editor/icons/icon_edit_bezier.svg73
-rw-r--r--editor/icons/icon_key_animation.svg65
-rw-r--r--editor/icons/icon_key_audio.svg65
-rw-r--r--editor/icons/icon_key_bezier.svg65
-rw-r--r--editor/icons/icon_key_bezier_handle.svg60
-rw-r--r--editor/icons/icon_key_bezier_point.svg64
-rw-r--r--editor/icons/icon_key_bezier_selected.svg64
-rw-r--r--editor/icons/icon_key_call.svg67
-rw-r--r--editor/icons/icon_key_selected.svg79
-rw-r--r--editor/icons/icon_key_xform.svg67
-rw-r--r--editor/icons/icon_time.svg74
-rw-r--r--editor/icons/icon_track_capture.svg61
-rw-r--r--editor/inspector_dock.cpp10
-rw-r--r--editor/inspector_dock.h3
-rw-r--r--editor/plugins/animation_player_editor_plugin.cpp253
-rw-r--r--editor/plugins/animation_player_editor_plugin.h32
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp25
-rw-r--r--editor/plugins/editor_preview_plugins.cpp288
-rw-r--r--editor/plugins/editor_preview_plugins.h12
-rw-r--r--editor/plugins/spatial_editor_plugin.cpp4
-rw-r--r--editor/property_selector.cpp62
-rw-r--r--editor/property_selector.h4
-rw-r--r--editor/scene_tree_dock.cpp14
-rw-r--r--editor/scene_tree_editor.cpp14
-rw-r--r--editor/scene_tree_editor.h1
-rw-r--r--platform/android/AndroidManifest.xml.template218
-rw-r--r--scene/animation/animation_player.cpp293
-rw-r--r--scene/animation/animation_player.h38
-rw-r--r--scene/gui/popup_menu.cpp25
-rw-r--r--scene/gui/popup_menu.h4
-rw-r--r--scene/gui/scroll_container.cpp17
-rw-r--r--scene/gui/scroll_container.h3
-rw-r--r--scene/main/viewport.cpp52
-rw-r--r--scene/main/viewport.h1
-rw-r--r--scene/resources/animation.cpp1040
-rw-r--r--scene/resources/animation.h83
-rw-r--r--servers/audio/audio_stream.cpp1
-rw-r--r--servers/audio/audio_stream.h1
60 files changed, 11749 insertions, 5181 deletions
diff --git a/editor/animation_bezier_editor.cpp b/editor/animation_bezier_editor.cpp
new file mode 100644
index 0000000000..197599442b
--- /dev/null
+++ b/editor/animation_bezier_editor.cpp
@@ -0,0 +1,1183 @@
+#include "animation_bezier_editor.h"
+
+float AnimationBezierTrackEdit::_bezier_h_to_pixel(float p_h) {
+ float h = p_h;
+ h = (h - v_scroll) / v_zoom;
+ h = (get_size().height / 2) - h;
+ return h;
+}
+
+static _FORCE_INLINE_ Vector2 _bezier_interp(real_t t, const Vector2 &start, const Vector2 &control_1, const Vector2 &control_2, const Vector2 &end) {
+ /* Formula from Wikipedia article on Bezier curves. */
+ real_t omt = (1.0 - t);
+ real_t omt2 = omt * omt;
+ real_t omt3 = omt2 * omt;
+ real_t t2 = t * t;
+ real_t t3 = t2 * t;
+
+ return start * omt3 + control_1 * omt2 * t * 3.0 + control_2 * omt * t2 * 3.0 + end * t3;
+}
+
+void AnimationBezierTrackEdit::_draw_track(int p_track, const Color &p_color) {
+
+ float scale = timeline->get_zoom_scale();
+ int limit = timeline->get_name_limit();
+ int right_limit = get_size().width - timeline->get_buttons_width();
+
+ //selection may have altered the order of keys
+ Map<float, int> key_order;
+
+ for (int i = 0; i < animation->track_get_key_count(p_track); i++) {
+ float ofs = animation->track_get_key_time(p_track, i);
+ if (moving_selection && track == p_track && selection.has(i)) {
+ ofs += moving_selection_offset.x;
+ }
+
+ key_order[ofs] = i;
+ }
+
+ for (Map<float, int>::Element *E = key_order.front(); E; E = E->next()) {
+
+ int i = E->get();
+
+ if (!E->next())
+ break;
+
+ int i_n = E->next()->get();
+
+ float offset = animation->track_get_key_time(p_track, i);
+ float height = animation->bezier_track_get_key_value(p_track, i);
+ Vector2 out_handle = animation->bezier_track_get_key_out_handle(p_track, i);
+ if (track == p_track && moving_handle != 0 && moving_handle_key == i) {
+ out_handle = moving_handle_right;
+ }
+
+ if (moving_selection && track == p_track && selection.has(i)) {
+ offset += moving_selection_offset.x;
+ height += moving_selection_offset.y;
+ }
+
+ out_handle += Vector2(offset, height);
+
+ float offset_n = animation->track_get_key_time(p_track, i_n);
+ float height_n = animation->bezier_track_get_key_value(p_track, i_n);
+ Vector2 in_handle = animation->bezier_track_get_key_in_handle(p_track, i_n);
+ if (track == p_track && moving_handle != 0 && moving_handle_key == i_n) {
+ in_handle = moving_handle_left;
+ }
+
+ if (moving_selection && track == p_track && selection.has(i_n)) {
+ offset_n += moving_selection_offset.x;
+ height_n += moving_selection_offset.y;
+ }
+
+ in_handle += Vector2(offset_n, height_n);
+
+ Vector2 start(offset, height);
+ Vector2 end(offset_n, height_n);
+
+ int from_x = (offset - timeline->get_value()) * scale + limit;
+ int point_start = from_x;
+ int to_x = (offset_n - timeline->get_value()) * scale + limit;
+ int point_end = to_x;
+
+ if (from_x > right_limit) //not visible
+ continue;
+
+ if (to_x < limit) //not visible
+ continue;
+
+ from_x = MAX(from_x, limit);
+ to_x = MIN(to_x, right_limit);
+
+ Vector<Vector2> lines;
+
+ Vector2 prev_pos;
+
+ for (int j = from_x; j <= to_x; j++) {
+
+ float t = (j - limit) / scale + timeline->get_value();
+
+ float h;
+
+ if (j == point_end) {
+ h = end.y; //make sure it always connects
+ } else if (j == point_start) {
+ h = start.y; //make sure it always connects
+ } else { //custom interpolation, used because it needs to show paths affected by moving the selection or handles
+ int iterations = 10;
+ float low = 0;
+ float high = 1;
+ float middle;
+
+ //narrow high and low as much as possible
+ for (int k = 0; k < iterations; k++) {
+
+ middle = (low + high) / 2;
+
+ Vector2 interp = _bezier_interp(middle, start, out_handle, in_handle, end);
+
+ if (interp.x < t) {
+ low = middle;
+ } else {
+ high = middle;
+ }
+ }
+
+ //interpolate the result:
+ Vector2 low_pos = _bezier_interp(low, start, out_handle, in_handle, end);
+ Vector2 high_pos = _bezier_interp(high, start, out_handle, in_handle, end);
+
+ float c = (t - low_pos.x) / (high_pos.x - low_pos.x);
+
+ h = low_pos.linear_interpolate(high_pos, c).y;
+ }
+
+ h = _bezier_h_to_pixel(h);
+
+ Vector2 pos(j, h);
+
+ if (j > from_x) {
+ lines.push_back(prev_pos);
+ lines.push_back(pos);
+ }
+ prev_pos = pos;
+ }
+
+ if (lines.size() >= 2) {
+ draw_multiline(lines, p_color);
+ }
+ }
+}
+
+void AnimationBezierTrackEdit::_draw_line_clipped(const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, int p_clip_left, int p_clip_right) {
+
+ Vector2 from = p_from;
+ Vector2 to = p_to;
+
+ if (from.x == to.x)
+ return;
+ if (to.x < from.x) {
+ SWAP(to, from);
+ }
+
+ if (to.x < p_clip_left)
+ return;
+
+ if (from.x > p_clip_right)
+ return;
+
+ if (to.x > p_clip_right) {
+ float c = (p_clip_right - from.x) / (to.x - from.x);
+ to = from.linear_interpolate(to, c);
+ }
+
+ if (from.x < p_clip_left) {
+ float c = (p_clip_left - from.x) / (to.x - from.x);
+ from = from.linear_interpolate(to, c);
+ }
+
+ draw_line(from, to, p_color);
+}
+
+void AnimationBezierTrackEdit::_notification(int p_what) {
+
+ if (p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_ENTER_TREE) {
+ bezier_icon = get_icon("KeyBezierPoint", "EditorIcons");
+ bezier_handle_icon = get_icon("KeyBezierHandle", "EditorIcons");
+ selected_icon = get_icon("KeyBezierSelected", "EditorIcons");
+ if (handle_mode_option->get_item_count() == 0) {
+ handle_mode_option->add_icon_item(get_icon("BezierHandlesFree", "EditorIcons"), TTR("Free"), HANDLE_MODE_FREE);
+ handle_mode_option->add_icon_item(get_icon("BezierHandlesBalanced", "EditorIcons"), TTR("Balanced"), HANDLE_MODE_BALANCED);
+ handle_mode_option->add_icon_item(get_icon("BezierHandlesMirror", "EditorIcons"), TTR("Mirror"), HANDLE_MODE_MIRROR);
+ }
+ }
+ if (p_what == NOTIFICATION_RESIZED) {
+
+ int right_limit = get_size().width - timeline->get_buttons_width();
+ int hsep = get_constant("hseparation", "ItemList");
+ int vsep = get_constant("vseparation", "ItemList");
+
+ handle_mode_option->set_position(Vector2(right_limit + hsep, get_size().height - handle_mode_option->get_combined_minimum_size().height - vsep));
+ handle_mode_option->set_size(Vector2(timeline->get_buttons_width() - hsep * 2, handle_mode_option->get_combined_minimum_size().height));
+ }
+ if (p_what == NOTIFICATION_DRAW) {
+ if (animation.is_null())
+ return;
+
+ int limit = timeline->get_name_limit();
+
+ if (has_focus()) {
+ Color accent = get_color("accent_color", "Editor");
+ accent.a *= 0.7;
+ draw_rect(Rect2(Point2(), get_size()), accent, false);
+ }
+
+ Ref<Font> font = get_font("font", "Label");
+ Color color = get_color("font_color", "Label");
+ int hsep = get_constant("hseparation", "ItemList");
+ int vsep = get_constant("vseparation", "ItemList");
+ Color linecolor = color;
+ linecolor.a = 0.2;
+
+ draw_line(Point2(limit, 0), Point2(limit, get_size().height), linecolor);
+
+ int right_limit = get_size().width - timeline->get_buttons_width();
+
+ draw_line(Point2(right_limit, 0), Point2(right_limit, get_size().height), linecolor);
+
+ Ref<Texture> close_icon = get_icon("Close", "EditorIcons");
+
+ close_icon_rect.position = Vector2(get_size().width - close_icon->get_width() - hsep, hsep);
+ close_icon_rect.size = close_icon->get_size();
+ draw_texture(close_icon, close_icon_rect.position);
+
+ String base_path = animation->track_get_path(track);
+ int end = base_path.find(":");
+ if (end != -1) {
+ base_path = base_path.substr(0, end + 1);
+ }
+
+ // NAMES AND ICON
+ int vofs = vsep;
+ int margin = 0;
+
+ {
+ int ofs = 0;
+
+ NodePath path = animation->track_get_path(track);
+
+ Node *node = NULL;
+
+ if (root && root->has_node(path)) {
+ node = root->get_node(path);
+ }
+
+ String text;
+
+ int h = font->get_height();
+
+ if (node) {
+ Ref<Texture> icon;
+ if (has_icon(node->get_class(), "EditorIcons")) {
+ icon = get_icon(node->get_class(), "EditorIcons");
+ } else {
+ icon = get_icon("Node", "EditorIcons");
+ }
+
+ h = MAX(h, icon->get_height());
+
+ draw_texture(icon, Point2(ofs, vofs + int(h - icon->get_height()) / 2));
+
+ margin = icon->get_width();
+
+ text = node->get_name();
+ ofs += hsep;
+ ofs += icon->get_width();
+
+ Vector2 string_pos = Point2(ofs, vofs + (h - font->get_height()) / 2 + font->get_ascent());
+ string_pos = string_pos.floor();
+ draw_string(font, string_pos, text, color, limit - ofs - hsep);
+
+ vofs += h + vsep;
+ }
+ }
+
+ // RELATED TRACKS TITLES
+
+ Map<int, Color> subtrack_colors;
+ subtracks.clear();
+
+ for (int i = 0; i < animation->get_track_count(); i++) {
+ if (animation->track_get_type(i) != Animation::TYPE_BEZIER)
+ continue;
+ String path = animation->track_get_path(i);
+ if (!path.begins_with(base_path))
+ continue; //another node
+ path = path.replace_first(base_path, "");
+
+ Color cc = color;
+ Rect2 rect = Rect2(margin, vofs, limit - margin - hsep, font->get_height() + vsep);
+ if (i != track) {
+ cc.a *= 0.7;
+ uint32_t hash = path.hash();
+ hash = ((hash >> 16) ^ hash) * 0x45d9f3b;
+ hash = ((hash >> 16) ^ hash) * 0x45d9f3b;
+ hash = (hash >> 16) ^ hash;
+ float h = (hash % 65535) / 65536.0;
+ Color subcolor;
+ subcolor.set_hsv(h, 0.2, 0.8);
+ subcolor.a = 0.5;
+ draw_rect(Rect2(0, vofs + font->get_height() * 0.1, margin - hsep, font->get_height() * 0.8), subcolor);
+ subtrack_colors[i] = subcolor;
+
+ subtracks[i] = rect;
+ } else {
+ Color ac = get_color("accent_color", "Editor");
+ ac.a = 0.5;
+ draw_rect(rect, ac);
+ }
+ draw_string(font, Point2(margin, vofs + font->get_ascent()), path, cc, limit - margin - hsep);
+
+ vofs += font->get_height() + vsep;
+ }
+
+ Color accent = get_color("accent_color", "Editor");
+
+ { //guides
+ float min_left_scale = font->get_height() + vsep;
+
+ float scale = 1;
+
+ while (scale / v_zoom < min_left_scale * 2) {
+ scale *= 5;
+ }
+
+ bool first = true;
+ int prev_iv = 0;
+ for (int i = font->get_height(); i < get_size().height; i++) {
+
+ float ofs = get_size().height / 2 - i;
+ ofs *= v_zoom;
+ ofs += v_scroll;
+
+ int iv = int(ofs / scale);
+ if (ofs < 0)
+ iv -= 1;
+ if (!first && iv != prev_iv) {
+
+ Color lc = linecolor;
+ lc.a *= 0.5;
+ draw_line(Point2(limit, i), Point2(right_limit, i), lc);
+ Color c = color;
+ c.a *= 0.5;
+ draw_string(font, Point2(limit + 8, i - 2), itos((iv + 1) * scale), c);
+ }
+
+ first = false;
+ prev_iv = iv;
+ }
+ }
+
+ { //draw OTHER curves
+
+ float scale = timeline->get_zoom_scale();
+ Ref<Texture> point = get_icon("KeyValue", "EditorIcons");
+ for (Map<int, Color>::Element *E = subtrack_colors.front(); E; E = E->next()) {
+
+ _draw_track(E->key(), E->get());
+
+ for (int i = 0; i < animation->track_get_key_count(E->key()); i++) {
+
+ float offset = animation->track_get_key_time(E->key(), i);
+ float value = animation->bezier_track_get_key_value(E->key(), i);
+
+ Vector2 pos((offset - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value));
+
+ if (pos.x >= limit && pos.x <= right_limit) {
+ draw_texture(point, pos - point->get_size() / 2, E->get());
+ }
+ }
+ }
+
+ //draw edited curve
+ _draw_track(track, accent);
+ }
+
+ //draw editor handles
+ {
+
+ float scale = timeline->get_zoom_scale();
+ edit_points.clear();
+
+ for (int i = 0; i < animation->track_get_key_count(track); i++) {
+
+ float offset = animation->track_get_key_time(track, i);
+ float value = animation->bezier_track_get_key_value(track, i);
+
+ if (moving_selection && selection.has(i)) {
+ offset += moving_selection_offset.x;
+ value += moving_selection_offset.y;
+ }
+
+ Vector2 pos((offset - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value));
+
+ Vector2 in_vec = animation->bezier_track_get_key_in_handle(track, i);
+ if (moving_handle != 0 && moving_handle_key == i) {
+ in_vec = moving_handle_left;
+ }
+ Vector2 pos_in = Vector2(((offset + in_vec.x) - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value + in_vec.y));
+
+ Vector2 out_vec = animation->bezier_track_get_key_out_handle(track, i);
+
+ if (moving_handle != 0 && moving_handle_key == i) {
+ out_vec = moving_handle_right;
+ }
+
+ Vector2 pos_out = Vector2(((offset + out_vec.x) - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value + out_vec.y));
+
+ _draw_line_clipped(pos, pos_in, accent, limit, right_limit);
+ _draw_line_clipped(pos, pos_out, accent, limit, right_limit);
+
+ EditPoint ep;
+ if (pos.x >= limit && pos.x <= right_limit) {
+ ep.point_rect.position = (pos - bezier_icon->get_size() / 2).floor();
+ ep.point_rect.size = bezier_icon->get_size();
+ if (selection.has(i)) {
+ draw_texture(selected_icon, ep.point_rect.position);
+ } else {
+ draw_texture(bezier_icon, ep.point_rect.position);
+ }
+ ep.point_rect = ep.point_rect.grow(ep.point_rect.size.width * 0.5);
+ }
+ if (pos_in.x >= limit && pos_in.x <= right_limit) {
+ ep.in_rect.position = (pos_in - bezier_handle_icon->get_size() / 2).floor();
+ ep.in_rect.size = bezier_handle_icon->get_size();
+ draw_texture(bezier_handle_icon, ep.in_rect.position);
+ ep.in_rect = ep.in_rect.grow(ep.in_rect.size.width * 0.5);
+ }
+ if (pos_out.x >= limit && pos_out.x <= right_limit) {
+ ep.out_rect.position = (pos_out - bezier_handle_icon->get_size() / 2).floor();
+ ep.out_rect.size = bezier_handle_icon->get_size();
+ draw_texture(bezier_handle_icon, ep.out_rect.position);
+ ep.out_rect = ep.out_rect.grow(ep.out_rect.size.width * 0.5);
+ }
+ edit_points.push_back(ep);
+ }
+ }
+
+ if (box_selecting) {
+ Color bs = accent;
+ bs.a *= 0.5;
+ Vector2 bs_from = box_selection_from;
+ Vector2 bs_to = box_selection_to;
+ if (bs_from.x > bs_to.x) {
+ SWAP(bs_from.x, bs_to.x);
+ }
+ if (bs_from.y > bs_to.y) {
+ SWAP(bs_from.y, bs_to.y);
+ }
+ draw_rect(Rect2(bs_from, bs_to - bs_from), bs);
+ }
+
+#if 0
+ // KEYFAMES //
+
+ {
+
+ 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->get_moving_selection_offset();
+ }
+ offset = offset * scale + limit;
+ draw_key(i, scale, int(offset), editor->is_key_selected(track, i), limit, limit_end);
+ }
+ }
+#endif
+ }
+}
+
+Ref<Animation> AnimationBezierTrackEdit::get_animation() const {
+ return animation;
+}
+
+void AnimationBezierTrackEdit::set_animation_and_track(const Ref<Animation> &p_animation, int p_track) {
+
+ animation = p_animation;
+ track = p_track;
+ update();
+}
+
+Size2 AnimationBezierTrackEdit::get_minimum_size() const {
+
+ return Vector2(1, 1);
+}
+
+void AnimationBezierTrackEdit::set_undo_redo(UndoRedo *p_undo_redo) {
+ undo_redo = p_undo_redo;
+}
+
+void AnimationBezierTrackEdit::set_timeline(AnimationTimelineEdit *p_timeline) {
+ timeline = p_timeline;
+ timeline->connect("zoom_changed", this, "_zoom_changed");
+}
+void AnimationBezierTrackEdit::set_editor(AnimationTrackEditor *p_editor) {
+ editor = p_editor;
+}
+
+void AnimationBezierTrackEdit::_play_position_draw() {
+
+ if (!animation.is_valid() || play_position_pos < 0)
+ return;
+
+ float scale = timeline->get_zoom_scale();
+ int h = get_size().height;
+
+ int px = (-timeline->get_value() + play_position_pos) * scale + timeline->get_name_limit();
+
+ if (px >= timeline->get_name_limit() && px < (get_size().width - timeline->get_buttons_width())) {
+ Color color = get_color("accent_color", "Editor");
+ play_position->draw_line(Point2(px, 0), Point2(px, h), color);
+ }
+}
+
+void AnimationBezierTrackEdit::set_play_position(float p_pos) {
+
+ play_position_pos = p_pos;
+ play_position->update();
+}
+
+void AnimationBezierTrackEdit::update_play_position() {
+ play_position->update();
+}
+
+void AnimationBezierTrackEdit::set_root(Node *p_root) {
+ root = p_root;
+}
+void AnimationBezierTrackEdit::_zoom_changed() {
+ update();
+}
+
+String AnimationBezierTrackEdit::get_tooltip(const Point2 &p_pos) const {
+
+ return Control::get_tooltip(p_pos);
+}
+
+void AnimationBezierTrackEdit::_clear_selection() {
+ selection.clear();
+ update();
+}
+
+void AnimationBezierTrackEdit::_clear_selection_for_anim(const Ref<Animation> &p_anim) {
+
+ if (!(animation == p_anim))
+ return;
+ //selection.clear();
+ _clear_selection();
+}
+
+void AnimationBezierTrackEdit::_select_at_anim(const Ref<Animation> &p_anim, int p_track, float p_pos) {
+
+ if (!(animation == p_anim))
+ return;
+
+ int idx = animation->track_find_key(p_track, p_pos, true);
+ ERR_FAIL_COND(idx < 0);
+
+ selection.insert(idx);
+ update();
+}
+
+void AnimationBezierTrackEdit::_gui_input(const Ref<InputEvent> &p_event) {
+
+ if (p_event->is_pressed()) {
+ if (ED_GET_SHORTCUT("animation_editor/duplicate_selection")->is_shortcut(p_event)) {
+ duplicate_selection();
+ accept_event();
+ }
+
+ if (ED_GET_SHORTCUT("animation_editor/delete_selection")->is_shortcut(p_event)) {
+ delete_selection();
+ accept_event();
+ }
+ }
+
+ Ref<InputEventMouseButton> mb = p_event;
+ if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == BUTTON_WHEEL_DOWN) {
+ if (mb->get_command()) {
+ timeline->get_zoom()->set_value(timeline->get_zoom()->get_value() * 1.05);
+ } else {
+ if (v_zoom < 1000) {
+ v_zoom *= 1.2;
+ }
+ }
+ update();
+ }
+
+ if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == BUTTON_WHEEL_UP) {
+ if (mb->get_command()) {
+ timeline->get_zoom()->set_value(timeline->get_zoom()->get_value() / 1.05);
+ } else {
+ if (v_zoom > 0.01) {
+ v_zoom /= 1.2;
+ }
+ }
+ update();
+ }
+
+ if (mb.is_valid() && mb->get_button_index() == BUTTON_MIDDLE) {
+
+ if (mb->is_pressed()) {
+ int x = mb->get_position().x - timeline->get_name_limit();
+ panning_timeline_from = x / timeline->get_zoom_scale();
+ panning_timeline = true;
+ panning_timeline_at = timeline->get_value();
+ } else {
+ panning_timeline = false;
+ }
+ }
+
+ if (mb.is_valid() && mb->get_button_index() == BUTTON_RIGHT && mb->is_pressed()) {
+
+ menu_insert_key = mb->get_position();
+ Vector2 popup_pos = get_global_transform().xform(mb->get_position());
+
+ menu->clear();
+ menu->add_icon_item(bezier_icon, TTR("Insert Key Here"), MENU_KEY_INSERT);
+ if (selection.size()) {
+ menu->add_separator();
+ menu->add_icon_item(get_icon("Duplicate", "EditorIcons"), TTR("Duplicate Selected Key(s)"), MENU_KEY_DUPLICATE);
+ menu->add_separator();
+ menu->add_icon_item(get_icon("Remove", "EditorIcons"), TTR("Delete Selected Key(s)"), MENU_KEY_DELETE);
+ }
+
+ menu->set_as_minsize();
+ menu->set_position(popup_pos);
+ menu->popup();
+ }
+
+ if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) {
+
+ if (close_icon_rect.has_point(mb->get_position())) {
+ emit_signal("close_request");
+ return;
+ }
+ for (Map<int, Rect2>::Element *E = subtracks.front(); E; E = E->next()) {
+ if (E->get().has_point(mb->get_position())) {
+ set_animation_and_track(animation, E->key());
+ return;
+ }
+ }
+
+ for (int i = 0; i < edit_points.size(); i++) {
+
+ //first check point
+ //command makes it ignore the main point, so control point editors can be force-edited
+ //path 2D editing in the 3D and 2D editors works the same way
+ if (!mb->get_command()) {
+ if (edit_points[i].point_rect.has_point(mb->get_position())) {
+ if (mb->get_shift()) {
+ //add to selection
+ if (selection.has(i)) {
+ selection.erase(i);
+ } else {
+ selection.insert(i);
+ }
+ update();
+ select_single_attempt = -1;
+ } else if (selection.has(i)) {
+ moving_selection_attempt = true;
+ moving_selection = false;
+ moving_selection_from_key = i;
+ moving_selection_offset = Vector2();
+ select_single_attempt = i;
+ update();
+ } else {
+
+ moving_selection_attempt = true;
+ moving_selection = true;
+ moving_selection_from_key = i;
+ moving_selection_offset = Vector2();
+ selection.clear();
+ selection.insert(i);
+ update();
+ }
+ return;
+ }
+ }
+
+ if (edit_points[i].in_rect.has_point(mb->get_position())) {
+ moving_handle = -1;
+ moving_handle_key = i;
+ moving_handle_left = animation->bezier_track_get_key_in_handle(track, i);
+ moving_handle_right = animation->bezier_track_get_key_out_handle(track, i);
+ update();
+ return;
+ }
+
+ if (edit_points[i].out_rect.has_point(mb->get_position())) {
+ moving_handle = 1;
+ moving_handle_key = i;
+ moving_handle_left = animation->bezier_track_get_key_in_handle(track, i);
+ moving_handle_right = animation->bezier_track_get_key_out_handle(track, i);
+ update();
+ return;
+ ;
+ }
+ }
+
+ //insert new point
+ if (mb->get_command() && mb->get_position().x >= timeline->get_name_limit() && mb->get_position().x < get_size().width - timeline->get_buttons_width()) {
+
+ Array new_point;
+ new_point.resize(5);
+
+ float h = (get_size().height / 2 - mb->get_position().y) * v_zoom + v_scroll;
+
+ new_point[0] = h;
+ new_point[1] = -0.25;
+ new_point[2] = 0;
+ new_point[3] = 0.25;
+ new_point[4] = 0;
+
+ float time = ((mb->get_position().x - timeline->get_name_limit()) / timeline->get_zoom_scale()) + timeline->get_value();
+ while (animation->track_find_key(track, time, true) != -1) {
+ time += 0.001;
+ }
+
+ undo_redo->create_action("Add Bezier Point");
+ undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, time, new_point);
+ undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", track, time);
+ undo_redo->commit_action();
+
+ //then attempt to move
+ int index = animation->track_find_key(track, time, true);
+ ERR_FAIL_COND(index == -1);
+ _clear_selection();
+ selection.insert(index);
+
+ moving_selection_attempt = true;
+ moving_selection = false;
+ moving_selection_from_key = index;
+ moving_selection_offset = Vector2();
+ select_single_attempt = -1;
+ update();
+
+ return;
+ }
+
+ //box select
+ if (mb->get_position().x >= timeline->get_name_limit() && mb->get_position().x < get_size().width - timeline->get_buttons_width()) {
+ box_selecting_attempt = true;
+ box_selecting = false;
+ box_selecting_add = false;
+ box_selection_from = mb->get_position();
+ return;
+ }
+ }
+
+ if (box_selecting_attempt && mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) {
+
+ if (box_selecting) {
+ //do actual select
+ if (!box_selecting_add) {
+ _clear_selection();
+ }
+
+ Vector2 bs_from = box_selection_from;
+ Vector2 bs_to = box_selection_to;
+ if (bs_from.x > bs_to.x) {
+ SWAP(bs_from.x, bs_to.x);
+ }
+ if (bs_from.y > bs_to.y) {
+ SWAP(bs_from.y, bs_to.y);
+ }
+ Rect2 selection_rect(bs_from, bs_to - bs_from);
+
+ for (int i = 0; i < edit_points.size(); i++) {
+
+ if (edit_points[i].point_rect.intersects(selection_rect)) {
+ selection.insert(i);
+ }
+ }
+ } else {
+ _clear_selection(); //clicked and nothing happened, so clear the selection
+ }
+ box_selecting_attempt = false;
+ box_selecting = false;
+ update();
+ }
+
+ if (moving_handle != 0 && mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) {
+
+ undo_redo->create_action("Move Bezier Points");
+ undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_in_handle", track, moving_handle_key, moving_handle_left);
+ undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_out_handle", track, moving_handle_key, moving_handle_right);
+ undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", track, moving_handle_key, animation->bezier_track_get_key_in_handle(track, moving_handle_key));
+ undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", track, moving_handle_key, animation->bezier_track_get_key_out_handle(track, moving_handle_key));
+ undo_redo->commit_action();
+
+ moving_handle = 0;
+ update();
+ }
+
+ if (moving_selection_attempt && mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) {
+
+ if (moving_selection) {
+ //combit it
+
+ undo_redo->create_action("Move Bezier Points");
+
+ List<AnimMoveRestore> to_restore;
+ // 1-remove the keys
+ for (Set<int>::Element *E = selection.back(); E; E = E->prev()) {
+
+ undo_redo->add_do_method(animation.ptr(), "track_remove_key", track, E->get());
+ }
+ // 2- remove overlapped keys
+ for (Set<int>::Element *E = selection.back(); E; E = E->prev()) {
+
+ float newtime = animation->track_get_key_time(track, E->get()) + moving_selection_offset.x;
+
+ int idx = animation->track_find_key(track, newtime, true);
+ if (idx == -1)
+ continue;
+
+ if (selection.has(idx))
+ continue; //already in selection, don't save
+
+ undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_position", track, newtime);
+ AnimMoveRestore amr;
+
+ amr.key = animation->track_get_key_value(track, idx);
+ amr.track = track;
+ amr.time = newtime;
+
+ to_restore.push_back(amr);
+ }
+
+ // 3-move the keys (re insert them)
+ for (Set<int>::Element *E = selection.back(); E; E = E->prev()) {
+
+ float newpos = animation->track_get_key_time(track, E->get()) + moving_selection_offset.x;
+ /*
+ if (newpos<0)
+ continue; //no add at the beginning
+ */
+ Array key = animation->track_get_key_value(track, E->get());
+ float h = key[0];
+ h += moving_selection_offset.y;
+ key[0] = h;
+ undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, newpos, key, 1);
+ }
+
+ // 4-(undo) remove inserted keys
+ for (Set<int>::Element *E = selection.back(); E; E = E->prev()) {
+
+ float newpos = animation->track_get_key_time(track, E->get()) + moving_selection_offset.x;
+ /*
+ if (newpos<0)
+ continue; //no remove what no inserted
+ */
+ undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", track, newpos);
+ }
+
+ // 5-(undo) reinsert keys
+ for (Set<int>::Element *E = selection.back(); E; E = E->prev()) {
+
+ float oldpos = animation->track_get_key_time(track, E->get());
+ undo_redo->add_undo_method(animation.ptr(), "track_insert_key", track, oldpos, animation->track_get_key_value(track, E->get()), 1);
+ }
+
+ // 6-(undo) reinsert overlapped keys
+ for (List<AnimMoveRestore>::Element *E = to_restore.front(); E; E = E->next()) {
+
+ AnimMoveRestore &amr = E->get();
+ undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, 1);
+ }
+
+ // 6-(undo) reinsert overlapped keys
+ for (List<AnimMoveRestore>::Element *E = to_restore.front(); E; E = E->next()) {
+
+ AnimMoveRestore &amr = E->get();
+ undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, 1);
+ }
+
+ undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);
+ undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
+
+ // 7-reselect
+
+ for (Set<int>::Element *E = selection.back(); E; E = E->prev()) {
+
+ float oldpos = animation->track_get_key_time(track, E->get());
+ float newpos = oldpos + moving_selection_offset.x;
+
+ undo_redo->add_do_method(this, "_select_at_anim", animation, track, newpos);
+ undo_redo->add_undo_method(this, "_select_at_anim", animation, track, oldpos);
+ }
+
+ undo_redo->commit_action();
+
+ moving_selection = false;
+ } else if (select_single_attempt != -1) {
+ selection.clear();
+ selection.insert(select_single_attempt);
+ }
+
+ moving_selection_attempt = false;
+ update();
+ }
+
+ Ref<InputEventMouseMotion> mm = p_event;
+ if (mm.is_valid() && mm->get_button_mask() & BUTTON_MASK_MIDDLE) {
+ v_scroll += mm->get_relative().y * v_zoom;
+ if (v_scroll > 100000)
+ v_scroll = 100000;
+ if (v_scroll < -100000)
+ v_scroll = -100000;
+
+ int x = mm->get_position().x - timeline->get_name_limit();
+ float ofs = x / timeline->get_zoom_scale();
+ float diff = ofs - panning_timeline_from;
+ timeline->set_value(panning_timeline_at - diff);
+
+ update();
+ }
+ if (moving_selection_attempt && mm.is_valid()) {
+
+ if (!moving_selection) {
+ moving_selection = true;
+ select_single_attempt = -1;
+ }
+
+ float y = (get_size().height / 2 - mm->get_position().y) * v_zoom + v_scroll;
+ float x = ((mm->get_position().x - timeline->get_name_limit()) / timeline->get_zoom_scale()) + timeline->get_value();
+
+ moving_selection_offset = Vector2(x - animation->track_get_key_time(track, moving_selection_from_key), y - animation->bezier_track_get_key_value(track, moving_selection_from_key));
+ update();
+ }
+
+ if (box_selecting_attempt && mm.is_valid()) {
+
+ if (!box_selecting) {
+ box_selecting = true;
+ box_selecting_add = mm->get_shift();
+ }
+
+ box_selection_to = mm->get_position();
+
+ if (get_local_mouse_position().y < 0) {
+ //avoid cursor from going too above, so it does not lose focus with viewport
+ warp_mouse(Vector2(get_local_mouse_position().x, 0));
+ }
+ update();
+ }
+
+ if (moving_handle != 0 && mm.is_valid()) {
+
+ float y = (get_size().height / 2 - mm->get_position().y) * v_zoom + v_scroll;
+ float x = ((mm->get_position().x - timeline->get_name_limit()) / timeline->get_zoom_scale()) + timeline->get_value();
+
+ Vector2 key_pos = Vector2(animation->track_get_key_time(track, moving_handle_key), animation->bezier_track_get_key_value(track, moving_handle_key));
+
+ Vector2 moving_handle_value = Vector2(x, y) - key_pos;
+
+ moving_handle_left = animation->bezier_track_get_key_in_handle(track, moving_handle_key);
+ moving_handle_right = animation->bezier_track_get_key_out_handle(track, moving_handle_key);
+
+ if (moving_handle == -1) {
+ moving_handle_left = moving_handle_value;
+ if (moving_handle_left.x > 0) {
+ moving_handle_left.x = 0;
+ }
+
+ if (handle_mode_option->get_selected() == HANDLE_MODE_BALANCED) {
+ Vector2 scale = Vector2(timeline->get_zoom_scale(), v_zoom);
+ moving_handle_right = (-(moving_handle_left * scale).normalized() * (moving_handle_right * scale).length()) / scale;
+
+ } else if (handle_mode_option->get_selected() == HANDLE_MODE_MIRROR) {
+ moving_handle_right = -moving_handle_left;
+ }
+ }
+
+ if (moving_handle == 1) {
+ moving_handle_right = moving_handle_value;
+ if (moving_handle_right.x < 0) {
+ moving_handle_right.x = 0;
+ }
+
+ if (handle_mode_option->get_selected() == HANDLE_MODE_BALANCED) {
+ Vector2 scale = Vector2(timeline->get_zoom_scale(), v_zoom);
+ moving_handle_left = (-(moving_handle_right * scale).normalized() * (moving_handle_left * scale).length()) / scale;
+ } else if (handle_mode_option->get_selected() == HANDLE_MODE_MIRROR) {
+ moving_handle_left = -moving_handle_right;
+ }
+ }
+
+ update();
+ }
+}
+
+void AnimationBezierTrackEdit::_menu_selected(int p_index) {
+
+ switch (p_index) {
+ case MENU_KEY_INSERT: {
+
+ Array new_point;
+ new_point.resize(5);
+
+ float h = (get_size().height / 2 - menu_insert_key.y) * v_zoom + v_scroll;
+
+ new_point[0] = h;
+ new_point[1] = -0.25;
+ new_point[2] = 0;
+ new_point[3] = 0.25;
+ new_point[4] = 0;
+
+ float time = ((menu_insert_key.x - timeline->get_name_limit()) / timeline->get_zoom_scale()) + timeline->get_value();
+ while (animation->track_find_key(track, time, true) != -1) {
+ time += 0.001;
+ }
+
+ undo_redo->create_action("Add Bezier Point");
+ undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, time, new_point);
+ undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", track, time);
+ undo_redo->commit_action();
+
+ } break;
+ case MENU_KEY_DUPLICATE: {
+ duplicate_selection();
+ } break;
+ case MENU_KEY_DELETE: {
+ delete_selection();
+ } break;
+ }
+}
+
+void AnimationBezierTrackEdit::duplicate_selection() {
+
+ if (selection.size() == 0)
+ return;
+
+ float top_time = 1e10;
+ for (Set<int>::Element *E = selection.back(); E; E = E->prev()) {
+
+ float t = animation->track_get_key_time(track, E->get());
+ if (t < top_time)
+ top_time = t;
+ }
+
+ undo_redo->create_action(TTR("Anim Duplicate Keys"));
+
+ List<Pair<int, float> > new_selection_values;
+
+ for (Set<int>::Element *E = selection.back(); E; E = E->prev()) {
+
+ float t = animation->track_get_key_time(track, E->get());
+ float dst_time = t + (timeline->get_play_position() - top_time);
+ int existing_idx = animation->track_find_key(track, dst_time, true);
+
+ undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, dst_time, animation->track_get_key_value(track, E->get()), animation->track_get_key_transition(track, E->get()));
+ undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", track, dst_time);
+
+ Pair<int, float> p;
+ p.first = track;
+ p.second = dst_time;
+ new_selection_values.push_back(p);
+
+ if (existing_idx != -1) {
+
+ undo_redo->add_undo_method(animation.ptr(), "track_insert_key", track, dst_time, animation->track_get_key_value(track, existing_idx), animation->track_get_key_transition(track, existing_idx));
+ }
+ }
+
+ undo_redo->commit_action();
+
+ //reselect duplicated
+
+ selection.clear();
+ for (List<Pair<int, float> >::Element *E = new_selection_values.front(); E; E = E->next()) {
+
+ int track = E->get().first;
+ float time = E->get().second;
+
+ int existing_idx = animation->track_find_key(track, time, true);
+
+ if (existing_idx == -1)
+ continue;
+
+ selection.insert(existing_idx);
+ }
+
+ update();
+}
+
+void AnimationBezierTrackEdit::delete_selection() {
+ if (selection.size()) {
+ undo_redo->create_action(TTR("Anim Delete Keys"));
+
+ for (Set<int>::Element *E = selection.back(); E; E = E->prev()) {
+
+ undo_redo->add_do_method(animation.ptr(), "track_remove_key", track, E->get());
+ undo_redo->add_undo_method(animation.ptr(), "track_insert_key", track, animation->track_get_key_time(track, E->get()), animation->track_get_key_value(track, E->get()), 1);
+ }
+ undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);
+ undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
+ undo_redo->commit_action();
+ //selection.clear();
+ }
+}
+
+void AnimationBezierTrackEdit::set_block_animation_update_ptr(bool *p_block_ptr) {
+ block_animation_update_ptr = p_block_ptr;
+}
+
+void AnimationBezierTrackEdit::_bind_methods() {
+
+ ClassDB::bind_method("_zoom_changed", &AnimationBezierTrackEdit::_zoom_changed);
+ ClassDB::bind_method("_menu_selected", &AnimationBezierTrackEdit::_menu_selected);
+ ClassDB::bind_method("_gui_input", &AnimationBezierTrackEdit::_gui_input);
+ ClassDB::bind_method("_play_position_draw", &AnimationBezierTrackEdit::_play_position_draw);
+
+ ClassDB::bind_method("_clear_selection", &AnimationBezierTrackEdit::_clear_selection);
+ ClassDB::bind_method("_clear_selection_for_anim", &AnimationBezierTrackEdit::_clear_selection);
+ ClassDB::bind_method("_select_at_anim", &AnimationBezierTrackEdit::_clear_selection);
+
+ ADD_SIGNAL(MethodInfo("timeline_changed", PropertyInfo(Variant::REAL, "position"), PropertyInfo(Variant::BOOL, "drag")));
+ ADD_SIGNAL(MethodInfo("remove_request", PropertyInfo(Variant::INT, "track")));
+ ADD_SIGNAL(MethodInfo("insert_key", PropertyInfo(Variant::REAL, "ofs")));
+ 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("clear_selection"));
+ ADD_SIGNAL(MethodInfo("close_request"));
+
+ ADD_SIGNAL(MethodInfo("move_selection_begin"));
+ ADD_SIGNAL(MethodInfo("move_selection", PropertyInfo(Variant::REAL, "ofs")));
+ ADD_SIGNAL(MethodInfo("move_selection_commit"));
+ ADD_SIGNAL(MethodInfo("move_selection_cancel"));
+}
+
+AnimationBezierTrackEdit::AnimationBezierTrackEdit() {
+ undo_redo = NULL;
+ timeline = NULL;
+ root = NULL;
+ menu = NULL;
+ block_animation_update_ptr = NULL;
+
+ moving_selection_attempt = false;
+ moving_selection = false;
+ select_single_attempt = -1;
+ box_selecting = false;
+ box_selecting_attempt = false;
+
+ moving_handle = 0;
+
+ play_position_pos = 0;
+ play_position = memnew(Control);
+ play_position->set_mouse_filter(MOUSE_FILTER_PASS);
+ add_child(play_position);
+ play_position->set_anchors_and_margins_preset(PRESET_WIDE);
+ play_position->connect("draw", this, "_play_position_draw");
+ set_focus_mode(FOCUS_CLICK);
+
+ v_scroll = 0;
+ v_zoom = 1;
+
+ panning_timeline = false;
+ set_clip_contents(true);
+ handle_mode = HANDLE_MODE_FREE;
+ handle_mode_option = memnew(OptionButton);
+ add_child(handle_mode_option);
+
+ menu = memnew(PopupMenu);
+ add_child(menu);
+ menu->connect("id_pressed", this, "_menu_selected");
+
+ //set_mouse_filter(MOUSE_FILTER_PASS); //scroll has to work too for selection
+}
diff --git a/editor/animation_bezier_editor.h b/editor/animation_bezier_editor.h
new file mode 100644
index 0000000000..544690844a
--- /dev/null
+++ b/editor/animation_bezier_editor.h
@@ -0,0 +1,141 @@
+#ifndef ANIMATION_BEZIER_EDITOR_H
+#define ANIMATION_BEZIER_EDITOR_H
+
+#include "animation_track_editor.h"
+
+class AnimationBezierTrackEdit : public Control {
+
+ GDCLASS(AnimationBezierTrackEdit, Control)
+
+ enum HandleMode {
+ HANDLE_MODE_FREE,
+ HANDLE_MODE_BALANCED,
+ HANDLE_MODE_MIRROR
+ };
+
+ enum {
+ MENU_KEY_INSERT,
+ MENU_KEY_DUPLICATE,
+ MENU_KEY_DELETE
+ };
+
+ HandleMode handle_mode;
+ OptionButton *handle_mode_option;
+
+ AnimationTimelineEdit *timeline;
+ UndoRedo *undo_redo;
+ Node *root;
+ Control *play_position; //separate control used to draw so updates for only position changed are much faster
+ float play_position_pos;
+
+ Ref<Animation> animation;
+ int track;
+
+ Vector<Rect2> view_rects;
+
+ Ref<Texture> bezier_icon;
+ Ref<Texture> bezier_handle_icon;
+ Ref<Texture> selected_icon;
+
+ Rect2 close_icon_rect;
+
+ Map<int, Rect2> subtracks;
+
+ float v_scroll;
+ float v_zoom;
+
+ PopupMenu *menu;
+
+ void _zoom_changed();
+
+ void _gui_input(const Ref<InputEvent> &p_event);
+ void _menu_selected(int p_index);
+
+ bool *block_animation_update_ptr; //used to block all tracks re-gen (speed up)
+
+ void _play_position_draw();
+
+ Vector2 insert_at_pos;
+
+ bool moving_selection_attempt;
+ int select_single_attempt;
+ bool moving_selection;
+ int moving_selection_from_key;
+
+ Vector2 moving_selection_offset;
+
+ bool box_selecting_attempt;
+ bool box_selecting;
+ bool box_selecting_add;
+ Vector2 box_selection_from;
+ Vector2 box_selection_to;
+
+ int moving_handle; //0 no move -1 or +1 out
+ int moving_handle_key;
+ Vector2 moving_handle_left;
+ Vector2 moving_handle_right;
+
+ void _clear_selection();
+ void _clear_selection_for_anim(const Ref<Animation> &p_anim);
+ void _select_at_anim(const Ref<Animation> &p_anim, int p_track, float p_pos);
+
+ Vector2 menu_insert_key;
+
+ struct AnimMoveRestore {
+
+ int track;
+ float time;
+ Variant key;
+ float transition;
+ };
+
+ AnimationTrackEditor *editor;
+
+ struct EditPoint {
+ Rect2 point_rect;
+ Rect2 in_rect;
+ Rect2 out_rect;
+ };
+
+ Vector<EditPoint> edit_points;
+
+ Set<int> selection;
+
+ bool panning_timeline;
+ float panning_timeline_from;
+ float panning_timeline_at;
+
+ void _draw_line_clipped(const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, int p_clip_left, int p_clip_right);
+ void _draw_track(int p_track, const Color &p_color);
+
+ float _bezier_h_to_pixel(float p_h);
+
+protected:
+ static void _bind_methods();
+ void _notification(int p_what);
+
+public:
+ virtual String get_tooltip(const Point2 &p_pos) const;
+
+ Ref<Animation> get_animation() const;
+
+ void set_animation_and_track(const Ref<Animation> &p_animation, int p_track);
+ virtual Size2 get_minimum_size() const;
+
+ void set_undo_redo(UndoRedo *p_undo_redo);
+ void set_timeline(AnimationTimelineEdit *p_timeline);
+ void set_editor(AnimationTrackEditor *p_editor);
+ void set_root(Node *p_root);
+
+ void set_block_animation_update_ptr(bool *p_block_ptr);
+
+ void set_play_position(float p_pos);
+ void update_play_position();
+
+ void duplicate_selection();
+ void delete_selection();
+
+ AnimationBezierTrackEdit();
+};
+
+#endif // ANIMATION_BEZIER_EDITOR_H
diff --git a/editor/animation_editor.cpp b/editor/animation_editor.cpp
deleted file mode 100644
index a03bf76d1b..0000000000
--- a/editor/animation_editor.cpp
+++ /dev/null
@@ -1,4146 +0,0 @@
-/*************************************************************************/
-/* animation_editor.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#include "animation_editor.h"
-
-#include "editor/plugins/animation_player_editor_plugin.h"
-#include "editor_node.h"
-#include "editor_settings.h"
-#include "io/resource_saver.h"
-#include "os/keyboard.h"
-#include "os/os.h"
-#include "pair.h"
-#include "scene/gui/separator.h"
-#include "scene/main/viewport.h"
-
-/* Missing to fix:
-
- *Set
- *Find better source for hint for edited value keys
- * + button on track to add a key
- * when clicked for first time, erase selection of not selected at first
- * automatically create discrete/continuous tracks!!
- *when create track do undo/redo
-*/
-
-class AnimationCurveEdit : public Control {
- GDCLASS(AnimationCurveEdit, Control);
-
-public:
- enum Mode {
- MODE_DISABLED,
- MODE_SINGLE,
- MODE_MULTIPLE
- };
-
-private:
- Set<float> multiples;
- float transition;
- Mode mode;
-
- LineEdit *value_edit;
-
- void _notification(int p_what) {
-
- if (p_what == NOTIFICATION_DRAW) {
-
- RID ci = get_canvas_item();
-
- Size2 s = get_size();
- Rect2 r(Point2(), s);
-
- //r=r.grow(3);
- Ref<StyleBox> sb = get_stylebox("normal", "LineEdit");
- sb->draw(ci, r);
- r.size -= sb->get_minimum_size();
- r.position += sb->get_offset();
- //VisualServer::get_singleton()->canvas_item_add
-
- Ref<Font> f = get_font("font", "Label");
- r = r.grow(-2);
- Color color = get_color("font_color", "Label");
-
- int points = 48;
- if (mode == MODE_MULTIPLE) {
-
- Color mcolor = color;
- mcolor.a *= 0.3;
-
- Set<float>::Element *E = multiples.front();
- for (int j = 0; j < 16; j++) {
-
- if (!E)
- break;
-
- float prev = 1.0;
- float exp = E->get();
- bool flip = false; //hint_text=="attenuation";
-
- for (int i = 1; i <= points; i++) {
-
- float ifl = i / float(points);
- float iflp = (i - 1) / float(points);
-
- float h = 1.0 - Math::ease(ifl, exp);
-
- if (flip) {
- ifl = 1.0 - ifl;
- iflp = 1.0 - iflp;
- }
-
- VisualServer::get_singleton()->canvas_item_add_line(ci, r.position + Point2(iflp * r.size.width, prev * r.size.height), r.position + Point2(ifl * r.size.width, h * r.size.height), mcolor);
- prev = h;
- }
-
- E = E->next();
- }
- }
-
- float exp = transition;
- if (mode != MODE_DISABLED) {
-
- float prev = 1.0;
-
- bool flip = false; //hint_text=="attenuation";
-
- for (int i = 1; i <= points; i++) {
-
- float ifl = i / float(points);
- float iflp = (i - 1) / float(points);
-
- float h = 1.0 - Math::ease(ifl, exp);
-
- if (flip) {
- ifl = 1.0 - ifl;
- iflp = 1.0 - iflp;
- }
-
- VisualServer::get_singleton()->canvas_item_add_line(ci, r.position + Point2(iflp * r.size.width, prev * r.size.height), r.position + Point2(ifl * r.size.width, h * r.size.height), color);
- prev = h;
- }
- }
-
- if (mode == MODE_DISABLED) {
- f->draw(ci, Point2(5, 5 + f->get_ascent()), TTR("Disabled"), color);
- } else if (mode == MODE_MULTIPLE) {
- f->draw(ci, Point2(5, 5 + f->get_ascent() + value_edit->get_size().height), TTR("All Selection"), color);
- }
- }
- }
-
- void _gui_input(const Ref<InputEvent> &p_ev) {
-
- Ref<InputEventMouseMotion> mm = p_ev;
- if (mm.is_valid() && mm->get_button_mask() & BUTTON_MASK_LEFT) {
-
- if (mode == MODE_DISABLED)
- return;
-
- value_edit->release_focus();
-
- float rel = mm->get_relative().x;
- if (rel == 0)
- return;
-
- bool flip = false;
-
- if (flip)
- rel = -rel;
-
- float val = transition;
- if (val == 0)
- return;
- bool sg = val < 0;
- val = Math::absf(val);
-
- val = Math::log(val) / Math::log((float)2.0);
- //logspace
- val += rel * 0.05;
- //
-
- val = Math::pow((float)2.0, val);
- if (sg)
- val = -val;
-
- force_transition(val);
- }
- }
-
- void _edit_value_changed(const String &p_value_str) {
-
- force_transition(p_value_str.to_float());
- }
-
-public:
- static void _bind_methods() {
-
- //ClassDB::bind_method("_update_obj",&AnimationKeyEdit::_update_obj);
- ClassDB::bind_method("_gui_input", &AnimationCurveEdit::_gui_input);
- ClassDB::bind_method("_edit_value_changed", &AnimationCurveEdit::_edit_value_changed);
- ADD_SIGNAL(MethodInfo("transition_changed"));
- }
-
- void set_mode(Mode p_mode) {
-
- mode = p_mode;
- value_edit->set_visible(mode != MODE_DISABLED);
- update();
- }
-
- void clear_multiples() {
- multiples.clear();
- update();
- }
- void set_multiple(float p_transition) {
-
- multiples.insert(p_transition);
- }
-
- void set_transition(float p_transition) {
- transition = Math::stepify(p_transition, 0.01);
- value_edit->set_text(String::num(transition));
- update();
- }
-
- float get_transition() const {
- return transition;
- }
-
- void force_transition(float p_value) {
- if (mode == MODE_DISABLED)
- return;
- set_transition(p_value);
- emit_signal("transition_changed", p_value);
- }
-
- AnimationCurveEdit() {
-
- transition = 1.0;
- set_default_cursor_shape(CURSOR_HSPLIT);
- mode = MODE_DISABLED;
-
- value_edit = memnew(LineEdit);
- value_edit->hide();
- value_edit->connect("text_entered", this, "_edit_value_changed");
- add_child(value_edit);
- }
-};
-
-class AnimationKeyEdit : public Object {
-
- GDCLASS(AnimationKeyEdit, Object);
-
-public:
- bool setting;
- bool hidden;
-
- static void _bind_methods() {
-
- ClassDB::bind_method("_update_obj", &AnimationKeyEdit::_update_obj);
- ClassDB::bind_method("_key_ofs_changed", &AnimationKeyEdit::_key_ofs_changed);
- }
-
- //PopupDialog *ke_dialog;
-
- void _fix_node_path(Variant &value) {
-
- NodePath np = value;
-
- if (np == NodePath())
- return;
-
- Node *root = EditorNode::get_singleton()->get_tree()->get_root();
-
- Node *np_node = root->get_node(np);
- ERR_FAIL_COND(!np_node);
-
- Node *edited_node = root->get_node(base);
- ERR_FAIL_COND(!edited_node);
-
- value = edited_node->get_path_to(np_node);
- }
-
- void _update_obj(const Ref<Animation> &p_anim) {
- if (setting)
- return;
- if (hidden)
- return;
- if (!(animation == p_anim))
- return;
- notify_change();
- }
-
- void _key_ofs_changed(const Ref<Animation> &p_anim, float from, float to) {
- if (hidden)
- return;
- if (!(animation == p_anim))
- return;
- if (from != key_ofs)
- return;
- key_ofs = to;
- if (setting)
- return;
- notify_change();
- }
-
- bool _set(const StringName &p_name, const Variant &p_value) {
-
- int key = animation->track_find_key(track, key_ofs, true);
- ERR_FAIL_COND_V(key == -1, false);
-
- String name = p_name;
- if (name == "time") {
-
- float new_time = p_value;
- if (new_time == key_ofs)
- return true;
-
- int existing = animation->track_find_key(track, new_time, true);
-
- setting = true;
- undo_redo->create_action(TTR("Anim Change Keyframe Time"), UndoRedo::MERGE_ENDS);
-
- Variant val = animation->track_get_key_value(track, key);
- float trans = animation->track_get_key_transition(track, key);
-
- undo_redo->add_do_method(animation.ptr(), "track_remove_key", track, key);
- undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, new_time, val, trans);
- undo_redo->add_do_method(this, "_key_ofs_changed", animation, key_ofs, new_time);
- undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", track, new_time);
- undo_redo->add_undo_method(animation.ptr(), "track_insert_key", track, key_ofs, val, trans);
- undo_redo->add_undo_method(this, "_key_ofs_changed", animation, new_time, key_ofs);
-
- if (existing != -1) {
- Variant v = animation->track_get_key_value(track, existing);
- float trans = animation->track_get_key_transition(track, existing);
- undo_redo->add_undo_method(animation.ptr(), "track_insert_key", track, new_time, v, trans);
- }
-
- undo_redo->commit_action();
- setting = false;
-
- return true;
- } else if (name == "easing") {
-
- float val = p_value;
- float prev_val = animation->track_get_key_transition(track, key);
- setting = true;
- undo_redo->create_action(TTR("Anim Change Transition"), UndoRedo::MERGE_ENDS);
- undo_redo->add_do_method(animation.ptr(), "track_set_key_transition", track, key, val);
- undo_redo->add_undo_method(animation.ptr(), "track_set_key_transition", track, key, prev_val);
- undo_redo->add_do_method(this, "_update_obj", animation);
- undo_redo->add_undo_method(this, "_update_obj", animation);
- undo_redo->commit_action();
- setting = false;
- return true;
- }
-
- switch (animation->track_get_type(track)) {
-
- case Animation::TYPE_TRANSFORM: {
-
- Dictionary d_old = animation->track_get_key_value(track, key);
- Dictionary d_new = d_old;
- d_new[p_name] = p_value;
- setting = true;
- undo_redo->create_action(TTR("Anim Change Transform"));
- undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, d_new);
- undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, d_old);
- undo_redo->add_do_method(this, "_update_obj", animation);
- undo_redo->add_undo_method(this, "_update_obj", animation);
- undo_redo->commit_action();
- setting = false;
- return true;
-
- } break;
- case Animation::TYPE_VALUE: {
-
- if (name == "value") {
-
- Variant value = p_value;
-
- if (value.get_type() == Variant::NODE_PATH) {
-
- _fix_node_path(value);
- }
-
- setting = true;
- undo_redo->create_action(TTR("Anim Change Keyframe Value"), UndoRedo::MERGE_ENDS);
- Variant prev = animation->track_get_key_value(track, key);
- undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, value);
- undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, prev);
- undo_redo->add_do_method(this, "_update_obj", animation);
- undo_redo->add_undo_method(this, "_update_obj", animation);
- undo_redo->commit_action();
- setting = false;
- return true;
- }
-
- } break;
- case Animation::TYPE_METHOD: {
-
- Dictionary d_old = animation->track_get_key_value(track, key);
- Dictionary d_new = d_old;
-
- bool change_notify_deserved = false;
- bool mergeable = false;
-
- if (name == "name") {
-
- d_new["method"] = p_value;
- }
-
- if (name == "arg_count") {
-
- Vector<Variant> args = d_old["args"];
- args.resize(p_value);
- d_new["args"] = args;
- change_notify_deserved = true;
- }
-
- if (name.begins_with("args/")) {
-
- Vector<Variant> args = d_old["args"];
- int idx = name.get_slice("/", 1).to_int();
- ERR_FAIL_INDEX_V(idx, args.size(), false);
-
- String what = name.get_slice("/", 2);
- if (what == "type") {
- Variant::Type t = Variant::Type(int(p_value));
-
- if (t != args[idx].get_type()) {
- Variant::CallError err;
- if (Variant::can_convert(args[idx].get_type(), t)) {
- Variant old = args[idx];
- Variant *ptrs[1] = { &old };
- args[idx] = Variant::construct(t, (const Variant **)ptrs, 1, err);
- } else {
-
- args[idx] = Variant::construct(t, NULL, 0, err);
- }
- change_notify_deserved = true;
- d_new["args"] = args;
- }
- }
- if (what == "value") {
-
- Variant value = p_value;
- if (value.get_type() == Variant::NODE_PATH) {
-
- _fix_node_path(value);
- }
-
- args[idx] = value;
- d_new["args"] = args;
- mergeable = true;
- }
- }
-
- if (mergeable)
- undo_redo->create_action(TTR("Anim Change Call"), UndoRedo::MERGE_ENDS);
- else
- undo_redo->create_action(TTR("Anim Change Call"));
-
- Variant prev = animation->track_get_key_value(track, key);
- setting = true;
- undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, d_new);
- undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, d_old);
- undo_redo->add_do_method(this, "_update_obj", animation);
- undo_redo->add_undo_method(this, "_update_obj", animation);
- undo_redo->commit_action();
- setting = false;
- if (change_notify_deserved)
- notify_change();
- return true;
- } break;
- }
-
- return false;
- }
-
- bool _get(const StringName &p_name, Variant &r_ret) const {
-
- int key = animation->track_find_key(track, key_ofs, true);
- ERR_FAIL_COND_V(key == -1, false);
-
- String name = p_name;
- if (name == "time") {
- r_ret = key_ofs;
- return true;
- } else if (name == "easing") {
- r_ret = animation->track_get_key_transition(track, key);
- return true;
- }
-
- switch (animation->track_get_type(track)) {
-
- case Animation::TYPE_TRANSFORM: {
-
- Dictionary d = animation->track_get_key_value(track, key);
- ERR_FAIL_COND_V(!d.has(name), false);
- r_ret = d[p_name];
- return true;
-
- } break;
- case Animation::TYPE_VALUE: {
-
- if (name == "value") {
- r_ret = animation->track_get_key_value(track, key);
- return true;
- }
-
- } break;
- case Animation::TYPE_METHOD: {
-
- Dictionary d = animation->track_get_key_value(track, key);
-
- if (name == "name") {
-
- ERR_FAIL_COND_V(!d.has("method"), false);
- r_ret = d["method"];
- return true;
- }
-
- ERR_FAIL_COND_V(!d.has("args"), false);
-
- Vector<Variant> args = d["args"];
-
- if (name == "arg_count") {
-
- r_ret = args.size();
- return true;
- }
-
- if (name.begins_with("args/")) {
-
- int idx = name.get_slice("/", 1).to_int();
- ERR_FAIL_INDEX_V(idx, args.size(), false);
-
- String what = name.get_slice("/", 2);
- if (what == "type") {
- r_ret = args[idx].get_type();
- return true;
- }
- if (what == "value") {
- r_ret = args[idx];
- return true;
- }
- }
-
- } break;
- }
-
- return false;
- }
- void _get_property_list(List<PropertyInfo> *p_list) const {
-
- if (animation.is_null())
- return;
-
- ERR_FAIL_INDEX(track, animation->get_track_count());
- int key = animation->track_find_key(track, key_ofs, true);
- ERR_FAIL_COND(key == -1);
-
- p_list->push_back(PropertyInfo(Variant::REAL, "time", PROPERTY_HINT_RANGE, "0," + rtos(animation->get_length()) + ",0.01"));
-
- switch (animation->track_get_type(track)) {
-
- case Animation::TYPE_TRANSFORM: {
-
- p_list->push_back(PropertyInfo(Variant::VECTOR3, "location"));
- p_list->push_back(PropertyInfo(Variant::QUAT, "rotation"));
- p_list->push_back(PropertyInfo(Variant::VECTOR3, "scale"));
-
- } 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";
- p_list->push_back(pi);
- } else {
-
- PropertyHint hint = PROPERTY_HINT_NONE;
- String 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();
- }
- }
-
- if (v.get_type() != Variant::NIL)
- p_list->push_back(PropertyInfo(v.get_type(), "value", hint, hint_string));
- }
-
- } break;
- case Animation::TYPE_METHOD: {
-
- p_list->push_back(PropertyInfo(Variant::STRING, "name"));
- p_list->push_back(PropertyInfo(Variant::INT, "arg_count", PROPERTY_HINT_RANGE, "0,5,1"));
-
- Dictionary d = animation->track_get_key_value(track, key);
- ERR_FAIL_COND(!d.has("args"));
- Vector<Variant> args = d["args"];
- String vtypes;
- for (int i = 0; i < Variant::VARIANT_MAX; i++) {
-
- if (i > 0)
- vtypes += ",";
- vtypes += Variant::get_type_name(Variant::Type(i));
- }
-
- for (int i = 0; i < args.size(); i++) {
-
- p_list->push_back(PropertyInfo(Variant::INT, "args/" + itos(i) + "/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"));
- }
-
- } break;
- }
-
- /*
- if (animation->track_get_type(track)!=Animation::TYPE_METHOD)
- p_list->push_back( PropertyInfo( Variant::REAL, "easing", PROPERTY_HINT_EXP_EASING));
- */
- }
-
- UndoRedo *undo_redo;
- Ref<Animation> animation;
- int track;
- float key_ofs;
-
- PropertyInfo hint;
- NodePath base;
-
- void notify_change() {
-
- _change_notify();
- }
-
- AnimationKeyEdit() {
- hidden = true;
- key_ofs = 0;
- track = -1;
- setting = false;
- }
-};
-
-void AnimationKeyEditor::_menu_add_track(int p_type) {
-
- ERR_FAIL_COND(!animation.is_valid());
-
- switch (p_type) {
-
- case ADD_TRACK_MENU_ADD_CALL_TRACK: {
- if (root) {
- call_select->popup_centered_ratio();
- break;
- }
- } break;
- case ADD_TRACK_MENU_ADD_VALUE_TRACK:
- case ADD_TRACK_MENU_ADD_TRANSFORM_TRACK: {
-
- undo_redo->create_action(TTR("Anim Add Track"));
- undo_redo->add_do_method(animation.ptr(), "add_track", p_type);
- undo_redo->add_do_method(animation.ptr(), "track_set_path", animation->get_track_count(), ".");
- undo_redo->add_undo_method(animation.ptr(), "remove_track", animation->get_track_count());
- undo_redo->commit_action();
-
- } break;
- }
-}
-
-void AnimationKeyEditor::_anim_duplicate_keys(bool transpose) {
- //duplicait!
- if (selection.size() && animation.is_valid() && selected_track >= 0 && selected_track < animation->get_track_count()) {
-
- int top_track = 0x7FFFFFFF;
- float top_time = 1e10;
- for (Map<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);
- if (t < top_time)
- top_time = t;
- if (sk.track < top_track)
- top_track = sk.track;
- }
- ERR_FAIL_COND(top_track == 0x7FFFFFFF || top_time == 1e10);
-
- //
-
- int start_track = transpose ? selected_track : top_track;
-
- undo_redo->create_action(TTR("Anim Duplicate Keys"));
-
- List<Pair<int, float> > new_selection_values;
-
- for (Map<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);
-
- float dst_time = t + (timeline_pos - top_time);
- int dst_track = sk.track + (start_track - top_track);
-
- if (dst_track < 0 || dst_track >= animation->get_track_count())
- continue;
-
- if (animation->track_get_type(dst_track) != animation->track_get_type(sk.track))
- continue;
-
- int existing_idx = animation->track_find_key(dst_track, dst_time, true);
-
- undo_redo->add_do_method(animation.ptr(), "track_insert_key", dst_track, dst_time, animation->track_get_key_value(E->key().track, E->key().key), animation->track_get_key_transition(E->key().track, E->key().key));
- undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", dst_track, dst_time);
-
- Pair<int, float> p;
- p.first = dst_track;
- p.second = dst_time;
- new_selection_values.push_back(p);
-
- if (existing_idx != -1) {
-
- undo_redo->add_undo_method(animation.ptr(), "track_insert_key", dst_track, dst_time, animation->track_get_key_value(dst_track, existing_idx), animation->track_get_key_transition(dst_track, existing_idx));
- }
- }
-
- undo_redo->commit_action();
-
- //reselect duplicated
-
- Map<SelectedKey, KeyInfo> new_selection;
- for (List<Pair<int, float> >::Element *E = new_selection_values.front(); E; E = E->next()) {
-
- int track = E->get().first;
- float time = E->get().second;
-
- int existing_idx = animation->track_find_key(track, time, true);
-
- if (existing_idx == -1)
- continue;
- SelectedKey sk2;
- sk2.track = track;
- sk2.key = existing_idx;
-
- KeyInfo ki;
- ki.pos = time;
-
- new_selection[sk2] = ki;
- }
-
- selection = new_selection;
- track_editor->update();
- _edit_if_single_selection();
- }
-}
-
-void AnimationKeyEditor::_menu_track(int p_type) {
-
- ERR_FAIL_COND(!animation.is_valid());
-
- last_menu_track_opt = p_type;
- switch (p_type) {
-
- case TRACK_MENU_SCALE:
- case TRACK_MENU_SCALE_PIVOT: {
-
- scale_dialog->popup_centered(Size2(200, 100));
- } break;
- case TRACK_MENU_MOVE_UP: {
-
- int idx = selected_track;
- if (idx > 0 && idx < animation->get_track_count()) {
- undo_redo->create_action(TTR("Move Anim Track Up"));
- undo_redo->add_do_method(animation.ptr(), "track_move_down", idx);
- undo_redo->add_undo_method(animation.ptr(), "track_move_up", idx - 1);
- undo_redo->commit_action();
- selected_track = idx - 1;
- }
-
- } break;
- case TRACK_MENU_MOVE_DOWN: {
-
- int idx = selected_track;
- if (idx >= 0 && idx < animation->get_track_count() - 1) {
- undo_redo->create_action(TTR("Move Anim Track Down"));
- undo_redo->add_do_method(animation.ptr(), "track_move_up", idx);
- undo_redo->add_undo_method(animation.ptr(), "track_move_down", idx + 1);
- undo_redo->commit_action();
- selected_track = idx + 1;
- }
-
- } break;
- case TRACK_MENU_REMOVE: {
-
- int idx = selected_track;
- if (idx >= 0 && idx < animation->get_track_count()) {
- undo_redo->create_action(TTR("Remove Anim Track"));
- 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));
- //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);
-
- 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(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->commit_action();
- }
-
- } break;
- case TRACK_MENU_DUPLICATE:
- case TRACK_MENU_DUPLICATE_TRANSPOSE: {
-
- _anim_duplicate_keys(p_type == TRACK_MENU_DUPLICATE_TRANSPOSE);
- } break;
- case TRACK_MENU_SET_ALL_TRANS_LINEAR:
- case TRACK_MENU_SET_ALL_TRANS_CONSTANT:
- case TRACK_MENU_SET_ALL_TRANS_OUT:
- case TRACK_MENU_SET_ALL_TRANS_IN:
- case TRACK_MENU_SET_ALL_TRANS_INOUT:
- case TRACK_MENU_SET_ALL_TRANS_OUTIN: {
-
- if (!selection.size() || !animation.is_valid())
- break;
-
- float t = 0;
- switch (p_type) {
- case TRACK_MENU_SET_ALL_TRANS_LINEAR: t = 1.0; break;
- case TRACK_MENU_SET_ALL_TRANS_CONSTANT: t = 0.0; break;
- case TRACK_MENU_SET_ALL_TRANS_OUT: t = 0.5; break;
- case TRACK_MENU_SET_ALL_TRANS_IN: t = 2.0; break;
- case TRACK_MENU_SET_ALL_TRANS_INOUT: t = -0.5; break;
- case TRACK_MENU_SET_ALL_TRANS_OUTIN: t = -2.0; break;
- }
-
- undo_redo->create_action(TTR("Set Transitions to:") + " " + rtos(t));
-
- for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
-
- const SelectedKey &sk = E->key();
-
- undo_redo->add_do_method(animation.ptr(), "track_set_key_transition", sk.track, sk.key, t);
- undo_redo->add_undo_method(animation.ptr(), "track_set_key_transition", sk.track, sk.key, animation->track_get_key_transition(sk.track, sk.key));
- }
-
- undo_redo->commit_action();
-
- } break;
- case TRACK_MENU_NEXT_STEP: {
-
- if (animation.is_null())
- break;
- float step = animation->get_step();
- if (step == 0)
- step = 1;
-
- float pos = timeline_pos;
-
- pos = Math::stepify(pos + step, step);
- if (pos > animation->get_length())
- pos = animation->get_length();
- timeline_pos = pos;
- track_pos->update();
- emit_signal("timeline_changed", pos, true);
-
- } break;
- case TRACK_MENU_PREV_STEP: {
- if (animation.is_null())
- break;
- float step = animation->get_step();
- if (step == 0)
- step = 1;
-
- float pos = timeline_pos;
- pos = Math::stepify(pos - step, step);
- if (pos < 0)
- pos = 0;
- timeline_pos = pos;
- track_pos->update();
- emit_signal("timeline_changed", pos, true);
-
- } break;
-
- case TRACK_MENU_OPTIMIZE: {
-
- optimize_dialog->popup_centered(Size2(250, 180));
- } break;
- case TRACK_MENU_CLEAN_UP: {
-
- cleanup_dialog->popup_centered_minsize(Size2(300, 0));
- } break;
- case TRACK_MENU_CLEAN_UP_CONFIRM: {
-
- if (cleanup_all->is_pressed()) {
- List<StringName> names;
- AnimationPlayerEditor::singleton->get_player()->get_animation_list(&names);
- for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- _cleanup_animation(AnimationPlayerEditor::singleton->get_player()->get_animation(E->get()));
- }
- } else {
- _cleanup_animation(animation);
- }
- } break;
- case CURVE_SET_LINEAR: {
- curve_edit->force_transition(1.0);
-
- } break;
- case CURVE_SET_IN: {
-
- curve_edit->force_transition(4.0);
-
- } break;
- case CURVE_SET_OUT: {
-
- curve_edit->force_transition(0.25);
- } break;
- case CURVE_SET_INOUT: {
- curve_edit->force_transition(-4);
-
- } break;
- case CURVE_SET_OUTIN: {
-
- curve_edit->force_transition(-0.25);
- } break;
- case CURVE_SET_CONSTANT: {
-
- curve_edit->force_transition(0);
- } break;
- }
-}
-
-void AnimationKeyEditor::_cleanup_animation(Ref<Animation> p_animation) {
-
- for (int i = 0; i < p_animation->get_track_count(); i++) {
-
- bool prop_exists = false;
- Variant::Type valid_type = Variant::NIL;
- Object *obj = NULL;
-
- RES res;
- Vector<StringName> leftover_path;
-
- Node *node = root->get_node_and_resource(p_animation->track_get_path(i), res, leftover_path);
-
- if (res.is_valid()) {
- obj = res.ptr();
- } else if (node) {
- obj = node;
- }
-
- if (obj && p_animation->track_get_type(i) == Animation::TYPE_VALUE) {
- valid_type = obj->get_static_property_type_indexed(leftover_path, &prop_exists);
- }
-
- if (!obj && cleanup_tracks->is_pressed()) {
-
- p_animation->remove_track(i);
- i--;
- continue;
- }
-
- if (!prop_exists || p_animation->track_get_type(i) != Animation::TYPE_VALUE || cleanup_keys->is_pressed() == false)
- continue;
-
- for (int j = 0; j < p_animation->track_get_key_count(i); j++) {
-
- Variant v = p_animation->track_get_key_value(i, j);
-
- if (!Variant::can_convert(v.get_type(), valid_type)) {
- p_animation->track_remove_key(i, j);
- j--;
- }
- }
-
- if (p_animation->track_get_key_count(i) == 0 && cleanup_tracks->is_pressed()) {
- p_animation->remove_track(i);
- i--;
- }
- }
-
- undo_redo->clear_history();
- _update_paths();
-}
-
-void AnimationKeyEditor::_animation_optimize() {
-
- animation->optimize(optimize_linear_error->get_value(), optimize_angular_error->get_value(), optimize_max_angle->get_value());
- track_editor->update();
- undo_redo->clear_history();
-}
-
-float AnimationKeyEditor::_get_zoom_scale() const {
-
- float zv = zoom->get_value();
- if (zv < 1) {
- zv = 1.0 - zv;
- return Math::pow(1.0f + zv, 8.0f) * 100;
- } else {
- return 1.0 / Math::pow(zv, 8.0f) * 100;
- }
-}
-
-void AnimationKeyEditor::_track_position_draw() {
-
- if (!animation.is_valid()) {
- return;
- }
-
- Ref<StyleBox> style = get_stylebox("normal", "TextEdit");
- Size2 size = track_editor->get_size() - style->get_minimum_size();
- Size2 ofs = style->get_offset();
-
- int settings_limit = size.width - right_data_size_cache;
- int name_limit = settings_limit * name_column_ratio;
-
- float keys_from = h_scroll->get_value();
- float zoom_scale = _get_zoom_scale();
- float keys_to = keys_from + (settings_limit - name_limit) / zoom_scale;
-
- //will move to separate control! (for speedup)
- if (timeline_pos >= keys_from && timeline_pos < keys_to) {
- //draw position
- int pixel = (timeline_pos - h_scroll->get_value()) * zoom_scale;
- pixel += name_limit;
- track_pos->draw_line(ofs + Point2(pixel, 0), ofs + Point2(pixel, size.height), get_color("accent_color", "Editor"));
- }
-}
-
-void AnimationKeyEditor::_track_editor_draw() {
-
- if (animation.is_valid() && animation->get_track_count()) {
- if (selected_track < 0)
- selected_track = 0;
- else if (selected_track >= animation->get_track_count())
- selected_track = animation->get_track_count() - 1;
- }
-
- track_pos->update();
- Control *te = track_editor;
- Ref<StyleBox> style = get_stylebox("normal", "TextEdit");
- te->draw_style_box(style, Rect2(Point2(), track_editor->get_size()));
-
- if (te->has_focus()) {
- te->draw_style_box(get_stylebox("bg_focus", "Tree"), Rect2(Point2(), track_editor->get_size()));
- }
-
- if (!animation.is_valid()) {
- v_scroll->hide();
- h_scroll->hide();
- length->set_editable(false);
- step->set_editable(false);
- loop->set_disabled(true);
- menu_add_track->set_disabled(true);
- menu_track->set_disabled(true);
- edit_button->set_disabled(true);
- key_editor_tab->hide();
- move_up_button->set_disabled(true);
- move_down_button->set_disabled(true);
- remove_button->set_disabled(true);
-
- return;
- }
-
- length->set_editable(true);
- step->set_editable(true);
- loop->set_disabled(false);
- menu_add_track->set_disabled(false);
- menu_track->set_disabled(false);
- edit_button->set_disabled(false);
- move_up_button->set_disabled(false);
- move_down_button->set_disabled(false);
- remove_button->set_disabled(false);
- if (edit_button->is_pressed())
- key_editor_tab->show();
-
- te_drawing = true;
-
- Size2 size = te->get_size() - style->get_minimum_size();
- Size2 ofs = style->get_offset();
-
- Ref<Font> font = te->get_font("font", "Tree");
- int sep = get_constant("vseparation", "Tree");
- int hsep = get_constant("hseparation", "Tree");
- Color color = get_color("font_color", "Tree");
- Color sepcolor = color;
- sepcolor.a = 0.2;
- Color timecolor = color;
- timecolor.a = 0.2;
- Color hover_color = color;
- hover_color.a = 0.05;
- Color select_color = color;
- select_color.a = 0.1;
- Color invalid_path_color = get_color("error_color", "Editor");
- Color track_select_color = get_color("highlighted_font_color", "Editor");
-
- Ref<Texture> remove_icon = get_icon("Remove", "EditorIcons");
- Ref<Texture> move_up_icon = get_icon("MoveUp", "EditorIcons");
- Ref<Texture> move_down_icon = get_icon("MoveDown", "EditorIcons");
- Ref<Texture> remove_icon_hl = get_icon("RemoveHl", "EditorIcons");
- Ref<Texture> move_up_icon_hl = get_icon("MoveUpHl", "EditorIcons");
- Ref<Texture> move_down_icon_hl = get_icon("MoveDownHl", "EditorIcons");
- Ref<Texture> add_key_icon = get_icon("TrackAddKey", "EditorIcons");
- Ref<Texture> add_key_icon_hl = get_icon("TrackAddKeyHl", "EditorIcons");
- Ref<Texture> down_icon = get_icon("select_arrow", "Tree");
- Ref<Texture> checked = get_icon("checked", "Tree");
- Ref<Texture> unchecked = get_icon("unchecked", "Tree");
-
- Ref<Texture> wrap_icon[2] = {
- get_icon("InterpWrapClamp", "EditorIcons"),
- get_icon("InterpWrapLoop", "EditorIcons"),
- };
-
- Ref<Texture> interp_icon[3] = {
- get_icon("InterpRaw", "EditorIcons"),
- get_icon("InterpLinear", "EditorIcons"),
- get_icon("InterpCubic", "EditorIcons")
- };
- Ref<Texture> cont_icon[3] = {
- get_icon("TrackContinuous", "EditorIcons"),
- get_icon("TrackDiscrete", "EditorIcons"),
- get_icon("TrackTrigger", "EditorIcons")
- };
- Ref<Texture> type_icon[3] = {
- get_icon("KeyValue", "EditorIcons"),
- get_icon("KeyXform", "EditorIcons"),
- get_icon("KeyCall", "EditorIcons")
- };
-
- Ref<Texture> valid_icon = get_icon("KeyValid", "EditorIcons");
- Ref<Texture> invalid_icon = get_icon("KeyInvalid", "EditorIcons");
- const Color modulate_selected = Color(0x84 / 255.0, 0xc2 / 255.0, 0xff / 255.0);
-
- Ref<Texture> hsize_icon = get_icon("Hsize", "EditorIcons");
-
- int right_separator_ofs = right_data_size_cache;
-
- int h = font->get_height() + sep;
-
- int fit = (size.height / h) - 1;
- int total = animation->get_track_count();
- if (total < fit) {
- v_scroll->hide();
- v_scroll->set_max(total);
- v_scroll->set_page(fit);
- } else {
- v_scroll->show();
- v_scroll->set_max(total);
- v_scroll->set_page(fit);
- }
-
- int left_check_ofs = checked->get_width();
- int settings_limit = size.width - right_separator_ofs;
- int name_limit = settings_limit * name_column_ratio;
-
- Color linecolor = color;
- linecolor.a = 0.2;
- te->draw_line(ofs + Point2(name_limit, 0), ofs + Point2(name_limit, size.height), linecolor);
- te->draw_line(ofs + Point2(settings_limit, 0), ofs + Point2(settings_limit, size.height), linecolor);
- te->draw_texture(hsize_icon, ofs + Point2(name_limit - hsize_icon->get_width() - hsep, (h - hsize_icon->get_height()) / 2));
-
- te->draw_line(ofs + Point2(0, h), ofs + Point2(size.width, h), linecolor);
- // draw time
-
- float keys_from;
- float keys_to;
- float zoom_scale;
-
- {
-
- int zoomw = settings_limit - name_limit;
-
- float scale = _get_zoom_scale();
- zoom_scale = scale;
-
- float l = animation->get_length();
- if (l <= 0)
- l = 0.001; //avoid crashor
-
- int end_px = (l - h_scroll->get_value()) * scale;
- int begin_px = -h_scroll->get_value() * scale;
- Color notimecol = get_color("dark_color_2", "Editor");
-
- {
-
- te->draw_rect(Rect2(ofs + Point2(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;
-
- te->draw_rect(Rect2(ofs + Point2(name_limit + begin_px, 0), Point2(end_px - begin_px - 1, h)), timecolor);
- }
- }
-
- keys_from = h_scroll->get_value();
- keys_to = keys_from + zoomw / scale;
-
- {
- 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);
- if (end > time_max)
- time_max = end;
- }
- }
-
- float extra = (zoomw / scale) * 0.5;
-
- if (time_min < -0.001)
- time_min -= extra;
- time_max += extra;
- h_scroll->set_min(time_min);
- h_scroll->set_max(time_max);
-
- if (zoomw / scale < (time_max - time_min)) {
- h_scroll->show();
-
- } else {
-
- h_scroll->hide();
- }
- }
-
- h_scroll->set_page(zoomw / scale);
-
- 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('.').width;
- int max_digit_width = font->get_char_size('0').width;
- for (int i = 1; i <= 9; i++) {
- const int digit_width = font->get_char_size('0' + i).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;
-
- 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;
- }
-
- for (int i = 0; i < zoomw; i++) {
-
- float pos = h_scroll->get_value() + double(i) / scale;
- float prev = h_scroll->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;
- te->draw_line(ofs + Point2(name_limit + i, 0), ofs + Point2(name_limit + i, h), linecolor);
- te->draw_string(font, ofs + Point2(name_limit + i + 3, (h - font->get_height()) / 2 + font->get_ascent()).floor(), String::num((scd - (scd % step)) / double(SC_ADJ), decimals), sub ? color_time_dec : color_time_sec, zoomw - i);
- }
- }
- }
-
- color.a *= 0.5;
-
- for (int i = 0; i < fit; i++) {
-
- //this code sucks, i always forget how it works
-
- int idx = v_scroll->get_value() + i;
- if (idx >= animation->get_track_count())
- break;
- int y = h + i * h + sep;
-
- bool prop_exists = false;
- Variant::Type valid_type = Variant::NIL;
- Object *obj = NULL;
-
- RES res;
- Vector<StringName> leftover_path;
-
- Node *node = root ? root->get_node_and_resource(animation->track_get_path(idx), res, leftover_path) : (Node *)NULL;
-
- if (res.is_valid()) {
- obj = res.ptr();
- } else if (node) {
- obj = node;
- }
-
- if (obj && animation->track_get_type(idx) == Animation::TYPE_VALUE) {
- // While leftover_path might be still empty, we wouldn't be able to get here anyway
- valid_type = obj->get_static_property_type_indexed(leftover_path, &prop_exists);
- }
-
- // Draw background color of the whole track
- if (/*mouse_over.over!=MouseOver::OVER_NONE &&*/ idx == mouse_over.track) {
- Color sepc = hover_color;
- te->draw_rect(Rect2(ofs + Point2(0, y), Size2(size.width, h - 1)), sepc);
- }
-
- if (selected_track == idx) {
- Color tc = select_color;
- //tc.a*=0.7;
- te->draw_rect(Rect2(ofs + Point2(0, y), Size2(size.width - 1, h - 1)), tc);
- }
-
- // Draw track enabled state check box
- Ref<Texture> check_box = animation->track_is_enabled(idx) ? checked : unchecked;
- te->draw_texture(check_box, ofs + Point2(0, y + (h - checked->get_height()) / 2).floor());
-
- // Draw track type glyph and node path
- te->draw_texture(type_icon[animation->track_get_type(idx)], ofs + Point2(left_check_ofs + sep, y + (h - type_icon[0]->get_height()) / 2).floor());
- NodePath np = animation->track_get_path(idx);
- Node *n = root ? root->get_node(np) : (Node *)NULL;
- Color ncol = color;
- if (n && editor_selection->is_selected(n))
- ncol = track_select_color;
- te->draw_string(font, Point2(ofs + Point2(left_check_ofs + sep + type_icon[0]->get_width() + sep, y + font->get_ascent() + (sep / 2))).floor(), np, ncol, name_limit - (left_check_ofs + sep) - (type_icon[0]->get_width() + sep) - 5);
-
- // Draw separator line below track area
- if (!obj)
- te->draw_line(ofs + Point2(0, y + h / 2), ofs + Point2(name_limit, y + h / 2), invalid_path_color);
-
- te->draw_line(ofs + Point2(0, y + h), ofs + Point2(size.width, y + h), sepcolor);
-
- Point2 icon_ofs = ofs + Point2(size.width, y + (h - remove_icon->get_height()) / 2).floor();
- icon_ofs.y += 4 * EDSCALE;
-
- /* icon_ofs.x-=remove_icon->get_width();
-
- te->draw_texture((mouse_over.over==MouseOver::OVER_REMOVE && mouse_over.track==idx)?remove_icon_hl:remove_icon,icon_ofs);
- icon_ofs.x-=hsep;
- icon_ofs.x-=move_down_icon->get_width();
- te->draw_texture((mouse_over.over==MouseOver::OVER_DOWN && mouse_over.track==idx)?move_down_icon_hl:move_down_icon,icon_ofs);
- icon_ofs.x-=hsep;
- icon_ofs.x-=move_up_icon->get_width();
- te->draw_texture((mouse_over.over==MouseOver::OVER_UP && mouse_over.track==idx)?move_up_icon_hl:move_up_icon,icon_ofs);
- icon_ofs.x-=hsep;
- te->draw_line(Point2(icon_ofs.x,ofs.y+y),Point2(icon_ofs.x,ofs.y+y+h),sepcolor);
-
- icon_ofs.x-=hsep;
- */
- track_ofs[0] = size.width - icon_ofs.x + ofs.x;
- icon_ofs.x -= down_icon->get_width();
- te->draw_texture(down_icon, icon_ofs - Size2(0, 4 * EDSCALE));
-
- int wrap_type = animation->track_get_interpolation_loop_wrap(idx) ? 1 : 0;
- icon_ofs.x -= hsep;
- icon_ofs.x -= wrap_icon[wrap_type]->get_width();
- te->draw_texture(wrap_icon[wrap_type], icon_ofs);
-
- icon_ofs.x -= hsep;
- te->draw_line(Point2(icon_ofs.x, ofs.y + y), Point2(icon_ofs.x, ofs.y + y + h), sepcolor);
-
- track_ofs[1] = size.width - icon_ofs.x + ofs.x;
-
- icon_ofs.x -= down_icon->get_width();
- te->draw_texture(down_icon, icon_ofs - Size2(0, 4 * EDSCALE));
-
- int interp_type = animation->track_get_interpolation_type(idx);
- ERR_CONTINUE(interp_type < 0 || interp_type >= 3);
- icon_ofs.x -= hsep;
- icon_ofs.x -= interp_icon[interp_type]->get_width();
- te->draw_texture(interp_icon[interp_type], icon_ofs);
-
- icon_ofs.x -= hsep;
- te->draw_line(Point2(icon_ofs.x, ofs.y + y), Point2(icon_ofs.x, ofs.y + y + h), sepcolor);
-
- track_ofs[2] = size.width - icon_ofs.x + ofs.x;
-
- if (animation->track_get_type(idx) == Animation::TYPE_VALUE) {
-
- int umode = animation->value_track_get_update_mode(idx);
-
- icon_ofs.x -= hsep;
- icon_ofs.x -= down_icon->get_width();
- te->draw_texture(down_icon, icon_ofs - Size2(0, 4 * EDSCALE));
-
- icon_ofs.x -= hsep;
- icon_ofs.x -= cont_icon[umode]->get_width();
- te->draw_texture(cont_icon[umode], icon_ofs);
- } else {
-
- icon_ofs.x -= hsep * 2 + cont_icon[0]->get_width() + down_icon->get_width();
- }
-
- icon_ofs.x -= hsep;
- te->draw_line(Point2(icon_ofs.x, ofs.y + y), Point2(icon_ofs.x, ofs.y + y + h), sepcolor);
-
- track_ofs[3] = size.width - icon_ofs.x + ofs.x;
-
- icon_ofs.x -= hsep;
- icon_ofs.x -= add_key_icon->get_width();
- te->draw_texture((mouse_over.over == MouseOver::OVER_ADD_KEY && mouse_over.track == idx) ? add_key_icon_hl : add_key_icon, icon_ofs);
- track_ofs[4] = size.width - icon_ofs.x + ofs.x;
-
- //draw the keys;
- int tt = animation->track_get_type(idx);
- float key_vofs = Math::floor((float)(h - type_icon[tt]->get_height()) / 2);
- float key_hofs = -Math::floor((float)type_icon[tt]->get_height() / 2);
-
- int kc = animation->track_get_key_count(idx);
- bool first = true;
-
- for (int i = 0; i < kc; i++) {
-
- float time = animation->track_get_key_time(idx, i);
- if (time < keys_from)
- continue;
- if (time > keys_to) {
-
- if (first && i > 0 && animation->track_get_key_value(idx, i) == animation->track_get_key_value(idx, i - 1)) {
- //draw whole line
- te->draw_line(ofs + Vector2(name_limit, y + h / 2), ofs + Point2(settings_limit, y + h / 2), color);
- }
-
- break;
- }
-
- float x = key_hofs + name_limit + (time - keys_from) * zoom_scale;
-
- Ref<Texture> tex = type_icon[tt];
- Color modulate = Color(1, 1, 1);
-
- bool is_hover = false;
- bool is_selected = false;
-
- SelectedKey sk;
- sk.key = i;
- sk.track = idx;
- if (selection.has(sk)) {
-
- if (click.click == ClickOver::CLICK_MOVE_KEYS)
- continue;
- is_selected = true;
- }
-
- if (mouse_over.over == MouseOver::OVER_KEY && mouse_over.track == idx && mouse_over.over_key == i)
- is_hover = true;
-
- Variant value = animation->track_get_key_value(idx, i);
-
- if (prop_exists && !Variant::can_convert(value.get_type(), valid_type)) {
-
- tex = invalid_icon;
- if (is_hover)
- modulate = Color(1.5, 1.5, 1.5);
- else
- modulate = Color(1, 1, 1);
- } else if (is_selected) {
-
- tex = valid_icon;
- modulate = modulate_selected;
- if (is_hover)
- modulate = modulate.lightened(0.2);
- } else if (is_hover) {
-
- tex = valid_icon;
- modulate = Color(1, 1, 1);
- }
-
- if (first && i > 0 && value == animation->track_get_key_value(idx, i - 1)) {
-
- te->draw_line(ofs + Vector2(name_limit, y + h / 2), ofs + Point2(x, y + h / 2), color);
- }
-
- if (i < kc - 1 && value == animation->track_get_key_value(idx, i + 1)) {
- float x_n = key_hofs + name_limit + (animation->track_get_key_time(idx, i + 1) - keys_from) * zoom_scale;
-
- x_n = MIN(x_n, settings_limit);
- te->draw_line(ofs + Point2(x_n, y + h / 2), ofs + Point2(x, y + h / 2), color);
- }
-
- te->draw_texture(tex, ofs + Point2(x, y + key_vofs).floor(), modulate);
-
- first = false;
- }
- }
-
- switch (click.click) {
- case ClickOver::CLICK_SELECT_KEYS: {
-
- Color box_color = get_color("accent_color", "Editor");
- box_color.a = 0.35;
- te->draw_rect(Rect2(click.at, click.to - click.at), box_color);
-
- } break;
- case ClickOver::CLICK_MOVE_KEYS: {
-
- float from_t = 1e20;
-
- for (Map<SelectedKey, KeyInfo>::Element *E = selection.front(); E; E = E->next()) {
- float t = animation->track_get_key_time(E->key().track, E->key().key);
- if (t < from_t)
- from_t = t;
- }
-
- float motion = from_t + (click.to.x - click.at.x) / zoom_scale;
- if (step->get_value())
- motion = Math::stepify(motion, step->get_value());
-
- for (Map<SelectedKey, KeyInfo>::Element *E = selection.front(); E; E = E->next()) {
-
- int idx = E->key().track;
- int i = idx - (int)v_scroll->get_value();
- if (i < 0 || i >= fit)
- continue;
- int y = h + i * h + sep;
-
- float key_vofs = Math::floor((float)(h - valid_icon->get_height()) / 2);
- float key_hofs = -Math::floor((float)valid_icon->get_height() / 2);
-
- float time = animation->track_get_key_time(idx, E->key().key);
- float diff = time - from_t;
-
- float t = motion + diff;
-
- float x = (t - keys_from) * zoom_scale;
- //x+=click.to.x - click.at.x;
- if (x < 0 || x >= (settings_limit - name_limit))
- continue;
-
- x += name_limit;
-
- te->draw_texture(valid_icon, ofs + Point2(x + key_hofs, y + key_vofs).floor(), modulate_selected);
- }
- } break;
- default: {};
- }
-
- te_drawing = false;
-}
-
-void AnimationKeyEditor::_track_name_changed(const String &p_name) {
-
- ERR_FAIL_COND(!animation.is_valid());
- undo_redo->create_action(TTR("Anim Track Rename"));
- undo_redo->add_do_method(animation.ptr(), "track_set_path", track_name_editing, p_name);
- undo_redo->add_undo_method(animation.ptr(), "track_set_path", track_name_editing, animation->track_get_path(track_name_editing));
- undo_redo->commit_action();
- track_name->hide();
-}
-
-void AnimationKeyEditor::_track_menu_selected(int p_idx) {
-
- ERR_FAIL_COND(!animation.is_valid());
-
- if (interp_editing != -1) {
-
- ERR_FAIL_INDEX(interp_editing, animation->get_track_count());
- undo_redo->create_action(TTR("Anim Track Change Interpolation"));
- undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_type", interp_editing, p_idx);
- undo_redo->add_undo_method(animation.ptr(), "track_set_interpolation_type", interp_editing, animation->track_get_interpolation_type(interp_editing));
- undo_redo->commit_action();
- } else if (cont_editing != -1) {
-
- ERR_FAIL_INDEX(cont_editing, animation->get_track_count());
-
- undo_redo->create_action(TTR("Anim Track Change Value Mode"));
- undo_redo->add_do_method(animation.ptr(), "value_track_set_update_mode", cont_editing, p_idx);
- undo_redo->add_undo_method(animation.ptr(), "value_track_set_update_mode", cont_editing, animation->value_track_get_update_mode(cont_editing));
- undo_redo->commit_action();
- } else if (wrap_editing != -1) {
-
- ERR_FAIL_INDEX(wrap_editing, animation->get_track_count());
-
- undo_redo->create_action(TTR("Anim Track Change Wrap Mode"));
- undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_loop_wrap", wrap_editing, p_idx ? true : false);
- undo_redo->add_undo_method(animation.ptr(), "track_set_interpolation_loop_wrap", wrap_editing, animation->track_get_interpolation_loop_wrap(wrap_editing));
- undo_redo->commit_action();
- } else {
- switch (p_idx) {
-
- case RIGHT_MENU_DUPLICATE:
- _anim_duplicate_keys();
- break;
- case RIGHT_MENU_DUPLICATE_TRANSPOSE:
- _anim_duplicate_keys(true);
- break;
- case RIGHT_MENU_REMOVE:
- _anim_delete_keys();
- break;
- }
- }
-}
-
-struct _AnimMoveRestore {
-
- int track;
- float time;
- Variant key;
- float transition;
-};
-
-void AnimationKeyEditor::_clear_selection_for_anim(const Ref<Animation> &p_anim) {
-
- if (!(animation == p_anim))
- return;
- //selection.clear();
- _clear_selection();
-}
-
-void AnimationKeyEditor::_select_at_anim(const Ref<Animation> &p_anim, int p_track, float p_pos) {
-
- if (!(animation == p_anim))
- return;
-
- int idx = animation->track_find_key(p_track, p_pos, true);
- ERR_FAIL_COND(idx < 0);
-
- SelectedKey sk;
- sk.track = p_track;
- sk.key = idx;
- KeyInfo ki;
- ki.pos = p_pos;
-
- selection.insert(sk, ki);
-}
-
-PropertyInfo AnimationKeyEditor::_find_hint_for_track(int p_idx, NodePath &r_base_path) {
-
- r_base_path = NodePath();
- ERR_FAIL_COND_V(!animation.is_valid(), PropertyInfo());
- ERR_FAIL_INDEX_V(p_idx, animation->get_track_count(), PropertyInfo());
-
- if (!root)
- return PropertyInfo();
-
- NodePath path = animation->track_get_path(p_idx);
-
- if (!root->has_node_and_resource(path))
- return PropertyInfo();
-
- RES res;
- Vector<StringName> leftover_path;
- Node *node = root->get_node_and_resource(path, res, leftover_path, true);
-
- if (node) {
- r_base_path = node->get_path();
- }
-
- if (leftover_path.empty())
- return PropertyInfo();
-
- Variant property_info_base;
- if (res.is_valid())
- property_info_base = res;
- else if (node)
- property_info_base = node;
-
- for (int i = 0; i < leftover_path.size() - 1; i++) {
- property_info_base = property_info_base.get_named(leftover_path[i]);
- }
-
- List<PropertyInfo> pinfo;
- property_info_base.get_property_list(&pinfo);
-
- for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) {
-
- if (E->get().name == leftover_path[leftover_path.size() - 1]) {
- return E->get();
- }
- }
-
- return PropertyInfo();
-}
-
-void AnimationKeyEditor::_curve_transition_changed(float p_what) {
-
- if (selection.size() == 0)
- return;
- if (selection.size() == 1)
- undo_redo->create_action(TTR("Edit Node Curve"), UndoRedo::MERGE_ENDS);
- else
- undo_redo->create_action(TTR("Edit Selection Curve"), UndoRedo::MERGE_ENDS);
-
- for (Map<SelectedKey, KeyInfo>::Element *E = selection.front(); E; E = E->next()) {
-
- int track = E->key().track;
- int key = E->key().key;
- float prev_val = animation->track_get_key_transition(track, key);
- undo_redo->add_do_method(animation.ptr(), "track_set_key_transition", track, key, p_what);
- undo_redo->add_undo_method(animation.ptr(), "track_set_key_transition", track, key, prev_val);
- }
-
- undo_redo->commit_action();
-}
-
-void AnimationKeyEditor::_toggle_edit_curves() {
-
- if (edit_button->is_pressed())
- key_editor_tab->show();
- else
- key_editor_tab->hide();
-}
-
-bool AnimationKeyEditor::_edit_if_single_selection() {
-
- if (selection.size() != 1) {
-
- if (selection.size() == 0) {
- curve_edit->set_mode(AnimationCurveEdit::MODE_DISABLED);
- //print_line("disable");
- } else {
-
- curve_edit->set_mode(AnimationCurveEdit::MODE_MULTIPLE);
- curve_edit->set_transition(1.0);
- curve_edit->clear_multiples();
- //add all
- for (Map<SelectedKey, KeyInfo>::Element *E = selection.front(); E; E = E->next()) {
-
- curve_edit->set_multiple(animation->track_get_key_transition(E->key().track, E->key().key));
- }
- //print_line("multiple");
- }
- return false;
- }
- curve_edit->set_mode(AnimationCurveEdit::MODE_SINGLE);
- //print_line("regular");
-
- int idx = selection.front()->key().track;
- int key = selection.front()->key().key;
- {
-
- key_edit->animation = animation;
- key_edit->track = idx;
- key_edit->key_ofs = animation->track_get_key_time(idx, key);
- key_edit->hint = _find_hint_for_track(idx, key_edit->base);
- key_edit->notify_change();
-
- curve_edit->set_transition(animation->track_get_key_transition(idx, key));
-
- /*key_edit_dialog->set_size( Size2( 200,200) );
- key_edit_dialog->set_position( track_editor->get_global_position() + ofs + mpos +Point2(-100,20));
- key_edit_dialog->popup();*/
- }
-
- return true;
-}
-
-void AnimationKeyEditor::_anim_delete_keys() {
- if (selection.size()) {
- undo_redo->create_action(TTR("Anim Delete Keys"));
-
- for (Map<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));
- }
- undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);
- undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
- undo_redo->commit_action();
- //selection.clear();
- accept_event();
- _edit_if_single_selection();
- }
-}
-
-void AnimationKeyEditor::_track_editor_gui_input(const Ref<InputEvent> &p_input) {
-
- Control *te = track_editor;
- Ref<StyleBox> style = get_stylebox("normal", "TextEdit");
-
- if (!animation.is_valid()) {
- return;
- }
-
- Size2 size = te->get_size() - style->get_minimum_size();
- Size2 ofs = style->get_offset();
-
- Ref<Font> font = te->get_font("font", "Tree");
- int sep = get_constant("vseparation", "Tree");
- int hsep = get_constant("hseparation", "Tree");
- Ref<Texture> remove_icon = get_icon("Remove", "EditorIcons");
- Ref<Texture> move_up_icon = get_icon("MoveUp", "EditorIcons");
- Ref<Texture> move_down_icon = get_icon("MoveDown", "EditorIcons");
- Ref<Texture> down_icon = get_icon("select_arrow", "Tree");
- Ref<Texture> hsize_icon = get_icon("Hsize", "EditorIcons");
- Ref<Texture> add_key_icon = get_icon("TrackAddKey", "EditorIcons");
- Ref<Texture> check_icon = get_icon("checked", "Tree");
-
- Ref<Texture> wrap_icon[2] = {
- get_icon("InterpWrapClamp", "EditorIcons"),
- get_icon("InterpWrapLoop", "EditorIcons"),
- };
- Ref<Texture> interp_icon[3] = {
- get_icon("InterpRaw", "EditorIcons"),
- get_icon("InterpLinear", "EditorIcons"),
- get_icon("InterpCubic", "EditorIcons")
- };
- Ref<Texture> cont_icon[3] = {
- get_icon("TrackContinuous", "EditorIcons"),
- get_icon("TrackDiscrete", "EditorIcons"),
- get_icon("TrackTrigger", "EditorIcons")
- };
- Ref<Texture> type_icon[3] = {
- get_icon("KeyValue", "EditorIcons"),
- get_icon("KeyXform", "EditorIcons"),
- get_icon("KeyCall", "EditorIcons")
- };
- int right_separator_ofs = right_data_size_cache;
-
- int h = font->get_height() + sep;
-
- int fit = (size.height / h) - 1;
- int total = animation->get_track_count();
- if (total < fit) {
- v_scroll->hide();
- } else {
- v_scroll->show();
- v_scroll->set_max(total);
- v_scroll->set_page(fit);
- }
-
- int left_check_ofs = check_icon->get_width();
- int settings_limit = size.width - right_separator_ofs;
- int name_limit = settings_limit * name_column_ratio;
-
- Ref<InputEventKey> key = p_input;
- if (key.is_valid()) {
-
- if (key->get_scancode() == KEY_D && key->is_pressed() && key->get_command()) {
-
- if (key->get_shift())
- _menu_track(TRACK_MENU_DUPLICATE_TRANSPOSE);
- else
- _menu_track(TRACK_MENU_DUPLICATE);
-
- accept_event();
-
- } else if (key->get_scancode() == KEY_DELETE && key->is_pressed() && click.click == ClickOver::CLICK_NONE) {
-
- _anim_delete_keys();
- } else if (animation.is_valid() && animation->get_track_count() > 0) {
-
- if (key->is_pressed() && (key->is_action("ui_up") || key->is_action("ui_page_up"))) {
-
- if (key->is_action("ui_up"))
- selected_track--;
- if (v_scroll->is_visible_in_tree() && key->is_action("ui_page_up"))
- selected_track--;
-
- if (selected_track < 0)
- selected_track = 0;
-
- if (v_scroll->is_visible_in_tree()) {
- if (v_scroll->get_value() > selected_track)
- v_scroll->set_value(selected_track);
- }
-
- track_editor->update();
- accept_event();
- }
-
- if (key->is_pressed() && (key->is_action("ui_down") || key->is_action("ui_page_down"))) {
-
- if (key->is_action("ui_down"))
- selected_track++;
- else if (v_scroll->is_visible_in_tree() && key->is_action("ui_page_down"))
- selected_track += v_scroll->get_page();
-
- if (selected_track >= animation->get_track_count())
- selected_track = animation->get_track_count() - 1;
-
- if (v_scroll->is_visible_in_tree() && v_scroll->get_page() + v_scroll->get_value() < selected_track + 1) {
- v_scroll->set_value(selected_track - v_scroll->get_page() + 1);
- }
-
- track_editor->update();
- accept_event();
- }
- }
- }
-
- Ref<InputEventMouseButton> mb = p_input;
-
- if (mb.is_valid()) {
-
- if (mb->get_button_index() == BUTTON_WHEEL_UP && mb->is_pressed()) {
-
- if (mb->get_command()) {
-
- zoom->set_value(zoom->get_value() + zoom->get_step());
- } else {
-
- v_scroll->set_value(v_scroll->get_value() - v_scroll->get_page() * mb->get_factor() / 8);
- }
- }
-
- if (mb->get_button_index() == BUTTON_WHEEL_DOWN && mb->is_pressed()) {
-
- if (mb->get_command()) {
-
- zoom->set_value(zoom->get_value() - zoom->get_step());
- } else {
-
- v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() * mb->get_factor() / 8);
- }
- }
-
- if (mb->get_button_index() == BUTTON_WHEEL_RIGHT && mb->is_pressed()) {
-
- h_scroll->set_value(h_scroll->get_value() - h_scroll->get_page() * mb->get_factor() / 8);
- }
-
- if (mb->get_button_index() == BUTTON_WHEEL_LEFT && mb->is_pressed()) {
-
- v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() * mb->get_factor() / 8);
- }
-
- if (mb->get_button_index() == BUTTON_RIGHT && mb->is_pressed()) {
-
- Point2 mpos = mb->get_position() - ofs;
-
- if (selection.size() == 0) {
- // Auto-select on right-click if nothing is selected
- // Note: This code is pretty much duplicated from the left click code,
- // both codes could be moved into a function to avoid the duplicated code.
- Point2 mpos = mb->get_position() - ofs;
-
- if (mpos.y < h) {
- return;
- }
-
- mpos.y -= h;
-
- int idx = mpos.y / h;
- idx += v_scroll->get_value();
- if (idx < 0 || idx >= animation->get_track_count())
- return;
-
- if (mpos.x < name_limit) {
- } else if (mpos.x < settings_limit) {
- float pos = mpos.x - name_limit;
- pos /= _get_zoom_scale();
- pos += h_scroll->get_value();
- float w_time = (type_icon[0]->get_width() / _get_zoom_scale()) / 2.0;
-
- int kidx = animation->track_find_key(idx, pos);
- int kidx_n = kidx + 1;
- int key = -1;
-
- if (kidx >= 0 && kidx < animation->track_get_key_count(idx)) {
-
- float kpos = animation->track_get_key_time(idx, kidx);
- if (ABS(pos - kpos) <= w_time) {
-
- key = kidx;
- }
- }
-
- if (key == -1 && kidx_n >= 0 && kidx_n < animation->track_get_key_count(idx)) {
-
- float kpos = animation->track_get_key_time(idx, kidx_n);
- if (ABS(pos - kpos) <= w_time) {
-
- key = kidx_n;
- }
- }
-
- if (key == -1) {
-
- click.click = ClickOver::CLICK_SELECT_KEYS;
- click.at = mb->get_position();
- click.to = click.at;
- click.shift = mb->get_shift();
- selected_track = idx;
- track_editor->update();
- //drag select region
- return;
- }
-
- SelectedKey sk;
- sk.track = idx;
- sk.key = key;
- KeyInfo ki;
- ki.pos = animation->track_get_key_time(idx, key);
- click.shift = mb->get_shift();
- click.selk = sk;
-
- if (!mb->get_shift() && !selection.has(sk))
- _clear_selection();
-
- selection.insert(sk, ki);
-
- click.click = ClickOver::CLICK_MOVE_KEYS;
- click.at = mb->get_position();
- click.to = click.at;
- update();
- selected_track = idx;
- track_editor->update();
-
- if (_edit_if_single_selection() && mb->get_command()) {
- edit_button->set_pressed(true);
- key_editor_tab->show();
- }
- }
- }
-
- if (selection.size()) {
- // User has right clicked and we have a selection, show a popup menu with options
- track_menu->clear();
- track_menu->set_size(Point2(1, 1));
- track_menu->add_item(TTR("Duplicate Selection"), RIGHT_MENU_DUPLICATE);
- track_menu->add_item(TTR("Duplicate Transposed"), RIGHT_MENU_DUPLICATE_TRANSPOSE);
- track_menu->add_item(TTR("Remove Selection"), RIGHT_MENU_REMOVE);
-
- track_menu->set_position(te->get_global_position() + mpos);
-
- interp_editing = -1;
- cont_editing = -1;
- wrap_editing = -1;
-
- track_menu->popup();
- }
- }
-
- if (mb->get_button_index() == BUTTON_LEFT && !(mb->get_button_mask() & ~BUTTON_MASK_LEFT)) {
-
- if (mb->is_pressed()) {
-
- Point2 mpos = mb->get_position() - ofs;
-
- if (mpos.y < h) {
-
- if (mpos.x < name_limit && mpos.x > (name_limit - hsep - hsize_icon->get_width())) {
-
- click.click = ClickOver::CLICK_RESIZE_NAMES;
- click.at = mb->get_position();
- click.to = click.at;
- click.at.y = name_limit;
- }
-
- if (mpos.x >= name_limit && mpos.x < settings_limit) {
- //seek
- //int zoomw = settings_limit-name_limit;
- float scale = _get_zoom_scale();
- float pos = h_scroll->get_value() + (mpos.x - name_limit) / scale;
- if (animation->get_step())
- pos = Math::stepify(pos, animation->get_step());
-
- if (pos < 0)
- pos = 0;
- if (pos >= animation->get_length())
- pos = animation->get_length();
- timeline_pos = pos;
- click.click = ClickOver::CLICK_DRAG_TIMELINE;
- click.at = mb->get_position();
- click.to = click.at;
- emit_signal("timeline_changed", pos, false);
- }
-
- return;
- }
-
- mpos.y -= h;
-
- int idx = mpos.y / h;
- idx += v_scroll->get_value();
- if (idx < 0)
- return;
-
- if (idx >= animation->get_track_count()) {
-
- if (mpos.x >= name_limit && mpos.x < settings_limit) {
-
- click.click = ClickOver::CLICK_SELECT_KEYS;
- click.at = mb->get_position();
- click.to = click.at;
- //drag select region
- }
-
- return;
- }
-
- if (mpos.x < left_check_ofs) {
- // Checkbox on the very left to enable/disable tracks.
-
- animation->track_set_enabled(idx, !animation->track_is_enabled(idx));
-
- } else if (mpos.x < name_limit - (type_icon[0]->get_width() / 2.0)) {
- //name column
-
- // area
- if (idx != selected_track) {
-
- selected_track = idx;
- track_editor->update();
- return;
- }
-
- Rect2 area(ofs.x + left_check_ofs + sep, ofs.y + ((int(mpos.y) / h) + 1) * h, name_limit - left_check_ofs - sep, h);
- track_name->set_text(animation->track_get_path(idx));
- track_name->set_position(te->get_global_position() + area.position);
- track_name->set_size(area.size);
- track_name->show_modal();
- track_name->grab_focus();
- track_name->select_all();
- track_name_editing = idx;
-
- } else if (mpos.x < settings_limit) {
-
- float pos = mpos.x - name_limit;
- pos /= _get_zoom_scale();
- pos += h_scroll->get_value();
- float w_time = (type_icon[0]->get_width() / _get_zoom_scale()) / 2.0;
-
- int kidx = animation->track_find_key(idx, pos);
- int kidx_n = kidx + 1;
- int key = -1;
-
- if (kidx >= 0 && kidx < animation->track_get_key_count(idx)) {
-
- float kpos = animation->track_get_key_time(idx, kidx);
- if (ABS(pos - kpos) <= w_time) {
-
- key = kidx;
- }
- }
-
- if (key == -1 && kidx_n >= 0 && kidx_n < animation->track_get_key_count(idx)) {
-
- float kpos = animation->track_get_key_time(idx, kidx_n);
- if (ABS(pos - kpos) <= w_time) {
-
- key = kidx_n;
- }
- }
-
- if (key == -1) {
-
- click.click = ClickOver::CLICK_SELECT_KEYS;
- click.at = mb->get_position();
- click.to = click.at;
- click.shift = mb->get_shift();
- selected_track = idx;
- track_editor->update();
- //drag select region
- return;
- }
-
- SelectedKey sk;
- sk.track = idx;
- sk.key = key;
- KeyInfo ki;
- ki.pos = animation->track_get_key_time(idx, key);
- click.shift = mb->get_shift();
- click.selk = sk;
-
- if (!mb->get_shift() && !selection.has(sk))
- _clear_selection();
-
- selection.insert(sk, ki);
-
- click.click = ClickOver::CLICK_MOVE_KEYS;
- click.at = mb->get_position();
- click.to = click.at;
- update();
- selected_track = idx;
- track_editor->update();
-
- if (_edit_if_single_selection() && mb->get_command()) {
- edit_button->set_pressed(true);
- key_editor_tab->show();
- }
- } else {
- //button column
- int ofsx = size.width - mpos.x;
- if (ofsx < 0)
- return;
- /*
- if (ofsx < remove_icon->get_width()) {
-
- undo_redo->create_action("Remove Anim Track");
- 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));
- //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);
-
- 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(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_continuous",idx,animation->value_track_is_continuous(idx));
-
- }
-
- undo_redo->commit_action();
-
-
- return;
- }
-
- ofsx-=hsep+remove_icon->get_width();
-
- if (ofsx < move_down_icon->get_width()) {
-
- if (idx < animation->get_track_count() -1) {
- undo_redo->create_action("Move Anim Track Down");
- undo_redo->add_do_method(animation.ptr(),"track_move_up",idx);
- undo_redo->add_undo_method(animation.ptr(),"track_move_down",idx+1);
- undo_redo->commit_action();
- }
- return;
- }
-
- ofsx-=hsep+move_down_icon->get_width();
-
- if (ofsx < move_up_icon->get_width()) {
-
- if (idx >0) {
- undo_redo->create_action("Move Anim Track Up");
- undo_redo->add_do_method(animation.ptr(),"track_move_down",idx);
- undo_redo->add_undo_method(animation.ptr(),"track_move_up",idx-1);
- undo_redo->commit_action();
- }
- return;
- }
-
-
- ofsx-=hsep*3+move_up_icon->get_width();
- */
-
- if (ofsx < track_ofs[1]) {
-
- track_menu->clear();
- track_menu->set_size(Point2(1, 1));
- static const char *interp_name[2] = { "Clamp Loop Interp", "Wrap Loop Interp" };
- for (int i = 0; i < 2; i++) {
- track_menu->add_icon_item(wrap_icon[i], interp_name[i]);
- }
-
- int popup_y = ofs.y + ((int(mpos.y) / h) + 2) * h;
- int popup_x = size.width - track_ofs[1];
-
- track_menu->set_position(te->get_global_position() + Point2(popup_x, popup_y));
-
- wrap_editing = idx;
- interp_editing = -1;
- cont_editing = -1;
-
- track_menu->popup();
-
- return;
- }
-
- if (ofsx < track_ofs[2]) {
-
- track_menu->clear();
- track_menu->set_size(Point2(1, 1));
- static const char *interp_name[3] = { "Nearest", "Linear", "Cubic" };
- for (int i = 0; i < 3; i++) {
- track_menu->add_icon_item(interp_icon[i], interp_name[i]);
- }
-
- int popup_y = ofs.y + ((int(mpos.y) / h) + 2) * h;
- int popup_x = size.width - track_ofs[2];
-
- track_menu->set_position(te->get_global_position() + Point2(popup_x, popup_y));
-
- interp_editing = idx;
- cont_editing = -1;
- wrap_editing = -1;
-
- track_menu->popup();
-
- return;
- }
-
- if (ofsx < track_ofs[3]) {
-
- track_menu->clear();
- track_menu->set_size(Point2(1, 1));
- String cont_name[3] = { TTR("Continuous"), TTR("Discrete"), TTR("Trigger") };
- for (int i = 0; i < 3; i++) {
- track_menu->add_icon_item(cont_icon[i], cont_name[i]);
- }
-
- int popup_y = ofs.y + ((int(mpos.y) / h) + 2) * h;
- int popup_x = size.width - track_ofs[3];
-
- track_menu->set_position(te->get_global_position() + Point2(popup_x, popup_y));
-
- interp_editing = -1;
- wrap_editing = -1;
- cont_editing = idx;
-
- track_menu->popup();
-
- return;
- }
-
- if (ofsx < track_ofs[4]) {
-
- Animation::TrackType tt = animation->track_get_type(idx);
-
- float pos = timeline_pos;
- int existing = animation->track_find_key(idx, pos, true);
-
- Variant newval;
-
- if (tt == Animation::TYPE_TRANSFORM) {
- Dictionary d;
- d["location"] = Vector3();
- d["rotation"] = Quat();
- d["scale"] = Vector3();
- newval = d;
-
- } else if (tt == Animation::TYPE_METHOD) {
-
- Dictionary d;
- d["method"] = "";
- d["args"] = Vector<Variant>();
-
- newval = d;
- } else if (tt == Animation::TYPE_VALUE) {
-
- NodePath np;
- PropertyInfo inf = _find_hint_for_track(idx, np);
- if (inf.type != Variant::NIL) {
-
- Variant::CallError err;
- newval = Variant::construct(inf.type, NULL, 0, err);
- }
-
- if (newval.get_type() == Variant::NIL) {
- //popup a new type
- cvi_track = idx;
- cvi_pos = pos;
-
- type_menu->set_position(get_global_position() + mpos + ofs);
- type_menu->popup();
- return;
- }
- }
-
- undo_redo->create_action(TTR("Anim Add Key"));
-
- undo_redo->add_do_method(animation.ptr(), "track_insert_key", idx, pos, newval, 1);
- undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", idx, pos);
-
- if (existing != -1) {
- Variant v = animation->track_get_key_value(idx, existing);
- float trans = animation->track_get_key_transition(idx, existing);
- undo_redo->add_undo_method(animation.ptr(), "track_insert_key", idx, pos, v, trans);
- }
-
- undo_redo->commit_action();
-
- return;
- }
- }
-
- } else {
-
- switch (click.click) {
- case ClickOver::CLICK_SELECT_KEYS: {
-
- float zoom_scale = _get_zoom_scale();
- float keys_from = h_scroll->get_value();
- float keys_to = keys_from + (settings_limit - name_limit) / zoom_scale;
-
- float from_time = keys_from + (click.at.x - (name_limit + ofs.x)) / zoom_scale;
- float to_time = keys_from + (click.to.x - (name_limit + ofs.x)) / zoom_scale;
-
- if (to_time < from_time)
- SWAP(from_time, to_time);
-
- if (from_time > keys_to || to_time < keys_from)
- break;
-
- if (from_time < keys_from)
- from_time = keys_from;
-
- if (to_time >= keys_to)
- to_time = keys_to;
-
- int from_track = int(click.at.y - ofs.y - h - sep) / h + v_scroll->get_value();
- int to_track = int(click.to.y - ofs.y - h - sep) / h + v_scroll->get_value();
- int from_mod = int(click.at.y - ofs.y - sep) % h;
- int to_mod = int(click.to.y - ofs.y - sep) % h;
-
- if (to_track < from_track) {
-
- SWAP(from_track, to_track);
- SWAP(from_mod, to_mod);
- }
-
- if ((from_mod > (h / 2)) && ((click.at.y - ofs.y) >= (h + sep))) {
- from_track++;
- }
-
- if (to_mod < h / 2) {
- to_track--;
- }
-
- if (from_track > to_track) {
- if (!click.shift)
- _clear_selection();
- _edit_if_single_selection();
- break;
- }
-
- int tracks_from = v_scroll->get_value();
- int tracks_to = v_scroll->get_value() + fit - 1;
- if (tracks_to >= animation->get_track_count())
- tracks_to = animation->get_track_count() - 1;
-
- tracks_from = 0;
- tracks_to = animation->get_track_count() - 1;
- if (to_track > tracks_to)
- to_track = tracks_to;
- if (from_track < tracks_from)
- from_track = tracks_from;
-
- if (from_track > tracks_to || to_track < tracks_from) {
- if (!click.shift)
- _clear_selection();
- _edit_if_single_selection();
- break;
- }
-
- if (!click.shift)
- _clear_selection();
-
- int higher_track = 0x7FFFFFFF;
- for (int i = from_track; i <= to_track; i++) {
-
- int kc = animation->track_get_key_count(i);
- for (int j = 0; j < kc; j++) {
-
- float t = animation->track_get_key_time(i, j);
- if (t < from_time)
- continue;
- if (t > to_time)
- break;
-
- if (i < higher_track)
- higher_track = i;
-
- SelectedKey sk;
- sk.track = i;
- sk.key = j;
- KeyInfo ki;
- ki.pos = t;
- selection[sk] = ki;
- }
- }
-
- if (higher_track != 0x7FFFFFFF) {
- selected_track = higher_track;
- track_editor->update();
- }
-
- _edit_if_single_selection();
-
- } break;
- case ClickOver::CLICK_MOVE_KEYS: {
-
- if (selection.empty())
- break;
- if (click.at == click.to) {
-
- if (!click.shift) {
-
- KeyInfo ki = selection[click.selk];
- _clear_selection();
- selection[click.selk] = ki;
- _edit_if_single_selection();
- }
-
- break;
- }
-
- float from_t = 1e20;
-
- for (Map<SelectedKey, KeyInfo>::Element *E = selection.front(); E; E = E->next()) {
- float t = animation->track_get_key_time(E->key().track, E->key().key);
- if (t < from_t)
- from_t = t;
- }
-
- float motion = from_t + (click.to.x - click.at.x) / _get_zoom_scale();
- if (step->get_value())
- motion = Math::stepify(motion, step->get_value());
-
- undo_redo->create_action(TTR("Anim Move Keys"));
-
- List<_AnimMoveRestore> to_restore;
-
- // 1-remove the keys
- for (Map<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()) {
-
- float newtime = E->get().pos - from_t + motion;
- int idx = animation->track_find_key(E->key().track, newtime, true);
- if (idx == -1)
- continue;
- SelectedKey sk;
- sk.key = idx;
- sk.track = E->key().track;
- if (selection.has(sk))
- continue; //already in selection, don't save
-
- undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_position", E->key().track, newtime);
- _AnimMoveRestore amr;
-
- amr.key = animation->track_get_key_value(E->key().track, idx);
- amr.track = E->key().track;
- amr.time = newtime;
- amr.transition = animation->track_get_key_transition(E->key().track, idx);
-
- to_restore.push_back(amr);
- }
-
- // 3-move the keys (re insert them)
- for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
-
- float newpos = E->get().pos - from_t + motion;
- /*
- if (newpos<0)
- continue; //no add at the beginning
- */
- 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()) {
-
- float newpos = E->get().pos + -from_t + motion;
- /*
- if (newpos<0)
- continue; //no remove what no inserted
- */
- undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", E->key().track, newpos);
- }
-
- // 5-(undo) reinsert keys
- for (Map<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));
- }
-
- // 6-(undo) reinsert overlapped keys
- for (List<_AnimMoveRestore>::Element *E = to_restore.front(); E; E = E->next()) {
-
- _AnimMoveRestore &amr = E->get();
- undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, amr.transition);
- }
-
- // 6-(undo) reinsert overlapped keys
- for (List<_AnimMoveRestore>::Element *E = to_restore.front(); E; E = E->next()) {
-
- _AnimMoveRestore &amr = E->get();
- undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, amr.transition);
- }
-
- undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);
- 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()) {
-
- float oldpos = E->get().pos;
- float newpos = oldpos - from_t + motion;
- //if (newpos>=0)
- undo_redo->add_do_method(this, "_select_at_anim", animation, E->key().track, newpos);
- undo_redo->add_undo_method(this, "_select_at_anim", animation, E->key().track, oldpos);
- }
-
- undo_redo->commit_action();
- _edit_if_single_selection();
-
- } break;
- default: {}
- }
-
- //button released
- click.click = ClickOver::CLICK_NONE;
- track_editor->update();
- }
- }
- }
-
- Ref<InputEventMouseMotion> mm = p_input;
-
- if (mm.is_valid()) {
-
- mouse_over.over = MouseOver::OVER_NONE;
- mouse_over.track = -1;
- te->update();
- track_editor->set_tooltip("");
-
- if (!track_editor->has_focus() && (!get_focus_owner() || !get_focus_owner()->is_text_field()))
- track_editor->call_deferred("grab_focus");
-
- if (click.click != ClickOver::CLICK_NONE) {
-
- switch (click.click) {
- case ClickOver::CLICK_RESIZE_NAMES: {
-
- float base = click.at.y;
- float clickp = click.at.x - ofs.x;
- float dif = base - clickp;
-
- float target = mm->get_position().x + dif - ofs.x;
-
- float ratio = target / settings_limit;
-
- if (ratio > 0.9)
- ratio = 0.9;
- else if (ratio < 0.2)
- ratio = 0.2;
-
- name_column_ratio = ratio;
-
- } break;
- case ClickOver::CLICK_DRAG_TIMELINE: {
-
- Point2 mpos = mm->get_position() - ofs;
- /*
- if (mpos.x<name_limit)
- mpos.x=name_limit;
- if (mpos.x>settings_limit)
- mpos.x=settings_limit;
- */
-
- //int zoomw = settings_limit-name_limit;
- float scale = _get_zoom_scale();
- float pos = h_scroll->get_value() + (mpos.x - name_limit) / scale;
- if (animation->get_step()) {
- pos = Math::stepify(pos, animation->get_step());
- }
- if (pos < 0)
- pos = 0;
- if (pos >= animation->get_length())
- pos = animation->get_length();
-
- if (pos < h_scroll->get_value()) {
- h_scroll->set_value(pos);
- } else if (pos > h_scroll->get_value() + (settings_limit - name_limit) / scale) {
- h_scroll->set_value(pos - (settings_limit - name_limit) / scale);
- }
-
- timeline_pos = pos;
- emit_signal("timeline_changed", pos, true);
-
- } break;
- case ClickOver::CLICK_SELECT_KEYS: {
-
- click.to = mm->get_position();
- if (click.to.y < h && click.at.y > h && mm->get_relative().y < 0) {
-
- float prev = v_scroll->get_value();
- v_scroll->set_value(v_scroll->get_value() - 1);
- if (prev != v_scroll->get_value())
- click.at.y += h;
- }
- if (click.to.y > size.height && click.at.y < size.height && mm->get_relative().y > 0) {
-
- float prev = v_scroll->get_value();
- v_scroll->set_value(v_scroll->get_value() + 1);
- if (prev != v_scroll->get_value())
- click.at.y -= h;
- }
-
- } break;
- case ClickOver::CLICK_MOVE_KEYS: {
-
- click.to = mm->get_position();
- } break;
- default: {}
- }
-
- return;
- } else if (mm->get_button_mask() & BUTTON_MASK_MIDDLE) {
-
- int rel = mm->get_relative().x;
- float relf = rel / _get_zoom_scale();
- h_scroll->set_value(h_scroll->get_value() - relf);
- }
-
- if (mm->get_button_mask() == 0) {
-
- Point2 mpos = mm->get_position() - ofs;
-
- if (mpos.y < h) {
- return;
- }
-
- mpos.y -= h;
-
- int idx = mpos.y / h;
- idx += v_scroll->get_value();
- if (idx < 0 || idx >= animation->get_track_count())
- return;
-
- mouse_over.track = idx;
-
- if (mpos.x < name_limit) {
- //name column
-
- mouse_over.over = MouseOver::OVER_NAME;
-
- } else if (mpos.x < settings_limit) {
-
- float pos = mpos.x - name_limit;
- pos /= _get_zoom_scale();
- pos += h_scroll->get_value();
- float w_time = (type_icon[0]->get_width() / _get_zoom_scale()) / 2.0;
-
- int kidx = animation->track_find_key(idx, pos);
- int kidx_n = kidx + 1;
-
- bool found = false;
-
- if (kidx >= 0 && kidx < animation->track_get_key_count(idx)) {
-
- float kpos = animation->track_get_key_time(idx, kidx);
- if (ABS(pos - kpos) <= w_time) {
-
- mouse_over.over = MouseOver::OVER_KEY;
- mouse_over.track = idx;
- mouse_over.over_key = kidx;
- found = true;
- }
- }
-
- if (!found && kidx_n >= 0 && kidx_n < animation->track_get_key_count(idx)) {
-
- float kpos = animation->track_get_key_time(idx, kidx_n);
- if (ABS(pos - kpos) <= w_time) {
-
- mouse_over.over = MouseOver::OVER_KEY;
- mouse_over.track = idx;
- mouse_over.over_key = kidx_n;
- found = true;
- }
- }
-
- if (found) {
-
- String text;
- text = "time: " + rtos(animation->track_get_key_time(idx, mouse_over.over_key)) + "\n";
-
- switch (animation->track_get_type(idx)) {
-
- case Animation::TYPE_TRANSFORM: {
-
- Dictionary d = animation->track_get_key_value(idx, mouse_over.over_key);
- if (d.has("location"))
- text += "location: " + String(d["location"]) + "\n";
- if (d.has("rotation"))
- text += "rot: " + String(d["rotation"]) + "\n";
- if (d.has("scale"))
- text += "scale: " + String(d["scale"]) + "\n";
- } break;
- case Animation::TYPE_VALUE: {
-
- Variant v = animation->track_get_key_value(idx, mouse_over.over_key);
- //text+="value: "+String(v)+"\n";
-
- bool prop_exists = false;
- Variant::Type valid_type = Variant::NIL;
- Object *obj = NULL;
-
- RES res;
- Vector<StringName> leftover_path;
- Node *node = root->get_node_and_resource(animation->track_get_path(idx), res, leftover_path);
-
- if (res.is_valid()) {
- obj = res.ptr();
- } else if (node) {
- obj = node;
- }
-
- if (obj) {
- valid_type = obj->get_static_property_type_indexed(leftover_path, &prop_exists);
- }
-
- text += "type: " + Variant::get_type_name(v.get_type()) + "\n";
- if (prop_exists && !Variant::can_convert(v.get_type(), valid_type)) {
- text += "value: " + String(v) + " (Invalid, expected type: " + Variant::get_type_name(valid_type) + ")\n";
- } else {
- text += "value: " + String(v) + "\n";
- }
-
- } break;
- case Animation::TYPE_METHOD: {
-
- Dictionary d = animation->track_get_key_value(idx, mouse_over.over_key);
- if (d.has("method"))
- text += String(d["method"]);
- text += "(";
- Vector<Variant> args;
- if (d.has("args"))
- args = d["args"];
- for (int i = 0; i < args.size(); i++) {
-
- if (i > 0)
- text += ", ";
- text += String(args[i]);
- }
- text += ")\n";
-
- } break;
- }
- text += "easing: " + rtos(animation->track_get_key_transition(idx, mouse_over.over_key));
-
- track_editor->set_tooltip(text);
- return;
- }
-
- } else {
- //button column
- int ofsx = size.width - mpos.x;
- if (ofsx < 0)
- return;
- /*
- if (ofsx < remove_icon->get_width()) {
-
- mouse_over.over=MouseOver::OVER_REMOVE;
-
- return;
- }
-
- ofsx-=hsep+remove_icon->get_width();
-
- if (ofsx < move_down_icon->get_width()) {
-
- mouse_over.over=MouseOver::OVER_DOWN;
- return;
- }
-
- ofsx-=hsep+move_down_icon->get_width();
-
- if (ofsx < move_up_icon->get_width()) {
-
- mouse_over.over=MouseOver::OVER_UP;
- return;
- }
-
- ofsx-=hsep*3+move_up_icon->get_width();
-
-*/
-
- if (ofsx < down_icon->get_width() + wrap_icon[0]->get_width() + hsep * 3) {
-
- mouse_over.over = MouseOver::OVER_WRAP;
- return;
- }
-
- ofsx -= hsep * 3 + wrap_icon[0]->get_width() + down_icon->get_width();
-
- if (ofsx < down_icon->get_width() + interp_icon[0]->get_width() + hsep * 3) {
-
- mouse_over.over = MouseOver::OVER_INTERP;
- return;
- }
-
- ofsx -= hsep * 2 + interp_icon[0]->get_width() + down_icon->get_width();
-
- if (ofsx < down_icon->get_width() + cont_icon[0]->get_width() + hsep * 3) {
-
- mouse_over.over = MouseOver::OVER_VALUE;
- return;
- }
-
- ofsx -= hsep * 3 + cont_icon[0]->get_width() + down_icon->get_width();
-
- if (ofsx < add_key_icon->get_width()) {
-
- mouse_over.over = MouseOver::OVER_ADD_KEY;
- return;
- }
- }
- }
- }
-
- Ref<InputEventMagnifyGesture> magnify_gesture = p_input;
- if (magnify_gesture.is_valid()) {
- zoom->set_value(zoom->get_value() * magnify_gesture->get_factor());
- }
-
- Ref<InputEventPanGesture> pan_gesture = p_input;
- if (pan_gesture.is_valid()) {
-
- h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() * pan_gesture->get_delta().x / 8);
- v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() * pan_gesture->get_delta().y / 8);
- }
-}
-
-void AnimationKeyEditor::_notification(int p_what) {
-
- switch (p_what) {
- case NOTIFICATION_VISIBILITY_CHANGED: {
-
- update_keying();
- EditorNode::get_singleton()->update_keying();
- emit_signal("keying_changed");
- } break;
-
- case NOTIFICATION_ENTER_TREE: {
-
- key_editor->edit(key_edit);
-
- zoomicon->set_custom_minimum_size(Size2(24 * EDSCALE, 0));
- zoomicon->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED);
-
- menu_track->set_icon(get_icon("Tools", "EditorIcons"));
- menu_track->get_popup()->add_item(TTR("Scale Selection"), TRACK_MENU_SCALE);
- menu_track->get_popup()->add_item(TTR("Scale From Cursor"), TRACK_MENU_SCALE_PIVOT);
- menu_track->get_popup()->add_separator();
- menu_track->get_popup()->add_item(TTR("Duplicate Selection"), TRACK_MENU_DUPLICATE);
- menu_track->get_popup()->add_item(TTR("Duplicate Transposed"), TRACK_MENU_DUPLICATE_TRANSPOSE);
- menu_track->get_popup()->add_separator();
- menu_track->get_popup()->add_item(TTR("Goto Next Step"), TRACK_MENU_NEXT_STEP, KEY_MASK_CMD | KEY_RIGHT);
- menu_track->get_popup()->add_item(TTR("Goto Prev Step"), TRACK_MENU_PREV_STEP, KEY_MASK_CMD | KEY_LEFT);
- menu_track->get_popup()->add_separator();
- PopupMenu *tpp = memnew(PopupMenu);
- tpp->add_item(TTR("Linear"), TRACK_MENU_SET_ALL_TRANS_LINEAR);
- tpp->add_item(TTR("Constant"), TRACK_MENU_SET_ALL_TRANS_CONSTANT);
- tpp->add_item(TTR("In"), TRACK_MENU_SET_ALL_TRANS_IN);
- tpp->add_item(TTR("Out"), TRACK_MENU_SET_ALL_TRANS_OUT);
- tpp->add_item(TTR("In-Out"), TRACK_MENU_SET_ALL_TRANS_INOUT);
- tpp->add_item(TTR("Out-In"), TRACK_MENU_SET_ALL_TRANS_OUTIN);
- tpp->set_name(TTR("Transitions"));
- tpp->connect("id_pressed", this, "_menu_track");
- optimize_dialog->connect("confirmed", this, "_animation_optimize");
-
- menu_track->get_popup()->add_child(tpp);
-
- menu_track->get_popup()->add_item(TTR("Optimize Animation"), TRACK_MENU_OPTIMIZE);
- menu_track->get_popup()->add_item(TTR("Clean-Up Animation"), TRACK_MENU_CLEAN_UP);
-
- curve_linear->connect("pressed", this, "_menu_track", varray(CURVE_SET_LINEAR));
- curve_in->connect("pressed", this, "_menu_track", varray(CURVE_SET_IN));
- curve_out->connect("pressed", this, "_menu_track", varray(CURVE_SET_OUT));
- curve_inout->connect("pressed", this, "_menu_track", varray(CURVE_SET_INOUT));
- curve_outin->connect("pressed", this, "_menu_track", varray(CURVE_SET_OUTIN));
- curve_constant->connect("pressed", this, "_menu_track", varray(CURVE_SET_CONSTANT));
-
- edit_button->connect("pressed", this, "_toggle_edit_curves");
-
- curve_edit->connect("transition_changed", this, "_curve_transition_changed");
- call_select->connect("selected", this, "_add_call_track");
-
- _update_menu();
-
- } break;
-
- case NOTIFICATION_THEME_CHANGED: {
- zoomicon->set_texture(get_icon("Zoom", "EditorIcons"));
-
- menu_add_track->set_icon(get_icon("Add", "EditorIcons"));
-
- menu_track->set_icon(get_icon("Tools", "EditorIcons"));
-
- menu_add_track->get_popup()->set_item_icon(ADD_TRACK_MENU_ADD_VALUE_TRACK, get_icon("KeyValue", "EditorIcons"));
- menu_add_track->get_popup()->set_item_icon(ADD_TRACK_MENU_ADD_TRANSFORM_TRACK, get_icon("KeyXform", "EditorIcons"));
- menu_add_track->get_popup()->set_item_icon(ADD_TRACK_MENU_ADD_CALL_TRACK, get_icon("KeyCall", "EditorIcons"));
-
- curve_linear->set_icon(get_icon("CurveLinear", "EditorIcons"));
- curve_in->set_icon(get_icon("CurveIn", "EditorIcons"));
- curve_out->set_icon(get_icon("CurveOut", "EditorIcons"));
- curve_inout->set_icon(get_icon("CurveInOut", "EditorIcons"));
- curve_outin->set_icon(get_icon("CurveOutIn", "EditorIcons"));
- curve_constant->set_icon(get_icon("CurveConstant", "EditorIcons"));
-
- move_up_button->set_icon(get_icon("MoveUp", "EditorIcons"));
- move_down_button->set_icon(get_icon("MoveDown", "EditorIcons"));
- remove_button->set_icon(get_icon("Remove", "EditorIcons"));
- edit_button->set_icon(get_icon("EditKey", "EditorIcons"));
-
- loop->set_icon(get_icon("Loop", "EditorIcons"));
-
- {
-
- right_data_size_cache = 0;
- int hsep = get_constant("hseparation", "Tree");
- Ref<Texture> remove_icon = get_icon("Remove", "EditorIcons");
- Ref<Texture> move_up_icon = get_icon("MoveUp", "EditorIcons");
- Ref<Texture> move_down_icon = get_icon("MoveDown", "EditorIcons");
- Ref<Texture> down_icon = get_icon("select_arrow", "Tree");
- Ref<Texture> add_key_icon = get_icon("TrackAddKey", "EditorIcons");
- Ref<Texture> interp_icon[3] = {
- get_icon("InterpRaw", "EditorIcons"),
- get_icon("InterpLinear", "EditorIcons"),
- get_icon("InterpCubic", "EditorIcons")
- };
- Ref<Texture> cont_icon[3] = {
- get_icon("TrackContinuous", "EditorIcons"),
- get_icon("TrackDiscrete", "EditorIcons"),
- get_icon("TrackTrigger", "EditorIcons")
- };
-
- Ref<Texture> wrap_icon[2] = {
- get_icon("InterpWrapClamp", "EditorIcons"),
- get_icon("InterpWrapLoop", "EditorIcons"),
- };
- right_data_size_cache = down_icon->get_width() * 3 + add_key_icon->get_width() + interp_icon[0]->get_width() + cont_icon[0]->get_width() + wrap_icon[0]->get_width() + hsep * 9;
- }
- } break;
- }
-}
-
-void AnimationKeyEditor::_scroll_changed(double) {
-
- if (te_drawing)
- return;
-
- track_editor->update();
-}
-
-void AnimationKeyEditor::_update_paths() {
-
- if (animation.is_valid()) {
- //timeline->set_max(animation->get_length());
- //timeline->set_step(0.01);
- track_editor->update();
- length->set_value(animation->get_length());
- step->set_value(animation->get_step());
- }
-}
-
-void AnimationKeyEditor::_root_removed() {
-
- root = NULL;
-}
-
-void AnimationKeyEditor::_update_menu() {
-
- updating = true;
-
- if (animation.is_valid()) {
-
- length->set_value(animation->get_length());
- loop->set_pressed(animation->has_loop());
- step->set_value(animation->get_step());
- }
-
- track_editor->update();
- updating = false;
-}
-void AnimationKeyEditor::_clear_selection() {
-
- selection.clear();
- key_edit->animation = Ref<Animation>();
- key_edit->track = 0;
- key_edit->key_ofs = 0;
- key_edit->hint = PropertyInfo();
- key_edit->base = NodePath();
- key_edit->notify_change();
-}
-
-void AnimationKeyEditor::set_animation(const Ref<Animation> &p_anim) {
-
- if (animation.is_valid())
- animation->disconnect("changed", this, "_update_paths");
- animation = p_anim;
- if (animation.is_valid())
- animation->connect("changed", this, "_update_paths");
-
- timeline_pos = 0;
- _clear_selection();
-
- _update_menu();
- selected_track = -1;
- _edit_if_single_selection();
-
- EditorNode::get_singleton()->update_keying();
-}
-
-void AnimationKeyEditor::set_root(Node *p_root) {
-
- if (root)
- root->disconnect("tree_exiting", this, "_root_removed");
-
- root = p_root;
-
- if (root)
- root->connect("tree_exiting", this, "_root_removed", make_binds(), CONNECT_ONESHOT);
-}
-
-Node *AnimationKeyEditor::get_root() const {
-
- return root;
-}
-
-void AnimationKeyEditor::update_keying() {
-
- bool keying_enabled = is_visible_in_tree() && animation.is_valid();
-
- if (keying_enabled == keying)
- return;
-
- keying = keying_enabled;
- _update_menu();
- emit_signal("keying_changed");
-}
-
-bool AnimationKeyEditor::has_keying() const {
-
- return keying;
-}
-
-void AnimationKeyEditor::_query_insert(const InsertData &p_id) {
-
- if (insert_frame != Engine::get_singleton()->get_frames_drawn()) {
- //clear insert list for the frame if frame changed
- if (insert_confirm->is_visible_in_tree())
- return; //do nothing
- insert_data.clear();
- insert_query = false;
- }
- insert_frame = Engine::get_singleton()->get_frames_drawn();
-
- for (List<InsertData>::Element *E = insert_data.front(); E; E = E->next()) {
- //prevent insertion of multiple tracks
- if (E->get().path == p_id.path)
- return; //already inserted a track for this on this frame
- }
-
- insert_data.push_back(p_id);
-
- if (p_id.track_idx == -1) {
- if (bool(EDITOR_DEF("editors/animation/confirm_insert_track", true))) {
- //potential new key, does not exist
- if (insert_data.size() == 1)
- insert_confirm->set_text(vformat(TTR("Create NEW track for %s and insert key?"), p_id.query));
- else
- insert_confirm->set_text(vformat(TTR("Create %d NEW tracks and insert keys?"), insert_data.size()));
-
- insert_confirm->get_ok()->set_text(TTR("Create"));
- insert_confirm->popup_centered_minsize();
- insert_query = true;
- } else {
- call_deferred("_insert_delay");
- insert_queue = true;
- }
-
- } else {
- if (!insert_query && !insert_queue) {
- call_deferred("_insert_delay");
- insert_queue = true;
- }
- }
-}
-
-void AnimationKeyEditor::insert_transform_key(Spatial *p_node, const String &p_sub, const Transform &p_xform) {
-
- if (!keying)
- return;
- if (!animation.is_valid())
- return;
-
- ERR_FAIL_COND(!root);
- //let's build a node path
- String path = root->get_path_to(p_node);
- if (p_sub != "")
- path += ":" + p_sub;
-
- NodePath np = path;
-
- int track_idx = -1;
-
- for (int i = 0; i < animation->get_track_count(); i++) {
-
- if (animation->track_get_type(i) != Animation::TYPE_TRANSFORM)
- continue;
- if (animation->track_get_path(i) != np)
- continue;
-
- track_idx = i;
- break;
- }
-
- InsertData id;
- Dictionary val;
-
- id.path = np;
- id.track_idx = track_idx;
- id.value = p_xform;
- id.type = Animation::TYPE_TRANSFORM;
- id.query = "node '" + p_node->get_name() + "'";
- id.advance = false;
-
- //dialog insert
-
- _query_insert(id);
-}
-
-void AnimationKeyEditor::insert_node_value_key(Node *p_node, const String &p_property, const Variant &p_value, bool p_only_if_exists) {
-
- ERR_FAIL_COND(!root);
- //let's build a node path
-
- Node *node = p_node;
-
- String path = root->get_path_to(node);
-
- for (int i = 1; i < history->get_path_size(); i++) {
-
- String prop = history->get_path_property(i);
- ERR_FAIL_COND(prop == "");
- path += ":" + prop;
- }
-
- path += ":" + p_property;
-
- NodePath np = path;
-
- //locate track
-
- int track_idx = -1;
-
- for (int i = 0; i < animation->get_track_count(); i++) {
-
- if (animation->track_get_type(i) != Animation::TYPE_VALUE)
- continue;
- if (animation->track_get_path(i) != np)
- continue;
-
- track_idx = i;
- break;
- }
-
- if (p_only_if_exists && track_idx == -1)
- return;
- InsertData id;
- id.path = np;
- id.track_idx = track_idx;
- id.value = p_value;
- id.type = Animation::TYPE_VALUE;
- id.query = "property '" + p_property + "'";
- id.advance = false;
- //dialog insert
- _query_insert(id);
-}
-
-void AnimationKeyEditor::insert_value_key(const String &p_property, const Variant &p_value, bool p_advance) {
-
- ERR_FAIL_COND(!root);
- //let's build a node path
- ERR_FAIL_COND(history->get_path_size() == 0);
- Object *obj = ObjectDB::get_instance(history->get_path_object(0));
- ERR_FAIL_COND(!Object::cast_to<Node>(obj));
-
- Node *node = Object::cast_to<Node>(obj);
-
- String path = root->get_path_to(node);
-
- for (int i = 1; i < history->get_path_size(); i++) {
-
- String prop = history->get_path_property(i);
- ERR_FAIL_COND(prop == "");
- path += ":" + prop;
- }
-
- path += ":" + p_property;
-
- NodePath np = path;
-
- //locate track
-
- int track_idx = -1;
-
- for (int i = 0; i < animation->get_track_count(); i++) {
-
- if (animation->track_get_type(i) != Animation::TYPE_VALUE)
- continue;
- if (animation->track_get_path(i) != np)
- continue;
-
- track_idx = i;
- break;
- }
-
- InsertData id;
- id.path = np;
- id.track_idx = track_idx;
- id.value = p_value;
- id.type = Animation::TYPE_VALUE;
- id.query = "property '" + p_property + "'";
- id.advance = p_advance;
- //dialog insert
- _query_insert(id);
-}
-
-void AnimationKeyEditor::_confirm_insert_list() {
-
- undo_redo->create_action(TTR("Anim Create & Insert"));
-
- int last_track = animation->get_track_count();
- while (insert_data.size()) {
-
- last_track = _confirm_insert(insert_data.front()->get(), last_track);
- insert_data.pop_front();
- }
-
- undo_redo->commit_action();
-}
-
-int AnimationKeyEditor::_confirm_insert(InsertData p_id, int p_last_track) {
-
- if (p_last_track == -1)
- p_last_track = animation->get_track_count();
-
- bool created = false;
- if (p_id.track_idx < 0) {
-
- created = true;
- undo_redo->create_action(TTR("Anim Insert Track & Key"));
- Animation::UpdateMode update_mode = Animation::UPDATE_DISCRETE;
-
- if (p_id.type == Animation::TYPE_VALUE) {
- //wants a new tack
-
- {
- //hack
- NodePath np;
- animation->add_track(p_id.type);
- animation->track_set_path(animation->get_track_count() - 1, p_id.path);
- PropertyInfo h = _find_hint_for_track(animation->get_track_count() - 1, np);
- animation->remove_track(animation->get_track_count() - 1); //hack
-
- if (h.type == Variant::REAL ||
- h.type == Variant::VECTOR2 ||
- h.type == Variant::RECT2 ||
- h.type == Variant::VECTOR3 ||
- h.type == Variant::AABB ||
- h.type == Variant::QUAT ||
- h.type == Variant::COLOR ||
- h.type == Variant::TRANSFORM) {
-
- update_mode = Animation::UPDATE_CONTINUOUS;
- }
-
- if (h.usage & PROPERTY_USAGE_ANIMATE_AS_TRIGGER) {
- update_mode = Animation::UPDATE_TRIGGER;
- }
- }
- }
-
- p_id.track_idx = p_last_track;
-
- undo_redo->add_do_method(animation.ptr(), "add_track", p_id.type);
- undo_redo->add_do_method(animation.ptr(), "track_set_path", p_id.track_idx, p_id.path);
- if (p_id.type == Animation::TYPE_VALUE)
- undo_redo->add_do_method(animation.ptr(), "value_track_set_update_mode", p_id.track_idx, update_mode);
-
- } else {
- undo_redo->create_action(TTR("Anim Insert Key"));
- }
-
- float time = timeline_pos;
- Variant value;
-
- switch (p_id.type) {
-
- case Animation::TYPE_VALUE: {
-
- value = p_id.value;
-
- } break;
- case Animation::TYPE_TRANSFORM: {
-
- Transform tr = p_id.value;
- Dictionary d;
- d["location"] = tr.origin;
- d["scale"] = tr.basis.get_scale();
- d["rotation"] = Quat(tr.basis); //.orthonormalized();
- value = d;
- } break;
- default: {}
- }
-
- undo_redo->add_do_method(animation.ptr(), "track_insert_key", p_id.track_idx, time, value);
-
- if (created) {
-
- //just remove the track
- undo_redo->add_undo_method(animation.ptr(), "remove_track", p_last_track);
- p_last_track++;
- } else {
-
- undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", p_id.track_idx, time);
- int existing = animation->track_find_key(p_id.track_idx, time, true);
- if (existing != -1) {
- Variant v = animation->track_get_key_value(p_id.track_idx, existing);
- float trans = animation->track_get_key_transition(p_id.track_idx, existing);
- undo_redo->add_undo_method(animation.ptr(), "track_insert_key", p_id.track_idx, time, v, trans);
- }
- }
-
- undo_redo->add_do_method(this, "update");
- undo_redo->add_undo_method(this, "update");
- undo_redo->add_do_method(track_editor, "update");
- undo_redo->add_undo_method(track_editor, "update");
- undo_redo->add_do_method(track_pos, "update");
- undo_redo->add_undo_method(track_pos, "update");
-
- undo_redo->commit_action();
-
- return p_last_track;
-}
-
-Ref<Animation> AnimationKeyEditor::get_current_animation() const {
-
- return animation;
-}
-
-void AnimationKeyEditor::_animation_len_changed(float p_len) {
-
- if (updating)
- return;
-
- if (!animation.is_null()) {
-
- undo_redo->create_action(TTR("Change Anim Len"));
- undo_redo->add_do_method(animation.ptr(), "set_length", p_len);
- undo_redo->add_undo_method(animation.ptr(), "set_length", animation->get_length());
- undo_redo->add_do_method(this, "_animation_len_update");
- undo_redo->add_undo_method(this, "_animation_len_update");
- undo_redo->commit_action();
- }
-}
-
-void AnimationKeyEditor::_animation_len_update() {
-
- if (!animation.is_null())
- emit_signal(alc, animation->get_length());
-}
-
-void AnimationKeyEditor::_animation_changed() {
- if (updating)
- return;
- _update_menu();
-}
-
-void AnimationKeyEditor::_animation_loop_changed() {
-
- if (updating)
- return;
-
- if (!animation.is_null()) {
-
- undo_redo->create_action(TTR("Change Anim Loop"));
- undo_redo->add_do_method(animation.ptr(), "set_loop", loop->is_pressed());
- undo_redo->add_undo_method(animation.ptr(), "set_loop", !loop->is_pressed());
- undo_redo->commit_action();
- }
-}
-
-void AnimationKeyEditor::_create_value_item(int p_type) {
-
- undo_redo->create_action(TTR("Anim Create Typed Value Key"));
-
- Variant::CallError ce;
- Variant v = Variant::construct(Variant::Type(p_type), NULL, 0, ce);
- undo_redo->add_do_method(animation.ptr(), "track_insert_key", cvi_track, cvi_pos, v);
- undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", cvi_track, cvi_pos);
-
- int existing = animation->track_find_key(cvi_track, cvi_pos, true);
-
- if (existing != -1) {
- Variant v = animation->track_get_key_value(cvi_track, existing);
- float trans = animation->track_get_key_transition(cvi_track, existing);
- undo_redo->add_undo_method(animation.ptr(), "track_insert_key", cvi_track, cvi_pos, v, trans);
- }
-
- undo_redo->commit_action();
-}
-
-void AnimationKeyEditor::set_anim_pos(float p_pos) {
-
- if (animation.is_null())
- return;
- timeline_pos = p_pos;
- update();
- track_pos->update();
- track_editor->update();
-}
-
-void AnimationKeyEditor::_pane_drag(const Point2 &p_delta) {
-
- Size2 ecs = ec->get_custom_minimum_size();
- ecs.y -= p_delta.y;
- if (ecs.y < 100)
- ecs.y = 100;
- ec->set_custom_minimum_size(ecs);
-}
-
-void AnimationKeyEditor::_insert_delay() {
-
- if (insert_query) {
- //discard since it's entered into query mode
- insert_queue = false;
- return;
- }
-
- undo_redo->create_action(TTR("Anim Insert"));
-
- int last_track = animation->get_track_count();
- bool advance = false;
- while (insert_data.size()) {
-
- if (insert_data.front()->get().advance)
- advance = true;
- last_track = _confirm_insert(insert_data.front()->get(), last_track);
- insert_data.pop_front();
- }
-
- undo_redo->commit_action();
-
- if (advance) {
- float step = animation->get_step();
- if (step == 0)
- step = 1;
-
- float pos = timeline_pos;
-
- pos = Math::stepify(pos + step, step);
- if (pos > animation->get_length())
- pos = animation->get_length();
- timeline_pos = pos;
- track_pos->update();
- emit_signal("timeline_changed", pos, true);
- }
- insert_queue = false;
-}
-
-void AnimationKeyEditor::_step_changed(float p_len) {
-
- updating = true;
- if (!animation.is_null()) {
- animation->set_step(p_len);
- emit_signal("animation_step_changed", animation->get_step());
- }
- updating = false;
-}
-
-void AnimationKeyEditor::_scale() {
-
- if (selection.empty())
- return;
-
- float from_t = 1e20;
- float to_t = -1e20;
- float len = -1e20;
- float pivot = 0;
-
- for (Map<SelectedKey, KeyInfo>::Element *E = selection.front(); E; E = E->next()) {
- float t = animation->track_get_key_time(E->key().track, E->key().key);
- if (t < from_t)
- from_t = t;
- if (t > to_t)
- to_t = t;
- }
-
- len = to_t - from_t;
- if (last_menu_track_opt == TRACK_MENU_SCALE_PIVOT) {
- pivot = timeline_pos;
-
- } else {
-
- pivot = from_t;
- }
-
- float s = scale->get_value();
- if (s == 0) {
- ERR_PRINT("Can't scale to 0");
- }
-
- undo_redo->create_action(TTR("Anim Scale Keys"));
-
- List<_AnimMoveRestore> to_restore;
-
- // 1-remove the keys
- for (Map<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()) {
-
- float newtime = (E->get().pos - from_t) * s + from_t;
- int idx = animation->track_find_key(E->key().track, newtime, true);
- if (idx == -1)
- continue;
- SelectedKey sk;
- sk.key = idx;
- sk.track = E->key().track;
- if (selection.has(sk))
- continue; //already in selection, don't save
-
- undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_position", E->key().track, newtime);
- _AnimMoveRestore amr;
-
- amr.key = animation->track_get_key_value(E->key().track, idx);
- amr.track = E->key().track;
- amr.time = newtime;
- amr.transition = animation->track_get_key_transition(E->key().track, idx);
-
- to_restore.push_back(amr);
- }
-
-#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()) {
-
- 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()) {
-
- float newpos = _NEW_POS(E->get().pos);
- undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", E->key().track, newpos);
- }
-
- // 5-(undo) reinsert keys
- for (Map<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));
- }
-
- // 6-(undo) reinsert overlapped keys
- for (List<_AnimMoveRestore>::Element *E = to_restore.front(); E; E = E->next()) {
-
- _AnimMoveRestore &amr = E->get();
- undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, amr.transition);
- }
-
- // 6-(undo) reinsert overlapped keys
- for (List<_AnimMoveRestore>::Element *E = to_restore.front(); E; E = E->next()) {
-
- _AnimMoveRestore &amr = E->get();
- undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, amr.transition);
- }
-
- undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);
- 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()) {
-
- float oldpos = E->get().pos;
- float newpos = _NEW_POS(oldpos);
- if (newpos >= 0)
- undo_redo->add_do_method(this, "_select_at_anim", animation, E->key().track, newpos);
- undo_redo->add_undo_method(this, "_select_at_anim", animation, E->key().track, oldpos);
- }
-#undef _NEW_POS
- undo_redo->commit_action();
-}
-
-void AnimationKeyEditor::_add_call_track(const NodePath &p_base) {
-
- Node *base = EditorNode::get_singleton()->get_edited_scene();
- if (!base)
- return;
- Node *from = base->get_node(p_base);
- if (!from || !root)
- return;
-
- NodePath path = root->get_path_to(from);
-
- //print_line("root: "+String(root->get_path()));
- //print_line("path: "+String(path));
-
- undo_redo->create_action(TTR("Anim Add Call Track"));
- undo_redo->add_do_method(animation.ptr(), "add_track", Animation::TYPE_METHOD);
- undo_redo->add_do_method(animation.ptr(), "track_set_path", animation->get_track_count(), path);
- undo_redo->add_undo_method(animation.ptr(), "remove_track", animation->get_track_count());
- undo_redo->commit_action();
-}
-
-void AnimationKeyEditor::cleanup() {
-
- set_animation(Ref<Animation>());
-}
-
-void AnimationKeyEditor::_bind_methods() {
-
- ClassDB::bind_method(D_METHOD("_root_removed"), &AnimationKeyEditor::_root_removed);
- ClassDB::bind_method(D_METHOD("_scale"), &AnimationKeyEditor::_scale);
- ClassDB::bind_method(D_METHOD("set_root"), &AnimationKeyEditor::set_root);
-
- //ClassDB::bind_method(D_METHOD("_confirm_insert"),&AnimationKeyEditor::_confirm_insert);
- ClassDB::bind_method(D_METHOD("_confirm_insert_list"), &AnimationKeyEditor::_confirm_insert_list);
-
- ClassDB::bind_method(D_METHOD("_update_paths"), &AnimationKeyEditor::_update_paths);
- ClassDB::bind_method(D_METHOD("_track_editor_draw"), &AnimationKeyEditor::_track_editor_draw);
-
- ClassDB::bind_method(D_METHOD("_animation_changed"), &AnimationKeyEditor::_animation_changed);
- ClassDB::bind_method(D_METHOD("_scroll_changed"), &AnimationKeyEditor::_scroll_changed);
- ClassDB::bind_method(D_METHOD("_track_editor_gui_input"), &AnimationKeyEditor::_track_editor_gui_input);
- ClassDB::bind_method(D_METHOD("_track_name_changed"), &AnimationKeyEditor::_track_name_changed);
- ClassDB::bind_method(D_METHOD("_track_menu_selected"), &AnimationKeyEditor::_track_menu_selected);
- ClassDB::bind_method(D_METHOD("_menu_add_track"), &AnimationKeyEditor::_menu_add_track);
- ClassDB::bind_method(D_METHOD("_menu_track"), &AnimationKeyEditor::_menu_track);
- ClassDB::bind_method(D_METHOD("_clear_selection_for_anim"), &AnimationKeyEditor::_clear_selection_for_anim);
- ClassDB::bind_method(D_METHOD("_select_at_anim"), &AnimationKeyEditor::_select_at_anim);
- ClassDB::bind_method(D_METHOD("_track_position_draw"), &AnimationKeyEditor::_track_position_draw);
- ClassDB::bind_method(D_METHOD("_insert_delay"), &AnimationKeyEditor::_insert_delay);
- ClassDB::bind_method(D_METHOD("_step_changed"), &AnimationKeyEditor::_step_changed);
-
- ClassDB::bind_method(D_METHOD("_animation_loop_changed"), &AnimationKeyEditor::_animation_loop_changed);
- ClassDB::bind_method(D_METHOD("_animation_len_changed"), &AnimationKeyEditor::_animation_len_changed);
- ClassDB::bind_method(D_METHOD("_create_value_item"), &AnimationKeyEditor::_create_value_item);
- ClassDB::bind_method(D_METHOD("_pane_drag"), &AnimationKeyEditor::_pane_drag);
-
- ClassDB::bind_method(D_METHOD("_animation_len_update"), &AnimationKeyEditor::_animation_len_update);
-
- ClassDB::bind_method(D_METHOD("set_animation"), &AnimationKeyEditor::set_animation);
- ClassDB::bind_method(D_METHOD("_animation_optimize"), &AnimationKeyEditor::_animation_optimize);
- ClassDB::bind_method(D_METHOD("_curve_transition_changed"), &AnimationKeyEditor::_curve_transition_changed);
- ClassDB::bind_method(D_METHOD("_toggle_edit_curves"), &AnimationKeyEditor::_toggle_edit_curves);
- ClassDB::bind_method(D_METHOD("_add_call_track"), &AnimationKeyEditor::_add_call_track);
-
- ADD_SIGNAL(MethodInfo("resource_selected", PropertyInfo(Variant::OBJECT, "res"), PropertyInfo(Variant::STRING, "prop")));
- ADD_SIGNAL(MethodInfo("keying_changed"));
- ADD_SIGNAL(MethodInfo("timeline_changed", PropertyInfo(Variant::REAL, "position"), PropertyInfo(Variant::BOOL, "drag")));
- ADD_SIGNAL(MethodInfo("animation_len_changed", PropertyInfo(Variant::REAL, "len")));
- ADD_SIGNAL(MethodInfo("animation_step_changed", PropertyInfo(Variant::REAL, "step")));
- ADD_SIGNAL(MethodInfo("key_edited", PropertyInfo(Variant::INT, "track"), PropertyInfo(Variant::INT, "key")));
-}
-
-AnimationKeyEditor::AnimationKeyEditor() {
-
- alc = "animation_len_changed";
- editor_selection = EditorNode::get_singleton()->get_editor_selection();
-
- selected_track = -1;
- updating = false;
- te_drawing = false;
- undo_redo = EditorNode::get_singleton()->get_undo_redo();
- history = EditorNode::get_singleton()->get_editor_history();
-
- ec = memnew(Control);
- ec->set_custom_minimum_size(Size2(0, 150) * EDSCALE);
- add_child(ec);
- ec->set_v_size_flags(SIZE_EXPAND_FILL);
-
- h_scroll = memnew(HScrollBar);
- h_scroll->connect("value_changed", this, "_scroll_changed");
- add_child(h_scroll);
- h_scroll->set_value(0);
-
- HBoxContainer *hb = memnew(HBoxContainer);
- add_child(hb);
-
- root = NULL;
- //menu = memnew( MenuButton );
- //menu->set_flat(true);
- //menu->set_position(Point2());
- //add_child(menu);
-
- zoomicon = memnew(TextureRect);
- hb->add_child(zoomicon);
- zoomicon->set_tooltip(TTR("Animation zoom."));
-
- zoom = memnew(HSlider);
- //hb->add_child(zoom);
- zoom->set_step(0.01);
- zoom->set_min(0.0);
- zoom->set_max(2.0);
- zoom->set_value(1.0);
- zoom->set_h_size_flags(SIZE_EXPAND_FILL);
- zoom->set_v_size_flags(SIZE_EXPAND_FILL);
- zoom->set_stretch_ratio(2);
- hb->add_child(zoom);
- zoom->connect("value_changed", this, "_scroll_changed");
- zoom->set_tooltip(TTR("Animation zoom."));
-
- hb->add_child(memnew(VSeparator));
-
- Label *l = memnew(Label);
- l->set_text(TTR("Length (s):"));
- hb->add_child(l);
-
- length = memnew(SpinBox);
- length->set_min(0.01);
- length->set_max(10000);
- length->set_step(0.01);
- length->set_h_size_flags(SIZE_EXPAND_FILL);
- length->set_stretch_ratio(1);
- length->set_tooltip(TTR("Animation length (in seconds)."));
- length->set_editable(false);
-
- hb->add_child(length);
- length->connect("value_changed", this, "_animation_len_changed");
-
- l = memnew(Label);
- l->set_text(TTR("Step (s):"));
- hb->add_child(l);
-
- step = memnew(SpinBox);
- step->set_min(0.00);
- step->set_max(128);
- step->set_step(0.01);
- step->set_value(0.0);
- step->set_h_size_flags(SIZE_EXPAND_FILL);
- step->set_stretch_ratio(1);
- step->set_tooltip(TTR("Cursor step snap (in seconds)."));
- step->set_editable(false);
-
- hb->add_child(step);
- step->connect("value_changed", this, "_step_changed");
-
- loop = memnew(ToolButton);
- loop->set_toggle_mode(true);
- loop->connect("pressed", this, "_animation_loop_changed");
- hb->add_child(loop);
- loop->set_tooltip(TTR("Enable/Disable looping in animation."));
- loop->set_disabled(true);
-
- hb->add_child(memnew(VSeparator));
-
- menu_add_track = memnew(MenuButton);
- hb->add_child(menu_add_track);
- menu_add_track->get_popup()->connect("id_pressed", this, "_menu_add_track");
- menu_add_track->set_tooltip(TTR("Add new tracks."));
- menu_add_track->get_popup()->add_icon_item(get_icon("KeyValue", "EditorIcons"), "Add Normal Track", ADD_TRACK_MENU_ADD_VALUE_TRACK);
- menu_add_track->get_popup()->add_icon_item(get_icon("KeyXform", "EditorIcons"), "Add Transform Track", ADD_TRACK_MENU_ADD_TRANSFORM_TRACK);
- menu_add_track->get_popup()->add_icon_item(get_icon("KeyCall", "EditorIcons"), "Add Call Func Track", ADD_TRACK_MENU_ADD_CALL_TRACK);
-
- move_up_button = memnew(ToolButton);
- hb->add_child(move_up_button);
- move_up_button->connect("pressed", this, "_menu_track", make_binds(TRACK_MENU_MOVE_UP));
- move_up_button->set_focus_mode(FOCUS_NONE);
- move_up_button->set_disabled(true);
- move_up_button->set_tooltip(TTR("Move current track up."));
-
- move_down_button = memnew(ToolButton);
- hb->add_child(move_down_button);
- move_down_button->connect("pressed", this, "_menu_track", make_binds(TRACK_MENU_MOVE_DOWN));
- move_down_button->set_focus_mode(FOCUS_NONE);
- move_down_button->set_disabled(true);
- move_down_button->set_tooltip(TTR("Move current track down."));
-
- remove_button = memnew(ToolButton);
- hb->add_child(remove_button);
- remove_button->connect("pressed", this, "_menu_track", make_binds(TRACK_MENU_REMOVE));
- remove_button->set_focus_mode(FOCUS_NONE);
- remove_button->set_disabled(true);
- remove_button->set_tooltip(TTR("Remove selected track."));
-
- hb->add_child(memnew(VSeparator));
-
- menu_track = memnew(MenuButton);
- hb->add_child(menu_track);
- menu_track->get_popup()->connect("id_pressed", this, "_menu_track");
- menu_track->set_tooltip(TTR("Track Tools"));
-
- edit_button = memnew(ToolButton);
- edit_button->set_toggle_mode(true);
- edit_button->set_focus_mode(FOCUS_NONE);
- edit_button->set_disabled(true);
-
- hb->add_child(edit_button);
- edit_button->set_tooltip(TTR("Enable editing of individual keys by clicking them."));
-
- optimize_dialog = memnew(ConfirmationDialog);
- add_child(optimize_dialog);
- optimize_dialog->set_title(TTR("Anim. Optimizer"));
- VBoxContainer *optimize_vb = memnew(VBoxContainer);
- optimize_dialog->add_child(optimize_vb);
-
- optimize_linear_error = memnew(SpinBox);
- optimize_linear_error->set_max(1.0);
- optimize_linear_error->set_min(0.001);
- optimize_linear_error->set_step(0.001);
- optimize_linear_error->set_value(0.05);
- optimize_vb->add_margin_child(TTR("Max. Linear Error:"), optimize_linear_error);
- optimize_angular_error = memnew(SpinBox);
- optimize_angular_error->set_max(1.0);
- optimize_angular_error->set_min(0.001);
- optimize_angular_error->set_step(0.001);
- optimize_angular_error->set_value(0.01);
-
- optimize_vb->add_margin_child(TTR("Max. Angular Error:"), optimize_angular_error);
- optimize_max_angle = memnew(SpinBox);
- optimize_vb->add_margin_child(TTR("Max Optimizable Angle:"), optimize_max_angle);
- optimize_max_angle->set_max(360.0);
- optimize_max_angle->set_min(0.0);
- optimize_max_angle->set_step(0.1);
- optimize_max_angle->set_value(22);
-
- optimize_dialog->get_ok()->set_text(TTR("Optimize"));
-
- /*keying = memnew( Button );
- keying->set_toggle_mode(true);
- //keying->set_text("Keys");
- keying->set_anchor_and_margin(MARGIN_LEFT,ANCHOR_END,60);
- keying->set_anchor_and_margin(MARGIN_RIGHT,ANCHOR_END,10);
- keying->set_anchor_and_margin(MARGIN_BOTTOM,ANCHOR_BEGIN,55);
- keying->set_anchor_and_margin(MARGIN_TOP,ANCHOR_BEGIN,10);
- //add_child(keying);
- keying->connect("pressed",this,"_keying_toggled");
- */
-
- /* l = memnew( Label );
- l->set_text("Base: ");
- l->set_position(Point2(0,3));
- //dr_panel->add_child(l);*/
-
- //menu->get_popup()->connect("id_pressed",this,"_menu_callback");
-
- hb = memnew(HBoxContainer);
- hb->set_anchors_and_margins_preset(Control::PRESET_WIDE);
- ec->add_child(hb);
- hb->set_v_size_flags(SIZE_EXPAND_FILL);
-
- track_editor = memnew(Control);
- track_editor->connect("draw", this, "_track_editor_draw");
- hb->add_child(track_editor);
- track_editor->connect("gui_input", this, "_track_editor_gui_input");
- track_editor->set_focus_mode(Control::FOCUS_ALL);
- track_editor->set_h_size_flags(SIZE_EXPAND_FILL);
-
- track_pos = memnew(Control);
- track_pos->set_anchors_and_margins_preset(Control::PRESET_WIDE);
- track_pos->set_mouse_filter(MOUSE_FILTER_IGNORE);
- track_editor->add_child(track_pos);
- track_pos->connect("draw", this, "_track_position_draw");
-
- select_anim_warning = memnew(Label);
- track_editor->add_child(select_anim_warning);
- select_anim_warning->set_anchors_and_margins_preset(Control::PRESET_WIDE);
- select_anim_warning->set_text(TTR("Select an AnimationPlayer from the Scene Tree to edit animations."));
- select_anim_warning->set_autowrap(true);
- select_anim_warning->set_align(Label::ALIGN_CENTER);
- select_anim_warning->set_valign(Label::VALIGN_CENTER);
-
- v_scroll = memnew(VScrollBar);
- hb->add_child(v_scroll);
- v_scroll->connect("value_changed", this, "_scroll_changed");
- v_scroll->set_value(0);
-
- key_editor_tab = memnew(TabContainer);
- key_editor_tab->set_tab_align(TabContainer::ALIGN_LEFT);
- hb->add_child(key_editor_tab);
- key_editor_tab->set_custom_minimum_size(Size2(200, 0) * EDSCALE);
-
- key_editor = memnew(PropertyEditor);
- key_editor->hide_top_label();
- key_editor->set_name(TTR("Key"));
- key_editor_tab->add_child(key_editor);
-
- key_edit = memnew(AnimationKeyEdit);
- key_edit->undo_redo = undo_redo;
- //key_edit->ke_dialog=key_edit_dialog;
-
- type_menu = memnew(PopupMenu);
- type_menu->set_pass_on_modal_close_click(false);
- add_child(type_menu);
- for (int i = 0; i < Variant::VARIANT_MAX; i++)
- type_menu->add_item(Variant::get_type_name(Variant::Type(i)), i);
- type_menu->connect("id_pressed", this, "_create_value_item");
-
- VBoxContainer *curve_vb = memnew(VBoxContainer);
- curve_vb->set_name(TTR("Transition"));
- HBoxContainer *curve_hb = memnew(HBoxContainer);
- curve_vb->add_child(curve_hb);
-
- curve_linear = memnew(ToolButton);
- curve_linear->set_focus_mode(FOCUS_NONE);
- curve_hb->add_child(curve_linear);
- curve_in = memnew(ToolButton);
- curve_in->set_focus_mode(FOCUS_NONE);
- curve_hb->add_child(curve_in);
- curve_out = memnew(ToolButton);
- curve_out->set_focus_mode(FOCUS_NONE);
- curve_hb->add_child(curve_out);
- curve_inout = memnew(ToolButton);
- curve_inout->set_focus_mode(FOCUS_NONE);
- curve_hb->add_child(curve_inout);
- curve_outin = memnew(ToolButton);
- curve_outin->set_focus_mode(FOCUS_NONE);
- curve_hb->add_child(curve_outin);
- curve_constant = memnew(ToolButton);
- curve_constant->set_focus_mode(FOCUS_NONE);
- curve_hb->add_child(curve_constant);
-
- curve_edit = memnew(AnimationCurveEdit);
- curve_vb->add_child(curve_edit);
- curve_edit->set_v_size_flags(SIZE_EXPAND_FILL);
- key_editor_tab->add_child(curve_vb);
-
- track_name = memnew(LineEdit);
- track_name->set_as_toplevel(true);
- track_name->hide();
- add_child(track_name);
- track_name->connect("text_entered", this, "_track_name_changed");
- track_menu = memnew(PopupMenu);
- track_menu->set_pass_on_modal_close_click(false);
- add_child(track_menu);
- track_menu->connect("id_pressed", this, "_track_menu_selected");
-
- key_editor_tab->hide();
-
- last_idx = 1;
-
- _update_menu();
-
- insert_confirm = memnew(ConfirmationDialog);
- add_child(insert_confirm);
- insert_confirm->connect("confirmed", this, "_confirm_insert_list");
-
- click.click = ClickOver::CLICK_NONE;
-
- name_column_ratio = 0.3;
- timeline_pos = 0;
-
- keying = false;
- insert_frame = 0;
- insert_query = false;
- insert_queue = false;
-
- editor_selection->connect("selection_changed", track_editor, "update");
-
- scale_dialog = memnew(ConfirmationDialog);
- VBoxContainer *vbc = memnew(VBoxContainer);
- scale_dialog->add_child(vbc);
-
- scale = memnew(SpinBox);
- scale->set_min(-99999);
- scale->set_max(99999);
- scale->set_step(0.001);
- vbc->add_margin_child(TTR("Scale Ratio:"), scale);
- scale_dialog->connect("confirmed", this, "_scale");
- add_child(scale_dialog);
-
- call_select = memnew(SceneTreeDialog);
- add_child(call_select);
- call_select->set_title(TTR("Call Functions in Which Node?"));
-
- cleanup_dialog = memnew(ConfirmationDialog);
- add_child(cleanup_dialog);
- VBoxContainer *cleanup_vb = memnew(VBoxContainer);
- cleanup_dialog->add_child(cleanup_vb);
-
- cleanup_keys = memnew(CheckButton);
- cleanup_keys->set_text(TTR("Remove invalid keys"));
- cleanup_keys->set_pressed(true);
- cleanup_vb->add_child(cleanup_keys);
-
- cleanup_tracks = memnew(CheckButton);
- cleanup_tracks->set_text(TTR("Remove unresolved and empty tracks"));
- cleanup_tracks->set_pressed(true);
- cleanup_vb->add_child(cleanup_tracks);
-
- cleanup_all = memnew(CheckButton);
- cleanup_all->set_text(TTR("Clean-up all animations"));
- cleanup_vb->add_child(cleanup_all);
-
- cleanup_dialog->set_title(TTR("Clean-Up Animation(s) (NO UNDO!)"));
- cleanup_dialog->get_ok()->set_text(TTR("Clean-Up"));
-
- cleanup_dialog->connect("confirmed", this, "_menu_track", varray(TRACK_MENU_CLEAN_UP_CONFIRM));
-
- track_editor->set_clip_contents(true);
-}
-
-AnimationKeyEditor::~AnimationKeyEditor() {
-
- memdelete(key_edit);
-}
diff --git a/editor/animation_editor.h b/editor/animation_editor.h
deleted file mode 100644
index 1e593f237c..0000000000
--- a/editor/animation_editor.h
+++ /dev/null
@@ -1,348 +0,0 @@
-/*************************************************************************/
-/* animation_editor.h */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#ifndef ANIMATION_EDITOR_H
-#define ANIMATION_EDITOR_H
-
-#include "scene/gui/control.h"
-#include "scene/gui/file_dialog.h"
-#include "scene/gui/menu_button.h"
-#include "scene/gui/scroll_bar.h"
-#include "scene/gui/slider.h"
-#include "scene/gui/spin_box.h"
-#include "scene/gui/tab_container.h"
-#include "scene/gui/texture_rect.h"
-#include "scene/gui/tool_button.h"
-
-#include "editor_data.h"
-#include "property_editor.h"
-#include "scene/animation/animation_cache.h"
-#include "scene/resources/animation.h"
-#include "scene_tree_editor.h"
-
-class AnimationKeyEdit;
-class AnimationCurveEdit;
-
-class AnimationKeyEditor : public VBoxContainer {
-
- GDCLASS(AnimationKeyEditor, VBoxContainer);
-
- /*
- enum {
-
- MENU_NEW_ANIMATION,
- MENU_OPEN_ANIMATION,
- MENU_EDIT_ANIMATION,
- MENU_CLOSE_ANIMATION,
- MENU_KEYING_ACTIVE,
- MENU_SET_ROOT_NODE,
- MENU_SYNC_TO_PLAYER,
- MENU_ANIM_BASE=100,
- };
-
-*/
-
- enum {
-
- ADD_TRACK_MENU_ADD_VALUE_TRACK,
- ADD_TRACK_MENU_ADD_TRANSFORM_TRACK,
- ADD_TRACK_MENU_ADD_CALL_TRACK,
- TRACK_MENU_SCALE,
- TRACK_MENU_SCALE_PIVOT,
- TRACK_MENU_MOVE_UP,
- TRACK_MENU_MOVE_DOWN,
- TRACK_MENU_REMOVE,
- TRACK_MENU_DUPLICATE,
- TRACK_MENU_DUPLICATE_TRANSPOSE,
- TRACK_MENU_SET_ALL_TRANS_LINEAR,
- TRACK_MENU_SET_ALL_TRANS_CONSTANT,
- TRACK_MENU_SET_ALL_TRANS_OUT,
- TRACK_MENU_SET_ALL_TRANS_IN,
- TRACK_MENU_SET_ALL_TRANS_INOUT,
- TRACK_MENU_SET_ALL_TRANS_OUTIN,
- TRACK_MENU_NEXT_STEP,
- TRACK_MENU_PREV_STEP,
- TRACK_MENU_OPTIMIZE,
- TRACK_MENU_CLEAN_UP,
- TRACK_MENU_CLEAN_UP_CONFIRM,
- CURVE_SET_LINEAR,
- CURVE_SET_IN,
- CURVE_SET_OUT,
- CURVE_SET_INOUT,
- CURVE_SET_OUTIN,
- CURVE_SET_CONSTANT
- };
-
- enum {
- RIGHT_MENU_DUPLICATE,
- RIGHT_MENU_DUPLICATE_TRANSPOSE,
- RIGHT_MENU_REMOVE
- };
-
- struct MouseOver {
-
- enum Over {
- OVER_NONE,
- OVER_NAME,
- OVER_KEY,
- OVER_VALUE,
- OVER_INTERP,
- OVER_WRAP,
- OVER_UP,
- OVER_DOWN,
- OVER_REMOVE,
- OVER_ADD_KEY,
- };
-
- Over over;
- int track;
- int over_key;
-
- } mouse_over;
-
- struct SelectedKey {
-
- int track;
- int key;
- bool operator<(const SelectedKey &p_key) const { return track == p_key.track ? key < p_key.key : track < p_key.track; };
- };
-
- struct KeyInfo {
-
- float pos;
- };
-
- Map<SelectedKey, KeyInfo> selection;
-
- struct ClickOver {
-
- enum Click {
-
- CLICK_NONE,
- CLICK_RESIZE_NAMES,
- CLICK_DRAG_TIMELINE,
- CLICK_MOVE_KEYS,
- CLICK_SELECT_KEYS
-
- };
-
- SelectedKey selk;
- bool shift;
- Click click;
- Point2 at;
- Point2 to;
- } click;
-
- float timeline_pos;
-
- float name_column_ratio;
-
- int track_name_editing;
- int interp_editing;
- int cont_editing;
- int wrap_editing;
- int selected_track;
- int track_ofs[5];
-
- int last_menu_track_opt;
- LineEdit *track_name;
- PopupMenu *track_menu;
- PopupMenu *type_menu;
-
- Control *ec;
- TextureRect *zoomicon;
- HSlider *zoom;
- //MenuButton *menu;
- SpinBox *length;
- Button *loop;
- bool keying;
- ToolButton *edit_button;
- ToolButton *move_up_button;
- ToolButton *move_down_button;
- ToolButton *remove_button;
-
- ToolButton *curve_linear;
- ToolButton *curve_in;
- ToolButton *curve_out;
- ToolButton *curve_inout;
- ToolButton *curve_outin;
- ToolButton *curve_constant;
-
- ConfirmationDialog *optimize_dialog;
- SpinBox *optimize_linear_error;
- SpinBox *optimize_angular_error;
- SpinBox *optimize_max_angle;
-
- ConfirmationDialog *cleanup_dialog;
- CheckButton *cleanup_keys;
- CheckButton *cleanup_tracks;
- CheckButton *cleanup_all;
-
- SpinBox *step;
-
- MenuButton *menu_add_track;
- MenuButton *menu_track;
-
- HScrollBar *h_scroll;
- VScrollBar *v_scroll;
-
- Control *track_editor;
- Control *track_pos;
- TabContainer *key_editor_tab;
-
- ConfirmationDialog *scale_dialog;
- SpinBox *scale;
-
- PropertyEditor *key_editor;
-
- SceneTreeDialog *call_select;
-
- Ref<Animation> animation;
- void _update_paths();
-
- int last_idx;
-
- Node *root;
-
- UndoRedo *undo_redo;
- EditorHistory *history;
- ConfirmationDialog *insert_confirm;
-
- AnimationKeyEdit *key_edit;
- AnimationCurveEdit *curve_edit;
-
- bool inserting;
-
- bool updating;
- bool te_drawing;
-
- void _animation_len_changed(float p_len);
- void _animation_loop_changed();
- void _step_changed(float p_len);
-
- struct InsertData {
-
- Animation::TrackType type;
- NodePath path;
- int track_idx;
- Variant value;
- String query;
- bool advance;
- }; /* insert_data;*/
-
- bool insert_query;
- List<InsertData> insert_data;
- uint64_t insert_frame;
-
- int cvi_track;
- float cvi_pos;
-
- int right_data_size_cache;
-
- EditorSelection *editor_selection;
-
- Label *select_anim_warning;
-
- float _get_zoom_scale() const;
-
- void _track_editor_draw();
- void _track_editor_gui_input(const Ref<InputEvent> &p_input);
- void _track_position_draw();
-
- void _track_name_changed(const String &p_name);
- void _track_menu_selected(int p_idx);
- void _confirm_insert_list();
- int _confirm_insert(InsertData p_id, int p_last_track = -1);
- void _query_insert(const InsertData &p_id);
- void _update_menu();
- bool insert_queue;
- void _insert_delay();
- void _scale();
-
- void _clear_selection();
-
- //void _browse_path();
-
- StringName alc;
-
- void _animation_changed();
- void _animation_optimize();
- void _cleanup_animation(Ref<Animation> p_animation);
-
- void _scroll_changed(double);
-
- void _menu_add_track(int p_type);
- void _menu_track(int p_type);
-
- void _clear_selection_for_anim(const Ref<Animation> &p_anim);
- void _select_at_anim(const Ref<Animation> &p_anim, int p_track, float p_pos);
- void _curve_transition_changed(float p_what);
-
- PropertyInfo _find_hint_for_track(int p_idx, NodePath &r_base_path);
-
- void _create_value_item(int p_type);
- void _pane_drag(const Point2 &p_delta);
- bool _edit_if_single_selection();
-
- void _toggle_edit_curves();
- void _animation_len_update();
-
- void _add_call_track(const NodePath &p_base);
-
- void _anim_duplicate_keys(bool transpose = false);
- void _anim_delete_keys();
-
- void _root_removed();
-
-protected:
- void _notification(int p_what);
- static void _bind_methods();
-
-public:
- void set_animation(const Ref<Animation> &p_anim);
- Ref<Animation> get_current_animation() const;
- void set_root(Node *p_root);
- Node *get_root() const;
- void update_keying();
- bool has_keying() const;
-
- void cleanup();
-
- void set_anim_pos(float p_pos);
- void insert_node_value_key(Node *p_node, const String &p_property, const Variant &p_value, bool p_only_if_exists = false);
- void insert_value_key(const String &p_property, const Variant &p_value, bool p_advance);
- void insert_transform_key(Spatial *p_node, const String &p_sub, const Transform &p_xform);
-
- void show_select_node_warning(bool p_show) { select_anim_warning->set_visible(p_show); }
- AnimationKeyEditor();
- ~AnimationKeyEditor();
-};
-
-#endif // ANIMATION_EDITOR_H
diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp
new file mode 100644
index 0000000000..adc9821d8a
--- /dev/null
+++ b/editor/animation_track_editor.cpp
@@ -0,0 +1,5001 @@
+#include "animation_track_editor.h"
+#include "animation_track_editor_plugins.h"
+#include "editor/animation_bezier_editor.h"
+#include "editor/plugins/animation_player_editor_plugin.h"
+#include "editor_node.h"
+#include "editor_scale.h"
+#include "os/keyboard.h"
+#include "scene/main/viewport.h"
+#include "servers/audio/audio_stream.h"
+
+class AnimationTrackKeyEdit : public Object {
+
+ GDCLASS(AnimationTrackKeyEdit, Object);
+
+public:
+ bool setting;
+ bool hidden;
+
+ bool _hide_script_from_inspector() {
+ return true;
+ }
+
+ static void _bind_methods() {
+
+ ClassDB::bind_method("_update_obj", &AnimationTrackKeyEdit::_update_obj);
+ ClassDB::bind_method("_key_ofs_changed", &AnimationTrackKeyEdit::_key_ofs_changed);
+ ClassDB::bind_method("_hide_script_from_inspector", &AnimationTrackKeyEdit::_hide_script_from_inspector);
+ }
+
+ //PopupDialog *ke_dialog;
+
+ void _fix_node_path(Variant &value) {
+
+ NodePath np = value;
+
+ if (np == NodePath())
+ return;
+
+ Node *root = EditorNode::get_singleton()->get_tree()->get_root();
+
+ Node *np_node = root->get_node(np);
+ ERR_FAIL_COND(!np_node);
+
+ Node *edited_node = root->get_node(base);
+ ERR_FAIL_COND(!edited_node);
+
+ value = edited_node->get_path_to(np_node);
+ }
+
+ void _update_obj(const Ref<Animation> &p_anim) {
+ if (setting)
+ return;
+ if (hidden)
+ return;
+ if (!(animation == p_anim))
+ return;
+ notify_change();
+ }
+
+ void _key_ofs_changed(const Ref<Animation> &p_anim, float from, float to) {
+ if (hidden)
+ return;
+ if (!(animation == p_anim))
+ return;
+ if (from != key_ofs)
+ return;
+ key_ofs = to;
+ if (setting)
+ return;
+ notify_change();
+ }
+
+ bool _set(const StringName &p_name, const Variant &p_value) {
+
+ int key = animation->track_find_key(track, key_ofs, true);
+ ERR_FAIL_COND_V(key == -1, false);
+
+ String name = p_name;
+ if (name == "time") {
+
+ float new_time = p_value;
+ if (new_time == key_ofs)
+ return true;
+
+ int existing = animation->track_find_key(track, new_time, true);
+
+ setting = true;
+ undo_redo->create_action(TTR("Anim Change Keyframe Time"), UndoRedo::MERGE_ENDS);
+
+ Variant val = animation->track_get_key_value(track, key);
+ float trans = animation->track_get_key_transition(track, key);
+
+ undo_redo->add_do_method(animation.ptr(), "track_remove_key", track, key);
+ undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, new_time, val, trans);
+ undo_redo->add_do_method(this, "_key_ofs_changed", animation, key_ofs, new_time);
+ undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", track, new_time);
+ undo_redo->add_undo_method(animation.ptr(), "track_insert_key", track, key_ofs, val, trans);
+ undo_redo->add_undo_method(this, "_key_ofs_changed", animation, new_time, key_ofs);
+
+ if (existing != -1) {
+ Variant v = animation->track_get_key_value(track, existing);
+ float trans = animation->track_get_key_transition(track, existing);
+ undo_redo->add_undo_method(animation.ptr(), "track_insert_key", track, new_time, v, trans);
+ }
+
+ undo_redo->commit_action();
+ setting = false;
+
+ return true;
+ } else if (name == "easing") {
+
+ float val = p_value;
+ float prev_val = animation->track_get_key_transition(track, key);
+ setting = true;
+ undo_redo->create_action(TTR("Anim Change Transition"), UndoRedo::MERGE_ENDS);
+ undo_redo->add_do_method(animation.ptr(), "track_set_key_transition", track, key, val);
+ undo_redo->add_undo_method(animation.ptr(), "track_set_key_transition", track, key, prev_val);
+ undo_redo->add_do_method(this, "_update_obj", animation);
+ undo_redo->add_undo_method(this, "_update_obj", animation);
+ undo_redo->commit_action();
+ setting = false;
+ return true;
+ }
+
+ switch (animation->track_get_type(track)) {
+
+ case Animation::TYPE_TRANSFORM: {
+
+ Dictionary d_old = animation->track_get_key_value(track, key);
+ Dictionary d_new = d_old;
+ d_new[p_name] = p_value;
+ setting = true;
+ undo_redo->create_action(TTR("Anim Change Transform"));
+ undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, d_new);
+ undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, d_old);
+ undo_redo->add_do_method(this, "_update_obj", animation);
+ undo_redo->add_undo_method(this, "_update_obj", animation);
+ undo_redo->commit_action();
+ setting = false;
+ return true;
+
+ } break;
+ case Animation::TYPE_VALUE: {
+
+ if (name == "value") {
+
+ Variant value = p_value;
+
+ if (value.get_type() == Variant::NODE_PATH) {
+
+ _fix_node_path(value);
+ }
+
+ setting = true;
+ undo_redo->create_action(TTR("Anim Change Keyframe Value"), UndoRedo::MERGE_ENDS);
+ Variant prev = animation->track_get_key_value(track, key);
+ undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, value);
+ undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, prev);
+ undo_redo->add_do_method(this, "_update_obj", animation);
+ undo_redo->add_undo_method(this, "_update_obj", animation);
+ undo_redo->commit_action();
+ setting = false;
+ return true;
+ }
+
+ } break;
+ case Animation::TYPE_METHOD: {
+
+ Dictionary d_old = animation->track_get_key_value(track, key);
+ Dictionary d_new = d_old;
+
+ bool change_notify_deserved = false;
+ bool mergeable = false;
+
+ if (name == "name") {
+
+ d_new["method"] = p_value;
+ }
+
+ if (name == "arg_count") {
+
+ Vector<Variant> args = d_old["args"];
+ args.resize(p_value);
+ d_new["args"] = args;
+ change_notify_deserved = true;
+ }
+
+ if (name.begins_with("args/")) {
+
+ Vector<Variant> args = d_old["args"];
+ int idx = name.get_slice("/", 1).to_int();
+ ERR_FAIL_INDEX_V(idx, args.size(), false);
+
+ String what = name.get_slice("/", 2);
+ if (what == "type") {
+ Variant::Type t = Variant::Type(int(p_value));
+
+ if (t != args[idx].get_type()) {
+ Variant::CallError err;
+ if (Variant::can_convert(args[idx].get_type(), t)) {
+ Variant old = args[idx];
+ Variant *ptrs[1] = { &old };
+ args[idx] = Variant::construct(t, (const Variant **)ptrs, 1, err);
+ } else {
+
+ args[idx] = Variant::construct(t, NULL, 0, err);
+ }
+ change_notify_deserved = true;
+ d_new["args"] = args;
+ }
+ }
+ if (what == "value") {
+
+ Variant value = p_value;
+ if (value.get_type() == Variant::NODE_PATH) {
+
+ _fix_node_path(value);
+ }
+
+ args[idx] = value;
+ d_new["args"] = args;
+ mergeable = true;
+ }
+ }
+
+ if (mergeable)
+ undo_redo->create_action(TTR("Anim Change Call"), UndoRedo::MERGE_ENDS);
+ else
+ undo_redo->create_action(TTR("Anim Change Call"));
+
+ Variant prev = animation->track_get_key_value(track, key);
+ setting = true;
+ undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, d_new);
+ undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, d_old);
+ undo_redo->add_do_method(this, "_update_obj", animation);
+ undo_redo->add_undo_method(this, "_update_obj", animation);
+ undo_redo->commit_action();
+ setting = false;
+ if (change_notify_deserved)
+ notify_change();
+ return true;
+ } break;
+ case Animation::TYPE_BEZIER: {
+
+ if (name == "value") {
+
+ Variant value = p_value;
+
+ setting = true;
+ undo_redo->create_action(TTR("Anim Change Keyframe Value"), UndoRedo::MERGE_ENDS);
+ float prev = animation->bezier_track_get_key_value(track, key);
+ undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_value", track, key, value);
+ undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_value", track, key, prev);
+ undo_redo->add_do_method(this, "_update_obj", animation);
+ undo_redo->add_undo_method(this, "_update_obj", animation);
+ undo_redo->commit_action();
+ setting = false;
+ return true;
+ }
+ if (name == "in_handle") {
+
+ Variant value = p_value;
+
+ setting = true;
+ undo_redo->create_action(TTR("Anim Change Keyframe Value"), UndoRedo::MERGE_ENDS);
+ Vector2 prev = animation->bezier_track_get_key_in_handle(track, key);
+ undo_redo->add_do_method(animation.ptr(), "bezier_track_set_in_handle", track, key, value);
+ undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_in_handle", track, key, prev);
+ undo_redo->add_do_method(this, "_update_obj", animation);
+ undo_redo->add_undo_method(this, "_update_obj", animation);
+ undo_redo->commit_action();
+ setting = false;
+ return true;
+ }
+ if (name == "out_handle") {
+
+ Variant value = p_value;
+
+ setting = true;
+ undo_redo->create_action(TTR("Anim Change Keyframe Value"), UndoRedo::MERGE_ENDS);
+ Vector2 prev = animation->bezier_track_get_key_out_handle(track, key);
+ undo_redo->add_do_method(animation.ptr(), "bezier_track_set_out_handle", track, key, value);
+ undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_out_handle", track, key, prev);
+ undo_redo->add_do_method(this, "_update_obj", animation);
+ undo_redo->add_undo_method(this, "_update_obj", animation);
+ undo_redo->commit_action();
+ setting = false;
+ return true;
+ }
+
+ } break;
+ case Animation::TYPE_AUDIO: {
+
+ if (name == "stream") {
+
+ Ref<AudioStream> stream = p_value;
+
+ setting = true;
+ undo_redo->create_action(TTR("Anim Change Keyframe Value"), UndoRedo::MERGE_ENDS);
+ RES 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);
+ undo_redo->add_undo_method(this, "_update_obj", animation);
+ undo_redo->commit_action();
+ setting = false;
+ return true;
+ }
+ if (name == "start_offset") {
+
+ float value = p_value;
+
+ setting = true;
+ undo_redo->create_action(TTR("Anim Change Keyframe Value"), UndoRedo::MERGE_ENDS);
+ float prev = animation->audio_track_get_key_start_offset(track, key);
+ undo_redo->add_do_method(animation.ptr(), "audio_track_set_key_start_offset", track, key, value);
+ undo_redo->add_undo_method(animation.ptr(), "audio_track_set_key_start_offset", track, key, prev);
+ undo_redo->add_do_method(this, "_update_obj", animation);
+ undo_redo->add_undo_method(this, "_update_obj", animation);
+ undo_redo->commit_action();
+ setting = false;
+ return true;
+ }
+ if (name == "end_offset") {
+
+ float value = p_value;
+
+ setting = true;
+ undo_redo->create_action(TTR("Anim Change Keyframe Value"), UndoRedo::MERGE_ENDS);
+ float prev = animation->audio_track_get_key_end_offset(track, key);
+ undo_redo->add_do_method(animation.ptr(), "audio_track_set_key_end_offset", track, key, value);
+ undo_redo->add_undo_method(animation.ptr(), "audio_track_set_key_end_offset", track, key, prev);
+ undo_redo->add_do_method(this, "_update_obj", animation);
+ undo_redo->add_undo_method(this, "_update_obj", animation);
+ undo_redo->commit_action();
+ setting = false;
+ return true;
+ }
+
+ } break;
+ case Animation::TYPE_ANIMATION: {
+
+ if (name == "animation") {
+
+ StringName name = p_value;
+
+ setting = true;
+ undo_redo->create_action(TTR("Anim Change Keyframe Value"), UndoRedo::MERGE_ENDS);
+ StringName prev = animation->animation_track_get_key_animation(track, key);
+ undo_redo->add_do_method(animation.ptr(), "animation_track_set_key_animation", track, key, name);
+ undo_redo->add_undo_method(animation.ptr(), "animation_track_set_key_animation", track, key, prev);
+ undo_redo->add_do_method(this, "_update_obj", animation);
+ undo_redo->add_undo_method(this, "_update_obj", animation);
+ undo_redo->commit_action();
+ setting = false;
+ return true;
+ }
+
+ } break;
+ }
+
+ return false;
+ }
+
+ bool _get(const StringName &p_name, Variant &r_ret) const {
+
+ int key = animation->track_find_key(track, key_ofs, true);
+ ERR_FAIL_COND_V(key == -1, false);
+
+ String name = p_name;
+ if (name == "time") {
+ r_ret = key_ofs;
+ return true;
+ } else if (name == "easing") {
+ r_ret = animation->track_get_key_transition(track, key);
+ return true;
+ }
+
+ switch (animation->track_get_type(track)) {
+
+ case Animation::TYPE_TRANSFORM: {
+
+ Dictionary d = animation->track_get_key_value(track, key);
+ ERR_FAIL_COND_V(!d.has(name), false);
+ r_ret = d[p_name];
+ return true;
+
+ } break;
+ case Animation::TYPE_VALUE: {
+
+ if (name == "value") {
+ r_ret = animation->track_get_key_value(track, key);
+ return true;
+ }
+
+ } break;
+ case Animation::TYPE_METHOD: {
+
+ Dictionary d = animation->track_get_key_value(track, key);
+
+ if (name == "name") {
+
+ ERR_FAIL_COND_V(!d.has("method"), false);
+ r_ret = d["method"];
+ return true;
+ }
+
+ ERR_FAIL_COND_V(!d.has("args"), false);
+
+ Vector<Variant> args = d["args"];
+
+ if (name == "arg_count") {
+
+ r_ret = args.size();
+ return true;
+ }
+
+ if (name.begins_with("args/")) {
+
+ int idx = name.get_slice("/", 1).to_int();
+ ERR_FAIL_INDEX_V(idx, args.size(), false);
+
+ String what = name.get_slice("/", 2);
+ if (what == "type") {
+ r_ret = args[idx].get_type();
+ return true;
+ }
+ if (what == "value") {
+ r_ret = args[idx];
+ return true;
+ }
+ }
+
+ } break;
+ case Animation::TYPE_BEZIER: {
+
+ if (name == "value") {
+ r_ret = animation->bezier_track_get_key_value(track, key);
+ return true;
+ }
+ if (name == "in_handle") {
+ r_ret = animation->bezier_track_get_key_in_handle(track, key);
+ return true;
+ }
+ if (name == "out_handle") {
+ r_ret = animation->bezier_track_get_key_out_handle(track, key);
+ return true;
+ }
+
+ } break;
+ case Animation::TYPE_AUDIO: {
+
+ if (name == "stream") {
+ r_ret = animation->audio_track_get_key_stream(track, key);
+ return true;
+ }
+ if (name == "start_offset") {
+ r_ret = animation->audio_track_get_key_start_offset(track, key);
+ return true;
+ }
+ if (name == "end_offset") {
+ r_ret = animation->audio_track_get_key_end_offset(track, key);
+ return true;
+ }
+
+ } break;
+ case Animation::TYPE_ANIMATION: {
+
+ if (name == "animation") {
+ r_ret = animation->animation_track_get_key_animation(track, key);
+ return true;
+ }
+
+ } break;
+ }
+
+ return false;
+ }
+ void _get_property_list(List<PropertyInfo> *p_list) const {
+
+ if (animation.is_null())
+ return;
+
+ ERR_FAIL_INDEX(track, animation->get_track_count());
+ int key = animation->track_find_key(track, key_ofs, true);
+ ERR_FAIL_COND(key == -1);
+
+ p_list->push_back(PropertyInfo(Variant::REAL, "time", PROPERTY_HINT_RANGE, "0," + rtos(animation->get_length()) + ",0.01"));
+
+ switch (animation->track_get_type(track)) {
+
+ case Animation::TYPE_TRANSFORM: {
+
+ p_list->push_back(PropertyInfo(Variant::VECTOR3, "location"));
+ p_list->push_back(PropertyInfo(Variant::QUAT, "rotation"));
+ p_list->push_back(PropertyInfo(Variant::VECTOR3, "scale"));
+
+ } 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";
+ p_list->push_back(pi);
+ } else {
+
+ PropertyHint hint = PROPERTY_HINT_NONE;
+ String 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();
+ }
+ }
+
+ if (v.get_type() != Variant::NIL)
+ p_list->push_back(PropertyInfo(v.get_type(), "value", hint, hint_string));
+ }
+
+ } break;
+ case Animation::TYPE_METHOD: {
+
+ p_list->push_back(PropertyInfo(Variant::STRING, "name"));
+ p_list->push_back(PropertyInfo(Variant::INT, "arg_count", PROPERTY_HINT_RANGE, "0,5,1"));
+
+ Dictionary d = animation->track_get_key_value(track, key);
+ ERR_FAIL_COND(!d.has("args"));
+ Vector<Variant> args = d["args"];
+ String vtypes;
+ for (int i = 0; i < Variant::VARIANT_MAX; i++) {
+
+ if (i > 0)
+ vtypes += ",";
+ vtypes += Variant::get_type_name(Variant::Type(i));
+ }
+
+ for (int i = 0; i < args.size(); i++) {
+
+ p_list->push_back(PropertyInfo(Variant::INT, "args/" + itos(i) + "/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"));
+ }
+
+ } break;
+ case Animation::TYPE_BEZIER: {
+
+ p_list->push_back(PropertyInfo(Variant::REAL, "value"));
+ p_list->push_back(PropertyInfo(Variant::VECTOR2, "in_handle"));
+ p_list->push_back(PropertyInfo(Variant::VECTOR2, "out_handle"));
+
+ } break;
+ case Animation::TYPE_AUDIO: {
+
+ p_list->push_back(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"));
+ p_list->push_back(PropertyInfo(Variant::REAL, "start_offset", PROPERTY_HINT_RANGE, "0,3600,0.01,or_greater"));
+ p_list->push_back(PropertyInfo(Variant::REAL, "end_offset", PROPERTY_HINT_RANGE, "0,3600,0.01,or_greater"));
+
+ } break;
+ case Animation::TYPE_ANIMATION: {
+
+ String animations;
+
+ if (root_path && root_path->has_node(animation->track_get_path(track))) {
+
+ AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(root_path->get_node(animation->track_get_path(track)));
+ if (ap) {
+ List<StringName> anims;
+ ap->get_animation_list(&anims);
+ for (List<StringName>::Element *E = anims.front(); E; E = E->next()) {
+ if (animations != String()) {
+ animations += ",";
+ }
+
+ animations += String(E->get());
+ }
+ }
+ }
+
+ if (animations != String()) {
+ animations += ",";
+ }
+ animations += "[stop]";
+
+ p_list->push_back(PropertyInfo(Variant::STRING, "animation", PROPERTY_HINT_ENUM, animations));
+
+ } break;
+ }
+
+ if (animation->track_get_type(track) == Animation::TYPE_VALUE) {
+ p_list->push_back(PropertyInfo(Variant::REAL, "easing", PROPERTY_HINT_EXP_EASING));
+ }
+ }
+
+ UndoRedo *undo_redo;
+ Ref<Animation> animation;
+ int track;
+ float key_ofs;
+ Node *root_path;
+
+ PropertyInfo hint;
+ NodePath base;
+
+ void notify_change() {
+
+ _change_notify();
+ }
+
+ AnimationTrackKeyEdit() {
+ hidden = true;
+ key_ofs = 0;
+ track = -1;
+ setting = false;
+ root_path = NULL;
+ }
+};
+
+void AnimationTimelineEdit::_zoom_changed(double) {
+
+ update();
+ emit_signal("zoom_changed");
+}
+
+float AnimationTimelineEdit::get_zoom_scale() const {
+
+ float zv = zoom->get_value();
+ if (zv < 1) {
+ zv = 1.0 - zv;
+ return Math::pow(1.0f + zv, 8.0f) * 100;
+ } else {
+ return 1.0 / Math::pow(zv, 8.0f) * 100;
+ }
+}
+
+void AnimationTimelineEdit::_anim_length_changed(double p_new_len) {
+
+ if (editing)
+ return;
+
+ editing = true;
+ *block_animation_update_ptr = true;
+ undo_redo->create_action("Change animation length");
+ undo_redo->add_do_method(animation.ptr(), "set_length", p_new_len);
+ undo_redo->add_undo_method(animation.ptr(), "set_length", animation->get_length());
+ undo_redo->commit_action();
+ *block_animation_update_ptr = false;
+ editing = false;
+ update();
+
+ emit_signal("length_changed", p_new_len);
+}
+
+void AnimationTimelineEdit::_anim_loop_pressed() {
+
+ *block_animation_update_ptr = true;
+ undo_redo->create_action("Change animation loop");
+ undo_redo->add_do_method(animation.ptr(), "set_loop", loop->is_pressed());
+ undo_redo->add_undo_method(animation.ptr(), "set_loop", animation->has_loop());
+ undo_redo->commit_action();
+ *block_animation_update_ptr = false;
+}
+
+int AnimationTimelineEdit::get_buttons_width() const {
+
+ Ref<Texture> interp_mode = get_icon("TrackContinuous", "EditorIcons");
+ Ref<Texture> interp_type = get_icon("InterpRaw", "EditorIcons");
+ Ref<Texture> loop_type = get_icon("InterpWrapClamp", "EditorIcons");
+ Ref<Texture> remove_icon = get_icon("Remove", "EditorIcons");
+ Ref<Texture> down_icon = get_icon("select_arrow", "Tree");
+
+ int total_w = interp_mode->get_width() + interp_type->get_width() + loop_type->get_width() + remove_icon->get_width();
+ total_w += (down_icon->get_width() + 4 * EDSCALE) * 4;
+
+ return total_w;
+}
+
+int AnimationTimelineEdit::get_name_limit() const {
+
+ Ref<Texture> hsize_icon = get_icon("Hsize", "EditorIcons");
+
+ int limit = MAX(name_limit, add_track->get_minimum_size().width + hsize_icon->get_width());
+
+ limit = MIN(limit, get_size().width - get_buttons_width() - 1);
+
+ return limit;
+}
+
+void AnimationTimelineEdit::_notification(int p_what) {
+
+ if (p_what == NOTIFICATION_ENTER_TREE) {
+ add_track->set_icon(get_icon("Add", "EditorIcons"));
+ loop->set_icon(get_icon("Loop", "EditorIcons"));
+ time_icon->set_texture(get_icon("Time", "EditorIcons"));
+
+ add_track->get_popup()->clear();
+ add_track->get_popup()->add_icon_item(get_icon("KeyValue", "EditorIcons"), TTR("Property Track"));
+ add_track->get_popup()->add_icon_item(get_icon("KeyXform", "EditorIcons"), TTR("3D Transform Track"));
+ add_track->get_popup()->add_icon_item(get_icon("KeyCall", "EditorIcons"), TTR("Call Method Track"));
+ add_track->get_popup()->add_icon_item(get_icon("KeyBezier", "EditorIcons"), TTR("Bezier Curve Track"));
+ add_track->get_popup()->add_icon_item(get_icon("KeyAudio", "EditorIcons"), TTR("Audio Playback Track"));
+ add_track->get_popup()->add_icon_item(get_icon("KeyAnimation", "EditorIcons"), TTR("Animation Playback Track"));
+ }
+
+ 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));
+ }
+ if (p_what == NOTIFICATION_DRAW) {
+
+ int key_range = get_size().width - get_buttons_width() - get_name_limit();
+
+ if (!animation.is_valid())
+ return;
+
+ Ref<Font> font = get_font("font", "Label");
+ Color color = get_color("font_color", "Label");
+
+ int zoomw = key_range;
+ float scale = get_zoom_scale();
+ int h = get_size().height;
+
+ float l = animation->get_length();
+ if (l <= 0)
+ l = 0.001; //avoid crashor
+
+ int end_px = (l - get_value()) * scale;
+ int begin_px = -get_value() * scale;
+ Color notimecol = get_color("dark_color_2", "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);
+
+ 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);
+ }
+ }
+
+ Ref<Texture> hsize_icon = get_icon("Hsize", "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 keys_from = get_value();
+ float keys_to = keys_from + zoomw / scale;
+
+ {
+ 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 (animation->track_get_type(i) == Animation::TYPE_BEZIER) {
+ beg += animation->bezier_track_get_key_in_handle(i, 0).x;
+ }
+
+ if (beg < time_min)
+ time_min = beg;
+
+ float end = animation->track_get_key_time(i, animation->track_get_key_count(i) - 1);
+ if (animation->track_get_type(i) == Animation::TYPE_BEZIER) {
+ end += animation->bezier_track_get_key_out_handle(i, animation->track_get_key_count(i) - 1).x;
+ }
+
+ if (end > time_max)
+ time_max = end;
+ }
+ }
+
+ float extra = (zoomw / scale) * 0.5;
+
+ //if (time_min < -0.001)
+ // time_min -= extra;
+ time_max += extra;
+ set_min(time_min);
+ set_max(time_max);
+
+ if (zoomw / scale < (time_max - time_min)) {
+ hscroll->show();
+
+ } else {
+
+ hscroll->hide();
+ }
+ }
+
+ set_page(zoomw / scale);
+
+ 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('.').width;
+ int max_digit_width = font->get_char_size('0').width;
+ for (int i = 1; i <= 9; i++) {
+ const int digit_width = font->get_char_size('0' + i).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;
+
+ 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;
+ }
+
+ 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);
+ draw_string(font, Point2(get_name_limit() + i + 3, (h - font->get_height()) / 2 + font->get_ascent()).floor(), String::num((scd - (scd % step)) / double(SC_ADJ), decimals), sub ? color_time_dec : color_time_sec, zoomw - i);
+ }
+ }
+
+ draw_line(Vector2(0, get_size().height), get_size(), linecolor);
+ }
+}
+
+void AnimationTimelineEdit::set_animation(const Ref<Animation> &p_animation) {
+ animation = p_animation;
+ if (animation.is_valid()) {
+ len_hb->show();
+ add_track->show();
+ } else {
+ len_hb->hide();
+ add_track->hide();
+ }
+ update();
+ update_values();
+}
+
+Size2 AnimationTimelineEdit::get_minimum_size() const {
+
+ Size2 ms = add_track->get_minimum_size();
+ Ref<Font> font = get_font("font", "Label");
+ ms.height = MAX(ms.height, font->get_height());
+ ms.width = get_buttons_width() + add_track->get_minimum_size().width + get_icon("Hsize", "EditorIcons")->get_width() + 2;
+ return ms;
+}
+
+void AnimationTimelineEdit::set_block_animation_update_ptr(bool *p_block_ptr) {
+ block_animation_update_ptr = p_block_ptr;
+}
+
+void AnimationTimelineEdit::set_undo_redo(UndoRedo *p_undo_redo) {
+ undo_redo = p_undo_redo;
+}
+
+void AnimationTimelineEdit::set_zoom(Range *p_zoom) {
+ zoom = p_zoom;
+ zoom->connect("value_changed", this, "_zoom_changed");
+}
+
+void AnimationTimelineEdit::set_play_position(float p_pos) {
+
+ play_position_pos = p_pos;
+ play_position->update();
+}
+
+float AnimationTimelineEdit::get_play_position() const {
+ return play_position_pos;
+}
+
+void AnimationTimelineEdit::update_play_position() {
+ play_position->update();
+}
+
+void AnimationTimelineEdit::update_values() {
+
+ if (!animation.is_valid() || editing)
+ return;
+
+ editing = true;
+ length->set_value(animation->get_length());
+ loop->set_pressed(animation->has_loop());
+ editing = false;
+}
+
+void AnimationTimelineEdit::_play_position_draw() {
+
+ if (!animation.is_valid() || play_position_pos < 0)
+ return;
+
+ float scale = get_zoom_scale();
+ int h = play_position->get_size().height;
+
+ int px = (-get_value() + play_position_pos) * scale + get_name_limit();
+
+ if (px >= get_name_limit() && px < (play_position->get_size().width - get_buttons_width())) {
+ Color color = get_color("accent_color", "Editor");
+ play_position->draw_line(Point2(px, 0), Point2(px, h), color);
+ }
+}
+
+void AnimationTimelineEdit::_gui_input(const Ref<InputEvent> &p_event) {
+
+ Ref<InputEventMouseButton> mb = p_event;
+
+ if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT && hsize_rect.has_point(mb->get_position())) {
+
+ dragging_hsize = true;
+ dragging_hsize_from = mb->get_position().x;
+ dragging_hsize_at = name_limit;
+ }
+
+ if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT && dragging_hsize) {
+ dragging_hsize = false;
+ }
+ if (mb.is_valid() && mb->get_position().x > get_name_limit() && mb->get_position().x < (get_size().width - get_buttons_width())) {
+
+ if (!panning_timeline && mb->get_button_index() == BUTTON_LEFT) {
+ int x = mb->get_position().x - get_name_limit();
+
+ float ofs = x / get_zoom_scale();
+ emit_signal("timeline_changed", ofs, false);
+ dragging_timeline = true;
+ }
+ if (!dragging_timeline && mb->get_button_index() == BUTTON_MIDDLE) {
+ int x = mb->get_position().x - get_name_limit();
+ panning_timeline_from = x / get_zoom_scale();
+ panning_timeline = true;
+ panning_timeline_at = get_value();
+ }
+ }
+
+ if (dragging_timeline && mb.is_valid() && mb->get_button_index() == BUTTON_LEFT && !mb->is_pressed()) {
+ dragging_timeline = false;
+ }
+
+ if (panning_timeline && mb.is_valid() && mb->get_button_index() == BUTTON_MIDDLE && !mb->is_pressed()) {
+ panning_timeline = false;
+ }
+
+ Ref<InputEventMouseMotion> mm = p_event;
+
+ if (mm.is_valid()) {
+
+ if (dragging_hsize) {
+ int ofs = mm->get_position().x - dragging_hsize_from;
+ name_limit = dragging_hsize_at + ofs;
+ update();
+ emit_signal("name_limit_changed");
+ play_position->update();
+ }
+ if (dragging_timeline) {
+ int x = mm->get_position().x - get_name_limit();
+ float ofs = x / get_zoom_scale();
+ emit_signal("timeline_changed", ofs, false);
+ }
+ if (panning_timeline) {
+ int x = mm->get_position().x - get_name_limit();
+ float ofs = x / get_zoom_scale();
+ float diff = ofs - panning_timeline_from;
+ set_value(panning_timeline_at - diff);
+ }
+ }
+}
+
+void AnimationTimelineEdit::set_hscroll(HScrollBar *p_hscroll) {
+
+ hscroll = p_hscroll;
+}
+
+void AnimationTimelineEdit::_track_added(int p_track) {
+ emit_signal("track_added", p_track);
+}
+
+void AnimationTimelineEdit::_bind_methods() {
+ ClassDB::bind_method("_zoom_changed", &AnimationTimelineEdit::_zoom_changed);
+ ClassDB::bind_method("_anim_length_changed", &AnimationTimelineEdit::_anim_length_changed);
+ ClassDB::bind_method("_anim_loop_pressed", &AnimationTimelineEdit::_anim_loop_pressed);
+ ClassDB::bind_method("_play_position_draw", &AnimationTimelineEdit::_play_position_draw);
+ ClassDB::bind_method("_gui_input", &AnimationTimelineEdit::_gui_input);
+ ClassDB::bind_method("_track_added", &AnimationTimelineEdit::_track_added);
+
+ ADD_SIGNAL(MethodInfo("zoom_changed"));
+ ADD_SIGNAL(MethodInfo("name_limit_changed"));
+ ADD_SIGNAL(MethodInfo("timeline_changed", PropertyInfo(Variant::REAL, "position"), PropertyInfo(Variant::BOOL, "drag")));
+ ADD_SIGNAL(MethodInfo("track_added", PropertyInfo(Variant::INT, "track")));
+ ADD_SIGNAL(MethodInfo("length_changed", PropertyInfo(Variant::REAL, "size")));
+}
+
+AnimationTimelineEdit::AnimationTimelineEdit() {
+
+ block_animation_update_ptr = NULL;
+ editing = false;
+ name_limit = 150;
+ zoom = NULL;
+
+ play_position_pos = 0;
+ play_position = memnew(Control);
+ play_position->set_mouse_filter(MOUSE_FILTER_PASS);
+ add_child(play_position);
+ play_position->set_anchors_and_margins_preset(PRESET_WIDE);
+ play_position->connect("draw", this, "_play_position_draw");
+
+ add_track = memnew(MenuButton);
+ add_track->set_position(Vector2(0, 0));
+ add_child(add_track);
+ add_track->set_text(TTR("Add Track"));
+
+ len_hb = memnew(HBoxContainer);
+
+ Control *expander = memnew(Control);
+ expander->set_h_size_flags(SIZE_EXPAND_FILL);
+ len_hb->add_child(expander);
+ time_icon = memnew(TextureRect);
+ time_icon->set_v_size_flags(SIZE_SHRINK_CENTER);
+ time_icon->set_tooltip(TTR("Animation Length Time (seconds)"));
+ len_hb->add_child(time_icon);
+ length = memnew(EditorSpinSlider);
+ length->set_min(0);
+ length->set_max(3600);
+ length->set_step(0.01);
+ length->set_allow_greater(true);
+ length->set_custom_minimum_size(Vector2(70 * EDSCALE, 0));
+ length->set_hide_slider(true);
+ length->set_tooltip(TTR("Animation Length Time (seconds)"));
+ length->connect("value_changed", this, "_anim_length_changed");
+ len_hb->add_child(length);
+ loop = memnew(ToolButton);
+ loop->set_tooltip(TTR("Animation Looping"));
+ loop->connect("pressed", this, "_anim_loop_pressed");
+ loop->set_toggle_mode(true);
+ len_hb->add_child(loop);
+ add_child(len_hb);
+
+ add_track->hide();
+ add_track->get_popup()->connect("index_pressed", this, "_track_added");
+ len_hb->hide();
+
+ panning_timeline = false;
+ dragging_timeline = false;
+ dragging_hsize = false;
+}
+
+////////////////////////////////////
+
+void AnimationTrackEdit::_notification(int p_what) {
+ if (p_what == NOTIFICATION_DRAW) {
+ if (animation.is_null())
+ return;
+ ERR_FAIL_INDEX(track, animation->get_track_count());
+
+ int limit = timeline->get_name_limit();
+
+ if (has_focus()) {
+ Color accent = get_color("accent_color", "Editor");
+ accent.a *= 0.7;
+ draw_rect(Rect2(Point2(), get_size()), accent, false);
+ }
+
+ Ref<Font> font = get_font("font", "Label");
+ Color color = get_color("font_color", "Label");
+ Ref<Texture> type_icons[6] = {
+ get_icon("KeyValue", "EditorIcons"),
+ get_icon("KeyXform", "EditorIcons"),
+ get_icon("KeyCall", "EditorIcons"),
+ get_icon("KeyBezier", "EditorIcons"),
+ get_icon("KeyAudio", "EditorIcons"),
+ get_icon("KeyAnimation", "EditorIcons")
+ };
+ int hsep = get_constant("hseparation", "ItemList");
+ Color linecolor = color;
+ linecolor.a = 0.2;
+
+ // NAMES AND ICONS //
+
+ {
+
+ Ref<Texture> check = animation->track_is_enabled(track) ? get_icon("checked", "CheckBox") : get_icon("unchecked", "CheckBox");
+
+ int ofs = in_group ? check->get_width() : 0; //not the best reference for margin but..
+
+ 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<Texture> 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;
+
+ NodePath path = animation->track_get_path(track);
+
+ Node *node = NULL;
+
+ if (root && root->has_node(path)) {
+ node = root->get_node(path);
+ }
+
+ String text;
+ Color text_color = color;
+ if (node && EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) {
+ text_color = get_color("accent_color", "Editor");
+ }
+
+ 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 {
+ Vector<StringName> sn = path.get_subnames();
+ for (int i = 0; i < sn.size(); i++) {
+ if (i > 0) {
+ text += ".";
+ }
+ text += sn[i];
+ }
+ }
+ text_color.a *= 0.7;
+ } else if (node) {
+ Ref<Texture> icon;
+ if (has_icon(node->get_class(), "EditorIcons")) {
+ icon = get_icon(node->get_class(), "EditorIcons");
+ } else {
+ icon = get_icon("Node", "EditorIcons");
+ }
+
+ draw_texture(icon, Point2(ofs, int(get_size().height - icon->get_height()) / 2));
+ icon_cache = icon;
+
+ text = node->get_name();
+ ofs += hsep;
+ ofs += icon->get_width();
+ Vector<StringName> sn = path.get_subnames();
+ for (int i = 0; i < sn.size(); i++) {
+ text += ".";
+ text += sn[i];
+ }
+ } else {
+ icon_cache = type_icon;
+
+ text = path;
+ }
+
+ path_cache = text;
+
+ path_rect = Rect2(ofs, 0, limit - ofs - hsep, get_size().height);
+
+ Vector2 string_pos = Point2(ofs, (get_size().height - font->get_height()) / 2 + font->get_ascent());
+ string_pos = string_pos.floor();
+ draw_string(font, string_pos, text, text_color, limit - ofs - hsep);
+
+ draw_line(Point2(limit, 0), Point2(limit, get_size().height), linecolor);
+ }
+
+ // KEYFAMES //
+
+ draw_bg(limit, get_size().width - timeline->get_buttons_width());
+
+ {
+
+ 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->get_moving_selection_offset();
+ }
+ 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->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(i, scale, int(offset), editor->is_key_selected(track, i), limit, limit_end);
+ }
+ }
+
+ draw_fg(limit, get_size().width - timeline->get_buttons_width());
+
+ // BUTTONS //
+ {
+
+ Ref<Texture> wrap_icon[2] = {
+ get_icon("InterpWrapClamp", "EditorIcons"),
+ get_icon("InterpWrapLoop", "EditorIcons"),
+ };
+
+ Ref<Texture> interp_icon[3] = {
+ get_icon("InterpRaw", "EditorIcons"),
+ get_icon("InterpLinear", "EditorIcons"),
+ get_icon("InterpCubic", "EditorIcons")
+ };
+ Ref<Texture> cont_icon[4] = {
+ get_icon("TrackContinuous", "EditorIcons"),
+ get_icon("TrackDiscrete", "EditorIcons"),
+ get_icon("TrackTrigger", "EditorIcons"),
+ get_icon("TrackCapture", "EditorIcons")
+ };
+
+ int ofs = get_size().width - timeline->get_buttons_width();
+
+ Ref<Texture> down_icon = get_icon("select_arrow", "Tree");
+
+ draw_line(Point2(ofs, 0), Point2(ofs, get_size().height), linecolor);
+
+ ofs += hsep;
+ {
+ //callmode
+
+ Animation::UpdateMode update_mode;
+
+ 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<Texture> update_icon = cont_icon[update_mode];
+
+ 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_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<Texture> bezier_icon = get_icon("EditBezier", "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();
+ }
+
+ ofs += down_icon->get_width();
+ draw_line(Point2(ofs + hsep * 0.5, 0), Point2(ofs + hsep * 0.5, get_size().height), linecolor);
+ ofs += hsep;
+ }
+
+ {
+ //interp
+
+ Animation::InterpolationType interp_mode = animation->track_get_interpolation_type(track);
+
+ Ref<Texture> icon = interp_icon[interp_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();
+
+ if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_TRANSFORM) {
+ 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;
+ interp_mode_rect.size.x += hsep;
+
+ if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_TRANSFORM) {
+ 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();
+ }
+
+ ofs += down_icon->get_width();
+ draw_line(Point2(ofs + hsep * 0.5, 0), Point2(ofs + hsep * 0.5, get_size().height), linecolor);
+ ofs += hsep;
+ }
+
+ {
+ //loop
+
+ bool loop_wrap = animation->track_get_interpolation_loop_wrap(track);
+
+ Ref<Texture> icon = wrap_icon[loop_wrap ? 1 : 0];
+
+ loop_mode_rect.position.x = ofs;
+ loop_mode_rect.position.y = int(get_size().height - icon->get_height()) / 2;
+ loop_mode_rect.size = icon->get_size();
+
+ if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_TRANSFORM) {
+ draw_texture(icon, loop_mode_rect.position);
+ }
+
+ loop_mode_rect.position.y = 0;
+ loop_mode_rect.size.y = get_size().height;
+
+ ofs += icon->get_width() + hsep;
+ loop_mode_rect.size.x += hsep;
+
+ if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_TRANSFORM) {
+ draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2));
+ loop_mode_rect.size.x += down_icon->get_width();
+ } else {
+ loop_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);
+ ofs += hsep;
+ }
+
+ {
+ //erase
+
+ Ref<Texture> icon = get_icon("Remove", "EditorIcons");
+
+ 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();
+
+ draw_texture(icon, remove_rect.position);
+ }
+ }
+
+ if (in_group) {
+ draw_line(Vector2(timeline->get_name_limit(), get_size().height), get_size(), linecolor);
+ } else {
+ draw_line(Vector2(0, get_size().height), get_size(), linecolor);
+ }
+
+ if (dropping_at != 0) {
+ Color drop_color = get_color("accent_color", "Editor");
+ if (dropping_at < 0) {
+ draw_line(Vector2(0, 0), Vector2(get_size().width, 0), drop_color);
+ } else {
+ draw_line(Vector2(0, get_size().height), get_size(), drop_color);
+ }
+ }
+ }
+
+ if (p_what == NOTIFICATION_MOUSE_EXIT || p_what == NOTIFICATION_DRAG_END) {
+ cancel_drop();
+ }
+}
+
+int AnimationTrackEdit::get_key_height() const {
+ if (!animation.is_valid())
+ return 0;
+
+ return type_icon->get_height();
+}
+Rect2 AnimationTrackEdit::get_key_rect(int p_index, float p_pixels_sec) {
+
+ if (!animation.is_valid())
+ return Rect2();
+ Rect2 rect = Rect2(-type_icon->get_width() / 2, 0, type_icon->get_width(), get_size().height);
+
+ //make it a big easier to click
+ rect.position.x -= rect.size.x * 0.5;
+ rect.size.x *= 2;
+ return rect;
+}
+
+bool AnimationTrackEdit::is_key_selectable_by_distance() const {
+ return true;
+}
+
+void AnimationTrackEdit::draw_key_link(int p_index, float p_pixels_sec, int p_x, int p_next_x, int p_clip_left, int p_clip_right) {
+ if (p_next_x < p_clip_left)
+ return;
+ if (p_x > p_clip_right)
+ return;
+
+ Variant current = animation->track_get_key_value(get_track(), p_index);
+ Variant next = animation->track_get_key_value(get_track(), p_index + 1);
+ if (current != next)
+ return;
+
+ Color color = get_color("font_color", "Label");
+ color.a = 0.5;
+
+ int from_x = MAX(p_x, p_clip_left);
+ int to_x = MIN(p_next_x, p_clip_right);
+
+ draw_line(Point2(from_x + 1, get_size().height / 2), Point2(to_x, get_size().height / 2), color, 2);
+}
+
+void AnimationTrackEdit::draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) {
+
+ if (!animation.is_valid())
+ return;
+
+ if (p_x < p_clip_left || p_x > p_clip_right)
+ return;
+
+ Vector2 ofs(p_x - type_icon->get_width() / 2, int(get_size().height - type_icon->get_height()) / 2);
+
+ if (animation->track_get_type(track) == Animation::TYPE_METHOD) {
+ Ref<Font> font = get_font("font", "Label");
+ Color color = get_color("font_color", "Label");
+ color.a = 0.5;
+
+ Dictionary d = animation->track_get_key_value(track, p_index);
+ String text;
+
+ if (d.has("method"))
+ text += String(d["method"]);
+ text += "(";
+ Vector<Variant> args;
+ if (d.has("args"))
+ args = d["args"];
+ for (int i = 0; i < args.size(); i++) {
+
+ if (i > 0)
+ text += ", ";
+ text += String(args[i]);
+ }
+ text += ")";
+
+ int limit = MAX(0, p_clip_right - p_x - type_icon->get_width());
+ if (limit > 0) {
+ draw_string(font, Vector2(p_x + type_icon->get_width(), int(get_size().height - font->get_height()) / 2 + font->get_ascent()), text, color, limit);
+ }
+ }
+ if (p_selected) {
+ draw_texture(selected_icon, ofs);
+ } else {
+ draw_texture(type_icon, ofs);
+ }
+}
+
+//helper
+void AnimationTrackEdit::draw_rect_clipped(const Rect2 &p_rect, const Color &p_color, bool p_filled) {
+
+ int clip_left = timeline->get_name_limit();
+ int clip_right = get_size().width - timeline->get_buttons_width();
+
+ if (p_rect.position.x > clip_right)
+ return;
+ if (p_rect.position.x + p_rect.size.x < clip_left)
+ return;
+ Rect2 clip = Rect2(clip_left, 0, clip_right - clip_left, get_size().height);
+ draw_rect(clip.clip(p_rect), p_color, p_filled);
+}
+
+void AnimationTrackEdit::draw_bg(int p_clip_left, int p_clip_right) {
+}
+
+void AnimationTrackEdit::draw_fg(int p_clip_left, int p_clip_right) {
+}
+
+void AnimationTrackEdit::draw_texture_clipped(const Ref<Texture> &p_texture, const Vector2 &p_pos) {
+
+ draw_texture_region_clipped(p_texture, Rect2(p_pos, p_texture->get_size()), Rect2(Point2(), p_texture->get_size()));
+}
+
+void AnimationTrackEdit::draw_texture_region_clipped(const Ref<Texture> &p_texture, const Rect2 &p_rect, const Rect2 &p_region) {
+
+ int clip_left = timeline->get_name_limit();
+ int clip_right = get_size().width - timeline->get_buttons_width();
+
+ //clip left and right
+ if (clip_left > p_rect.position.x + p_rect.size.x)
+ return;
+ if (clip_right < p_rect.position.x)
+ return;
+
+ Rect2 rect = p_rect;
+ Rect2 region = p_region;
+
+ if (clip_left > rect.position.x) {
+ int rect_pixels = (clip_left - rect.position.x);
+ int region_pixels = rect_pixels * region.size.x / rect.size.x;
+
+ rect.position.x += rect_pixels;
+ rect.size.x -= rect_pixels;
+
+ region.position.x += region_pixels;
+ region.size.x -= region_pixels;
+ }
+
+ if (clip_right < rect.position.x + rect.size.x) {
+
+ int rect_pixels = rect.position.x + rect.size.x - clip_right;
+ int region_pixels = rect_pixels * region.size.x / rect.size.x;
+
+ rect.size.x -= rect_pixels;
+ region.size.x -= region_pixels;
+ }
+
+ draw_texture_rect_region(p_texture, rect, region);
+}
+
+int AnimationTrackEdit::get_track() const {
+ return track;
+}
+
+Ref<Animation> AnimationTrackEdit::get_animation() const {
+ return animation;
+}
+
+void AnimationTrackEdit::set_animation_and_track(const Ref<Animation> &p_animation, int p_track) {
+
+ animation = p_animation;
+ track = p_track;
+ update();
+
+ Ref<Texture> type_icons[6] = {
+ get_icon("KeyValue", "EditorIcons"),
+ get_icon("KeyXform", "EditorIcons"),
+ get_icon("KeyCall", "EditorIcons"),
+ get_icon("KeyBezier", "EditorIcons"),
+ get_icon("KeyAudio", "EditorIcons"),
+ get_icon("KeyAnimation", "EditorIcons")
+ };
+
+ ERR_FAIL_INDEX(track, animation->get_track_count());
+
+ type_icon = type_icons[animation->track_get_type(track)];
+ selected_icon = get_icon("KeySelected", "EditorIcons");
+}
+
+Size2 AnimationTrackEdit::get_minimum_size() const {
+
+ Ref<Texture> texture = get_icon("Object", "EditorIcons");
+ Ref<Font> font = get_font("font", "Label");
+ int separation = get_constant("vseparation", "ItemList");
+
+ int max_h = MAX(texture->get_height(), font->get_height());
+ max_h = MAX(max_h, get_key_height());
+
+ return Vector2(1, max_h + separation);
+}
+
+void AnimationTrackEdit::set_undo_redo(UndoRedo *p_undo_redo) {
+ undo_redo = p_undo_redo;
+}
+
+void AnimationTrackEdit::set_timeline(AnimationTimelineEdit *p_timeline) {
+ timeline = p_timeline;
+ timeline->connect("zoom_changed", this, "_zoom_changed");
+}
+void AnimationTrackEdit::set_editor(AnimationTrackEditor *p_editor) {
+ editor = p_editor;
+}
+
+void AnimationTrackEdit::_play_position_draw() {
+
+ if (!animation.is_valid() || play_position_pos < 0)
+ return;
+
+ float scale = timeline->get_zoom_scale();
+ int h = get_size().height;
+
+ int px = (-timeline->get_value() + play_position_pos) * scale + timeline->get_name_limit();
+
+ if (px >= timeline->get_name_limit() && px < (get_size().width - timeline->get_buttons_width())) {
+ Color color = get_color("accent_color", "Editor");
+ play_position->draw_line(Point2(px, 0), Point2(px, h), color);
+ }
+}
+
+void AnimationTrackEdit::set_play_position(float p_pos) {
+
+ play_position_pos = p_pos;
+ play_position->update();
+}
+
+void AnimationTrackEdit::update_play_position() {
+ play_position->update();
+}
+
+void AnimationTrackEdit::set_root(Node *p_root) {
+ root = p_root;
+}
+void AnimationTrackEdit::_zoom_changed() {
+ update();
+}
+
+void AnimationTrackEdit::_path_entered(const String &p_text) {
+
+ *block_animation_update_ptr = true;
+ undo_redo->create_action("Change Track Path");
+ undo_redo->add_do_method(animation.ptr(), "track_set_path", track, p_text);
+ undo_redo->add_undo_method(animation.ptr(), "track_set_path", track, animation->track_get_path(track));
+ undo_redo->commit_action();
+ *block_animation_update_ptr = false;
+ update();
+ path->hide();
+}
+
+String AnimationTrackEdit::get_tooltip(const Point2 &p_pos) const {
+
+ if (check_rect.has_point(p_pos)) {
+ return TTR("Toggle this track on/off");
+ }
+
+ if (path_rect.has_point(p_pos)) {
+ return animation->track_get_path(track);
+ }
+
+ if (update_mode_rect.has_point(p_pos)) {
+ return TTR("Update Mode (How this property is set).");
+ }
+
+ if (interp_mode_rect.has_point(p_pos)) {
+ return TTR("Interpolation Mode");
+ }
+
+ if (loop_mode_rect.has_point(p_pos)) {
+ return TTR("Loop Wrap Mode (Interpolate end with beginning on loop");
+ }
+
+ if (remove_rect.has_point(p_pos)) {
+ return TTR("Remove this track");
+ }
+
+ if (p_pos.x >= timeline->get_name_limit() && p_pos.x <= (get_size().width - timeline->get_buttons_width())) {
+
+ int key_idx = -1;
+ float key_distance = 1e20;
+
+ for (int i = animation->track_get_key_count(track) - 1; i >= 0; i--) { //select should happen in the opposite order of drawing for more accurate overlap select
+
+ Rect2 rect = const_cast<AnimationTrackEdit *>(this)->get_key_rect(i, timeline->get_zoom_scale());
+ float offset = animation->track_get_key_time(track, i) - timeline->get_value();
+ offset = offset * timeline->get_zoom_scale() + timeline->get_name_limit();
+ rect.position.x += offset;
+
+ if (rect.has_point(p_pos)) {
+
+ if (const_cast<AnimationTrackEdit *>(this)->is_key_selectable_by_distance()) {
+ float distance = ABS(offset - p_pos.x);
+ if (key_idx == -1 || distance < key_distance) {
+ key_idx = i;
+ key_distance = distance;
+ }
+ } else {
+ //first one does it
+ break;
+ }
+ }
+ }
+
+ if (key_idx != -1) {
+
+ String text = TTR("Time (s): ") + rtos(animation->track_get_key_time(track, key_idx)) + "\n";
+ switch (animation->track_get_type(track)) {
+
+ case Animation::TYPE_TRANSFORM: {
+
+ Dictionary d = animation->track_get_key_value(track, key_idx);
+ if (d.has("location"))
+ text += "Pos: " + String(d["location"]) + "\n";
+ if (d.has("rotation"))
+ text += "Rot: " + String(d["rotation"]) + "\n";
+ if (d.has("scale"))
+ text += "Scale: " + String(d["scale"]) + "\n";
+ } break;
+ case Animation::TYPE_VALUE: {
+
+ Variant v = animation->track_get_key_value(track, key_idx);
+ //text+="value: "+String(v)+"\n";
+
+ bool prop_exists = false;
+ Variant::Type valid_type = Variant::NIL;
+ Object *obj = NULL;
+
+ RES res;
+ Vector<StringName> leftover_path;
+ Node *node = root->get_node_and_resource(animation->track_get_path(track), res, leftover_path);
+
+ if (res.is_valid()) {
+ obj = res.ptr();
+ } else if (node) {
+ obj = node;
+ }
+
+ if (obj) {
+ valid_type = obj->get_static_property_type_indexed(leftover_path, &prop_exists);
+ }
+
+ text += "Type: " + Variant::get_type_name(v.get_type()) + "\n";
+ if (prop_exists && !Variant::can_convert(v.get_type(), valid_type)) {
+ text += "Value: " + String(v) + " (Invalid, expected type: " + Variant::get_type_name(valid_type) + ")\n";
+ } else {
+ text += "Value: " + String(v) + "\n";
+ }
+ text += "Easing: " + rtos(animation->track_get_key_transition(track, key_idx));
+
+ } break;
+ case Animation::TYPE_METHOD: {
+
+ Dictionary d = animation->track_get_key_value(track, key_idx);
+ if (d.has("method"))
+ text += String(d["method"]);
+ text += "(";
+ Vector<Variant> args;
+ if (d.has("args"))
+ args = d["args"];
+ for (int i = 0; i < args.size(); i++) {
+
+ if (i > 0)
+ text += ", ";
+ text += String(args[i]);
+ }
+ text += ")\n";
+
+ } break;
+ case Animation::TYPE_BEZIER: {
+
+ float h = animation->bezier_track_get_key_value(track, key_idx);
+ text += "Value: " + rtos(h) + "\n";
+ Vector2 ih = animation->bezier_track_get_key_in_handle(track, key_idx);
+ text += "In-Handle: " + ih + "\n";
+ Vector2 oh = animation->bezier_track_get_key_out_handle(track, key_idx);
+ text += "Out-Handle: " + oh + "\n";
+ } break;
+ case Animation::TYPE_AUDIO: {
+
+ String stream_name = "null";
+ RES 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();
+ } else if (stream->get_name() != "") {
+ stream_name = stream->get_name();
+ } else {
+ stream_name = stream->get_class();
+ }
+ }
+
+ text += "Stream: " + stream_name + "\n";
+ float so = animation->audio_track_get_key_start_offset(track, key_idx);
+ text += "Start (s): " + rtos(so) + "\n";
+ float eo = animation->audio_track_get_key_end_offset(track, key_idx);
+ text += "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";
+ } break;
+ }
+ return text;
+ }
+ }
+
+ return Control::get_tooltip(p_pos);
+}
+
+void AnimationTrackEdit::_gui_input(const Ref<InputEvent> &p_event) {
+
+ if (p_event->is_pressed()) {
+ if (ED_GET_SHORTCUT("animation_editor/duplicate_selection")->is_shortcut(p_event)) {
+ emit_signal("duplicate_request");
+ accept_event();
+ }
+
+ if (ED_GET_SHORTCUT("animation_editor/duplicate_selection_transposed")->is_shortcut(p_event)) {
+ emit_signal("duplicate_transpose_request");
+ accept_event();
+ }
+
+ if (ED_GET_SHORTCUT("animation_editor/delete_selection")->is_shortcut(p_event)) {
+ emit_signal("delete_request");
+ accept_event();
+ }
+ }
+
+ Ref<InputEventMouseButton> mb = p_event;
+ if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) {
+ Point2 pos = mb->get_position();
+
+ if (check_rect.has_point(pos)) {
+ *block_animation_update_ptr = true;
+ undo_redo->create_action("Toggle track enabled");
+ undo_redo->add_do_method(animation.ptr(), "track_set_enabled", track, !animation->track_is_enabled(track));
+ undo_redo->add_undo_method(animation.ptr(), "track_set_enabled", track, animation->track_is_enabled(track));
+ undo_redo->commit_action();
+ *block_animation_update_ptr = false;
+ update();
+ accept_event();
+ }
+ if (path_rect.has_point(pos)) {
+
+ clicking_on_name = true;
+ accept_event();
+ }
+
+ if (update_mode_rect.has_point(pos)) {
+ if (!menu) {
+ menu = memnew(PopupMenu);
+ add_child(menu);
+ menu->connect("id_pressed", this, "_menu_selected");
+ }
+ menu->clear();
+ menu->add_icon_item(get_icon("TrackContinuous", "EditorIcons"), TTR("Continuous"), MENU_CALL_MODE_CONTINUOUS);
+ menu->add_icon_item(get_icon("TrackDiscrete", "EditorIcons"), TTR("Discrete"), MENU_CALL_MODE_DISCRETE);
+ menu->add_icon_item(get_icon("TrackTrigger", "EditorIcons"), TTR("Trigger"), MENU_CALL_MODE_TRIGGER);
+ menu->add_icon_item(get_icon("TrackCapture", "EditorIcons"), TTR("Capture"), MENU_CALL_MODE_CAPTURE);
+ menu->set_as_minsize();
+
+ Vector2 popup_pos = get_global_position() + update_mode_rect.position + Vector2(0, update_mode_rect.size.height);
+ menu->set_global_position(popup_pos);
+ menu->popup();
+ accept_event();
+ }
+
+ if (interp_mode_rect.has_point(pos)) {
+ if (!menu) {
+ menu = memnew(PopupMenu);
+ add_child(menu);
+ menu->connect("id_pressed", this, "_menu_selected");
+ }
+ menu->clear();
+ menu->add_icon_item(get_icon("InterpRaw", "EditorIcons"), TTR("Nearest"), MENU_INTERPOLATION_NEAREST);
+ menu->add_icon_item(get_icon("InterpLinear", "EditorIcons"), TTR("Linear"), MENU_INTERPOLATION_LINEAR);
+ menu->add_icon_item(get_icon("InterpCubic", "EditorIcons"), TTR("Cubic"), MENU_INTERPOLATION_CUBIC);
+ menu->set_as_minsize();
+
+ Vector2 popup_pos = get_global_position() + interp_mode_rect.position + Vector2(0, interp_mode_rect.size.height);
+ menu->set_global_position(popup_pos);
+ menu->popup();
+ accept_event();
+ }
+
+ if (loop_mode_rect.has_point(pos)) {
+ if (!menu) {
+ menu = memnew(PopupMenu);
+ add_child(menu);
+ menu->connect("id_pressed", this, "_menu_selected");
+ }
+ menu->clear();
+ menu->add_icon_item(get_icon("InterpWrapClamp", "EditorIcons"), TTR("Clamp Loop Interp"), MENU_LOOP_CLAMP);
+ menu->add_icon_item(get_icon("InterpWrapLoop", "EditorIcons"), TTR("Wrap Loop Interp"), MENU_LOOP_WRAP);
+ menu->set_as_minsize();
+
+ Vector2 popup_pos = get_global_position() + loop_mode_rect.position + Vector2(0, loop_mode_rect.size.height);
+ menu->set_global_position(popup_pos);
+ menu->popup();
+ accept_event();
+ }
+
+ if (remove_rect.has_point(pos)) {
+ emit_signal("remove_request", track);
+ accept_event();
+ }
+
+ if (bezier_edit_rect.has_point(pos)) {
+ emit_signal("bezier_edit");
+ accept_event();
+ }
+
+ //check keyframes
+
+ float scale = timeline->get_zoom_scale();
+ int limit = timeline->get_name_limit();
+ int limit_end = get_size().width - timeline->get_buttons_width();
+
+ if (pos.x >= limit && pos.x <= limit_end) {
+
+ int key_idx = -1;
+ float key_distance = 1e20;
+
+ for (int i = animation->track_get_key_count(track) - 1; i >= 0; i--) { //select should happen in the opposite order of drawing for more accurate overlap select
+
+ 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;
+
+ print_line("rect: " + rect + " pos: " + pos);
+ if (rect.has_point(pos)) {
+
+ if (is_key_selectable_by_distance()) {
+ float distance = ABS(offset - pos.x);
+ if (key_idx == -1 || distance < key_distance) {
+ key_idx = i;
+ key_distance = distance;
+ }
+ } else {
+ //first one does it
+ key_idx = i;
+ break;
+ }
+ }
+ }
+
+ if (key_idx != -1) {
+ if (mb->get_command() || mb->get_shift()) {
+ if (editor->is_key_selected(track, key_idx)) {
+ emit_signal("deselect_key", key_idx);
+
+ } else {
+ emit_signal("select_key", key_idx, false);
+ moving_selection_attempt = true;
+ select_single_attempt = -1;
+ moving_selection_from_ofs = (mb->get_position().x - timeline->get_name_limit()) / timeline->get_zoom_scale();
+ }
+ } else {
+ if (!editor->is_key_selected(track, key_idx)) {
+ emit_signal("select_key", key_idx, true);
+ select_single_attempt = -1;
+ } else {
+ select_single_attempt = key_idx;
+ }
+
+ moving_selection_attempt = true;
+ moving_selection_from_ofs = (mb->get_position().x - timeline->get_name_limit()) / timeline->get_zoom_scale();
+ }
+ accept_event();
+ } else {
+ emit_signal("clear_selection");
+ }
+ }
+
+ /*using focus instead
+ * if (!selected && pos.x >= timeline->get_name_limit() && pos.x < (get_size().width - timeline->get_buttons_width())) {
+ set_selected(true);
+ emit_signal("selected");
+ }
+ */
+ }
+
+ if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == BUTTON_RIGHT) {
+ Point2 pos = mb->get_position();
+ if (pos.x >= timeline->get_name_limit() && pos.x <= get_size().width - timeline->get_buttons_width()) {
+ //can do something with menu too! show insert key
+ float offset = (pos.x - timeline->get_name_limit()) / timeline->get_zoom_scale();
+ if (!menu) {
+ menu = memnew(PopupMenu);
+ add_child(menu);
+ menu->connect("id_pressed", this, "_menu_selected");
+ }
+
+ menu->clear();
+ menu->add_icon_item(get_icon("Key", "EditorIcons"), TTR("Insert Key"), MENU_KEY_INSERT);
+ if (editor->is_selection_active()) {
+ menu->add_separator();
+ menu->add_icon_item(get_icon("Duplicate", "EditorIcons"), TTR("Duplicate Key(s)"), MENU_KEY_DUPLICATE);
+ menu->add_separator();
+ menu->add_icon_item(get_icon("Remove", "EditorIcons"), TTR("Delete Key(s)"), MENU_KEY_DELETE);
+ }
+ menu->set_as_minsize();
+
+ Vector2 popup_pos = get_global_transform().xform(get_local_mouse_position());
+ menu->set_global_position(popup_pos);
+ menu->popup();
+
+ insert_at_pos = offset + timeline->get_value();
+ accept_event();
+ }
+ }
+
+ if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT && clicking_on_name) {
+
+ if (!path) {
+ path = memnew(LineEdit);
+ add_child(path);
+ path->set_as_toplevel(true);
+ path->connect("text_entered", this, "_path_entered");
+ }
+
+ path->set_text(animation->track_get_path(track));
+ Vector2 theme_ofs = path->get_stylebox("normal", "LineEdit")->get_offset();
+ path->set_position(get_global_position() + path_rect.position - theme_ofs);
+ path->set_size(path_rect.size);
+ path->show_modal();
+ path->grab_focus();
+ path->set_cursor_position(path->get_text().length());
+ clicking_on_name = false;
+ }
+
+ if (mb.is_valid() && moving_selection_attempt) {
+
+ if (!mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) {
+ moving_selection_attempt = false;
+ if (moving_selection) {
+ emit_signal("move_selection_commit");
+ } else if (select_single_attempt != -1) {
+ emit_signal("select_key", select_single_attempt, true);
+ }
+ moving_selection = false;
+ select_single_attempt = -1;
+ }
+
+ if (moving_selection && mb->is_pressed() && mb->get_button_index() == BUTTON_RIGHT) {
+
+ moving_selection_attempt = false;
+ moving_selection = false;
+ emit_signal("move_selection_cancel");
+ }
+ }
+
+ Ref<InputEventMouseMotion> mm = p_event;
+ if (mm.is_valid() && mm->get_button_mask() & BUTTON_MASK_LEFT && moving_selection_attempt) {
+
+ if (!moving_selection) {
+ moving_selection = true;
+ emit_signal("move_selection_begin");
+ }
+
+ float new_ofs = (mm->get_position().x - timeline->get_name_limit()) / timeline->get_zoom_scale();
+ emit_signal("move_selection", new_ofs - moving_selection_from_ofs);
+ }
+}
+
+Variant AnimationTrackEdit::get_drag_data(const Point2 &p_point) {
+
+ if (!clicking_on_name)
+ return Variant();
+
+ Dictionary drag_data;
+ drag_data["type"] = "animation_track";
+ drag_data["index"] = track;
+
+ ToolButton *tb = memnew(ToolButton);
+ tb->set_text(path_cache);
+ tb->set_icon(icon_cache);
+ set_drag_preview(tb);
+
+ clicking_on_name = false;
+
+ return drag_data;
+}
+
+bool AnimationTrackEdit::can_drop_data(const Point2 &p_point, const Variant &p_data) const {
+
+ Dictionary d = p_data;
+ if (!d.has("type")) {
+ return false;
+ }
+
+ String type = d["type"];
+ if (type != "animation_track")
+ return false;
+
+ if (p_point.y < get_size().height / 2) {
+ dropping_at = -1;
+ } else {
+ dropping_at = 1;
+ }
+
+ const_cast<AnimationTrackEdit *>(this)->update();
+ const_cast<AnimationTrackEdit *>(this)->emit_signal("drop_attempted", track);
+
+ return true;
+}
+void AnimationTrackEdit::drop_data(const Point2 &p_point, const Variant &p_data) {
+
+ Dictionary d = p_data;
+ if (!d.has("type")) {
+ return;
+ }
+
+ String type = d["type"];
+ if (type != "animation_track")
+ return;
+
+ int from_track = d["index"];
+
+ if (dropping_at < 0) {
+ emit_signal("dropped", from_track, track);
+ } else {
+ emit_signal("dropped", from_track, track + 1);
+ }
+}
+
+void AnimationTrackEdit::_menu_selected(int p_index) {
+
+ switch (p_index) {
+ case MENU_CALL_MODE_CONTINUOUS:
+ case MENU_CALL_MODE_DISCRETE:
+ case MENU_CALL_MODE_TRIGGER:
+ case MENU_CALL_MODE_CAPTURE: {
+
+ Animation::UpdateMode update_mode = Animation::UpdateMode(p_index);
+ *block_animation_update_ptr = true;
+ undo_redo->create_action("Change animation update mode");
+ undo_redo->add_do_method(animation.ptr(), "value_track_set_update_mode", track, update_mode);
+ undo_redo->add_undo_method(animation.ptr(), "value_track_set_update_mode", track, animation->value_track_get_update_mode(track));
+ undo_redo->commit_action();
+ *block_animation_update_ptr = false;
+ update();
+
+ } break;
+ case MENU_INTERPOLATION_NEAREST:
+ case MENU_INTERPOLATION_LINEAR:
+ case MENU_INTERPOLATION_CUBIC: {
+
+ Animation::InterpolationType interp_mode = Animation::InterpolationType(p_index - MENU_INTERPOLATION_NEAREST);
+ *block_animation_update_ptr = true;
+ undo_redo->create_action("Change animation interpolation mode");
+ undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_type", track, interp_mode);
+ undo_redo->add_undo_method(animation.ptr(), "track_set_interpolation_type", track, animation->track_get_interpolation_type(track));
+ undo_redo->commit_action();
+ *block_animation_update_ptr = false;
+ update();
+ } break;
+ case MENU_LOOP_WRAP:
+ case MENU_LOOP_CLAMP: {
+
+ bool loop_wrap = p_index == MENU_LOOP_WRAP;
+ *block_animation_update_ptr = true;
+ undo_redo->create_action("Change animation loop mode");
+ undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_loop_wrap", track, loop_wrap);
+ undo_redo->add_undo_method(animation.ptr(), "track_set_interpolation_loop_wrap", track, animation->track_get_interpolation_loop_wrap(track));
+ undo_redo->commit_action();
+ *block_animation_update_ptr = false;
+ update();
+
+ } break;
+ case MENU_KEY_INSERT: {
+ emit_signal("insert_key", insert_at_pos);
+ } break;
+ case MENU_KEY_DUPLICATE: {
+ emit_signal("duplicate_request");
+
+ } break;
+ case MENU_KEY_DELETE: {
+ emit_signal("delete_request");
+
+ } break;
+ }
+}
+
+void AnimationTrackEdit::set_block_animation_update_ptr(bool *p_block_ptr) {
+ block_animation_update_ptr = p_block_ptr;
+}
+
+void AnimationTrackEdit::cancel_drop() {
+ if (dropping_at != 0) {
+ dropping_at = 0;
+ update();
+ }
+}
+void AnimationTrackEdit::set_in_group(bool p_enable) {
+ in_group = p_enable;
+ update();
+}
+
+void AnimationTrackEdit::append_to_selection(const Rect2 &p_box) {
+
+ Rect2 select_rect(timeline->get_name_limit(), 0, get_size().width - timeline->get_name_limit() - timeline->get_buttons_width(), get_size().height);
+ select_rect = select_rect.clip(p_box);
+
+ for (int i = animation->track_get_key_count(track) - 1; i >= 0; i--) { //select should happen in the opposite order of drawing for more accurate overlap select
+
+ Rect2 rect = const_cast<AnimationTrackEdit *>(this)->get_key_rect(i, timeline->get_zoom_scale());
+ float offset = animation->track_get_key_time(track, i) - timeline->get_value();
+ offset = offset * timeline->get_zoom_scale() + timeline->get_name_limit();
+ rect.position.x += offset;
+
+ if (select_rect.intersects(rect)) {
+ emit_signal("select_key", i, false);
+ }
+ }
+}
+
+void AnimationTrackEdit::_bind_methods() {
+
+ ClassDB::bind_method("_zoom_changed", &AnimationTrackEdit::_zoom_changed);
+ ClassDB::bind_method("_menu_selected", &AnimationTrackEdit::_menu_selected);
+ ClassDB::bind_method("_gui_input", &AnimationTrackEdit::_gui_input);
+ ClassDB::bind_method("_path_entered", &AnimationTrackEdit::_path_entered);
+ ClassDB::bind_method("_play_position_draw", &AnimationTrackEdit::_play_position_draw);
+
+ ADD_SIGNAL(MethodInfo("timeline_changed", PropertyInfo(Variant::REAL, "position"), PropertyInfo(Variant::BOOL, "drag")));
+ 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::REAL, "ofs")));
+ 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("clear_selection"));
+ ADD_SIGNAL(MethodInfo("bezier_edit"));
+
+ ADD_SIGNAL(MethodInfo("move_selection_begin"));
+ ADD_SIGNAL(MethodInfo("move_selection", PropertyInfo(Variant::REAL, "ofs")));
+ ADD_SIGNAL(MethodInfo("move_selection_commit"));
+ ADD_SIGNAL(MethodInfo("move_selection_cancel"));
+
+ ADD_SIGNAL(MethodInfo("duplicate_request"));
+ ADD_SIGNAL(MethodInfo("duplicate_transpose_request"));
+ ADD_SIGNAL(MethodInfo("delete_request"));
+}
+
+AnimationTrackEdit::AnimationTrackEdit() {
+ undo_redo = NULL;
+ timeline = NULL;
+ root = NULL;
+ path = NULL;
+ menu = NULL;
+ block_animation_update_ptr = NULL;
+ 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);
+ play_position->set_anchors_and_margins_preset(PRESET_WIDE);
+ play_position->connect("draw", this, "_play_position_draw");
+ set_focus_mode(FOCUS_CLICK);
+ set_mouse_filter(MOUSE_FILTER_PASS); //scroll has to work too for selection
+}
+
+//////////////////////////////////////
+
+AnimationTrackEdit *AnimationTrackEditPlugin::create_value_track_edit(Object *p_object, Variant::Type p_type, const String &p_property, PropertyHint p_hint, const String &p_hint_string, int p_usage) {
+ if (get_script_instance()) {
+ Variant args[6] = {
+ p_object,
+ p_type,
+ p_property,
+ p_hint,
+ p_hint_string,
+ p_usage
+ };
+
+ Variant *argptrs[6] = {
+ &args[0],
+ &args[1],
+ &args[2],
+ &args[3],
+ &args[4],
+ &args[5]
+ };
+
+ Variant::CallError ce;
+ return Object::cast_to<AnimationTrackEdit>(get_script_instance()->call("create_value_track_edit", (const Variant **)&argptrs, 6, ce).operator Object *());
+ }
+ return NULL;
+}
+
+AnimationTrackEdit *AnimationTrackEditPlugin::create_audio_track_edit() {
+
+ if (get_script_instance()) {
+ return Object::cast_to<AnimationTrackEdit>(get_script_instance()->call("create_audio_track_edit").operator Object *());
+ }
+ return NULL;
+}
+
+AnimationTrackEdit *AnimationTrackEditPlugin::create_animation_track_edit(Object *p_object) {
+ if (get_script_instance()) {
+ return Object::cast_to<AnimationTrackEdit>(get_script_instance()->call("create_animation_track_edit", p_object).operator Object *());
+ }
+ return NULL;
+}
+
+///////////////////////////////////////
+
+void AnimationTrackEditGroup::_notification(int p_what) {
+
+ if (p_what == NOTIFICATION_DRAW) {
+ Ref<Font> font = get_font("font", "Label");
+ int separation = get_constant("hseparation", "ItemList");
+ Color color = get_color("font_color", "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_color("accent_color", "Editor");
+ }
+ }
+
+ Color bgcol = get_color("dark_color_2", "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);
+ draw_line(Point2(timeline->get_name_limit(), 0), Point2(timeline->get_name_limit(), get_size().height), linecolor);
+ draw_line(Point2(get_size().width - timeline->get_buttons_width(), 0), Point2(get_size().width - timeline->get_buttons_width(), get_size().height), linecolor);
+
+ 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()) / 2 + font->get_ascent()), node_name, color, timeline->get_name_limit() - ofs);
+
+ 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_color("accent_color", "Editor");
+ draw_line(Point2(px, 0), Point2(px, get_size().height), accent);
+ }
+ }
+}
+
+void AnimationTrackEditGroup::set_type_and_name(const Ref<Texture> &p_type, const String &p_name, const NodePath &p_node) {
+ icon = p_type;
+ node_name = p_name;
+ node = p_node;
+ update();
+ minimum_size_changed();
+}
+
+Size2 AnimationTrackEditGroup::get_minimum_size() const {
+
+ Ref<Font> font = get_font("font", "Label");
+ int separation = get_constant("vseparation", "ItemList");
+
+ return Vector2(0, MAX(font->get_height(), icon->get_height()) + separation);
+}
+
+void AnimationTrackEditGroup::set_timeline(AnimationTimelineEdit *p_timeline) {
+ timeline = p_timeline;
+ timeline->connect("zoom_changed", this, "_zoom_changed");
+}
+
+void AnimationTrackEditGroup::set_root(Node *p_root) {
+ root = p_root;
+ update();
+}
+
+void AnimationTrackEditGroup::_zoom_changed() {
+ update();
+}
+
+void AnimationTrackEditGroup::_bind_methods() {
+ ClassDB::bind_method("_zoom_changed", &AnimationTrackEditGroup::_zoom_changed);
+}
+
+AnimationTrackEditGroup::AnimationTrackEditGroup() {
+ set_mouse_filter(MOUSE_FILTER_PASS);
+}
+
+//////////////////////////////////////
+
+void AnimationTrackEditor::add_track_edit_plugin(const Ref<AnimationTrackEditPlugin> &p_plugin) {
+
+ if (track_edit_plugins.find(p_plugin) != -1)
+ return;
+ track_edit_plugins.push_back(p_plugin);
+}
+
+void AnimationTrackEditor::remove_track_edit_plugin(const Ref<AnimationTrackEditPlugin> &p_plugin) {
+
+ track_edit_plugins.erase(p_plugin);
+}
+
+void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim) {
+
+ if (animation != p_anim && _get_track_selected() >= 0) {
+ track_edits[_get_track_selected()]->release_focus();
+ }
+ if (animation.is_valid()) {
+ animation->disconnect("changed", this, "_animation_changed");
+ _clear_selection();
+ }
+ animation = p_anim;
+ timeline->set_animation(p_anim);
+
+ _cancel_bezier_edit();
+ _update_tracks();
+
+ if (animation.is_valid()) {
+ animation->connect("changed", this, "_animation_changed");
+
+ step->set_block_signals(true);
+ step->set_value(animation->get_step());
+ step->set_block_signals(false);
+ step->set_read_only(false);
+ snap->set_disabled(false);
+ } else {
+ step->set_block_signals(true);
+ step->set_value(0);
+ step->set_block_signals(false);
+ step->set_read_only(true);
+ snap->set_disabled(true);
+ }
+}
+
+Ref<Animation> AnimationTrackEditor::get_current_animation() const {
+
+ return animation;
+}
+void AnimationTrackEditor::_root_removed(Node *p_root) {
+ root = NULL;
+}
+
+void AnimationTrackEditor::set_root(Node *p_root) {
+ if (root) {
+ root->disconnect("tree_exiting", this, "_root_removed");
+ }
+
+ root = p_root;
+
+ if (root) {
+ root->connect("tree_exiting", this, "_root_removed", make_binds(), CONNECT_ONESHOT);
+ }
+
+ _update_tracks();
+}
+
+Node *AnimationTrackEditor::get_root() const {
+
+ return root;
+}
+
+void AnimationTrackEditor::update_keying() {
+ bool keying_enabled = is_visible_in_tree() && animation.is_valid();
+
+ if (keying_enabled == keying)
+ return;
+
+ keying = keying_enabled;
+ //_update_menu();
+ emit_signal("keying_changed");
+}
+
+bool AnimationTrackEditor::has_keying() const {
+ return keying;
+}
+
+void AnimationTrackEditor::cleanup() {
+ set_animation(Ref<Animation>());
+}
+
+void AnimationTrackEditor::_name_limit_changed() {
+
+ for (int i = 0; i < track_edits.size(); i++) {
+ track_edits[i]->update();
+ }
+}
+
+void AnimationTrackEditor::_timeline_changed(float p_new_pos, bool p_drag) {
+
+ emit_signal("timeline_changed", p_new_pos, p_drag);
+}
+
+void AnimationTrackEditor::_track_remove_request(int p_track) {
+
+ int idx = p_track;
+ if (idx >= 0 && idx < animation->get_track_count()) {
+ _clear_selection();
+ undo_redo->create_action(TTR("Remove Anim Track"));
+ 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));
+ //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);
+
+ 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(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->commit_action();
+ }
+}
+
+void AnimationTrackEditor::set_anim_pos(float p_pos) {
+
+ timeline->set_play_position(p_pos);
+ for (int i = 0; i < track_edits.size(); i++) {
+ track_edits[i]->set_play_position(p_pos);
+ }
+ for (int i = 0; i < groups.size(); i++) {
+ groups[i]->update();
+ }
+ bezier_edit->set_play_position(p_pos);
+}
+
+void AnimationTrackEditor::_query_insert(const InsertData &p_id) {
+
+ if (insert_frame != Engine::get_singleton()->get_frames_drawn()) {
+ //clear insert list for the frame if frame changed
+ if (insert_confirm->is_visible_in_tree())
+ return; //do nothing
+ insert_data.clear();
+ insert_query = false;
+ }
+ insert_frame = Engine::get_singleton()->get_frames_drawn();
+
+ for (List<InsertData>::Element *E = insert_data.front(); E; E = E->next()) {
+ //prevent insertion of multiple tracks
+ if (E->get().path == p_id.path)
+ return; //already inserted a track for this on this frame
+ }
+
+ insert_data.push_back(p_id);
+
+ if (p_id.track_idx == -1) {
+ if (bool(EDITOR_DEF("editors/animation/confirm_insert_track", true))) {
+ //potential new key, does not exist
+ if (insert_data.size() == 1)
+ insert_confirm_text->set_text(vformat(TTR("Create NEW track for %s and insert key?"), p_id.query));
+ else
+ insert_confirm_text->set_text(vformat(TTR("Create %d NEW tracks and insert keys?"), insert_data.size()));
+
+ bool all_bezier = true;
+ for (int i = 0; i < insert_data.size(); i++) {
+ if (insert_data[i].type != Animation::TYPE_VALUE && insert_data[i].type != Animation::TYPE_BEZIER) {
+ all_bezier = false;
+ }
+
+ if (insert_data[i].type != Animation::TYPE_VALUE) {
+ continue;
+ }
+ switch (insert_data[i].value.get_type()) {
+ case Variant::INT:
+ case Variant::REAL:
+ case Variant::VECTOR2:
+ case Variant::VECTOR3:
+ case Variant::QUAT:
+ case Variant::PLANE:
+ case Variant::COLOR: {
+ //good
+ } break;
+ default: {
+ all_bezier = false;
+ }
+ }
+ }
+
+ insert_confirm_bezier->set_visible(all_bezier);
+ insert_confirm->get_ok()->set_text(TTR("Create"));
+ insert_confirm->popup_centered_minsize();
+ insert_query = true;
+ } else {
+ call_deferred("_insert_delay");
+ insert_queue = true;
+ }
+
+ } else {
+ if (!insert_query && !insert_queue) {
+ call_deferred("_insert_delay");
+ insert_queue = true;
+ }
+ }
+}
+
+void AnimationTrackEditor::_insert_delay() {
+
+ if (insert_query) {
+ //discard since it's entered into query mode
+ insert_queue = false;
+ return;
+ }
+
+ undo_redo->create_action(TTR("Anim Insert"));
+
+ int last_track = animation->get_track_count();
+ bool advance = false;
+ while (insert_data.size()) {
+
+ if (insert_data.front()->get().advance)
+ advance = true;
+ last_track = _confirm_insert(insert_data.front()->get(), last_track);
+ insert_data.pop_front();
+ }
+
+ undo_redo->commit_action();
+
+ if (advance) {
+ float step = animation->get_step();
+ if (step == 0)
+ step = 1;
+
+ float pos = timeline->get_play_position();
+
+ pos = Math::stepify(pos + step, step);
+ if (pos > animation->get_length())
+ pos = animation->get_length();
+ set_anim_pos(pos);
+ emit_signal("timeline_changed", pos, true);
+ }
+ insert_queue = false;
+}
+
+void AnimationTrackEditor::insert_transform_key(Spatial *p_node, const String &p_sub, const Transform &p_xform) {
+
+ if (!keying)
+ return;
+ if (!animation.is_valid())
+ return;
+
+ ERR_FAIL_COND(!root);
+ //let's build a node path
+ String path = root->get_path_to(p_node);
+ if (p_sub != "")
+ path += ":" + p_sub;
+
+ NodePath np = path;
+
+ int track_idx = -1;
+
+ for (int i = 0; i < animation->get_track_count(); i++) {
+
+ if (animation->track_get_type(i) != Animation::TYPE_TRANSFORM)
+ continue;
+ if (animation->track_get_path(i) != np)
+ continue;
+
+ track_idx = i;
+ break;
+ }
+
+ InsertData id;
+ Dictionary val;
+
+ id.path = np;
+ id.track_idx = track_idx;
+ id.value = p_xform;
+ id.type = Animation::TYPE_TRANSFORM;
+ id.query = "node '" + p_node->get_name() + "'";
+ id.advance = false;
+
+ //dialog insert
+
+ _query_insert(id);
+}
+
+void AnimationTrackEditor::_insert_animation_key(NodePath p_path, const Variant &p_value) {
+
+ String path = p_path;
+
+ //animation property is a special case, always creates an animation track
+ for (int i = 0; i < animation->get_track_count(); i++) {
+
+ String np = animation->track_get_path(i);
+
+ if (path == np && animation->track_get_type(i) == Animation::TYPE_ANIMATION) {
+ //exists
+ InsertData id;
+ id.path = path;
+ id.track_idx = i;
+ id.value = p_value;
+ id.type = Animation::TYPE_ANIMATION;
+ id.query = "animation";
+ id.advance = false;
+ //dialog insert
+ _query_insert(id);
+ return;
+ }
+ }
+
+ InsertData id;
+ id.path = path;
+ id.track_idx = -1;
+ id.value = p_value;
+ id.type = Animation::TYPE_ANIMATION;
+ id.query = "animation";
+ id.advance = false;
+ //dialog insert
+ _query_insert(id);
+}
+
+void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_property, const Variant &p_value, bool p_only_if_exists) {
+
+ ERR_FAIL_COND(!root);
+ //let's build a node path
+
+ Node *node = p_node;
+
+ String path = root->get_path_to(node);
+
+ if (Object::cast_to<AnimationPlayer>(node) && p_property == "current_animation") {
+ if (node == AnimationPlayerEditor::singleton->get_player()) {
+ EditorNode::get_singleton()->show_warning(TTR("AnimationPlayer can't animate itself, only other players."));
+ return;
+ }
+ _insert_animation_key(path, p_value);
+ return;
+ }
+
+ EditorHistory *history = EditorNode::get_singleton()->get_editor_history();
+ for (int i = 1; i < history->get_path_size(); i++) {
+
+ String prop = history->get_path_property(i);
+ ERR_FAIL_COND(prop == "");
+ path += ":" + prop;
+ }
+
+ path += ":" + p_property;
+
+ NodePath np = path;
+
+ //locate track
+
+ bool inserted = false;
+
+ for (int i = 0; i < animation->get_track_count(); i++) {
+
+ if (animation->track_get_type(i) == Animation::TYPE_VALUE) {
+ if (animation->track_get_path(i) != np)
+ continue;
+
+ InsertData id;
+ id.path = np;
+ id.track_idx = i;
+ id.value = p_value;
+ id.type = Animation::TYPE_VALUE;
+ id.query = "property '" + p_property + "'";
+ id.advance = false;
+ //dialog insert
+ _query_insert(id);
+ inserted = true;
+ } else if (animation->track_get_type(i) == Animation::TYPE_BEZIER) {
+
+ Variant value;
+ if (animation->track_get_path(i) == np) {
+ value = p_value; //all good
+ } else {
+ String path = animation->track_get_path(i);
+ if (NodePath(path.get_basename()) == np) {
+ String subindex = path.get_extension();
+ value = p_value.get(subindex);
+ } else {
+ continue;
+ }
+ }
+
+ InsertData id;
+ id.path = animation->track_get_path(i);
+ id.track_idx = i;
+ id.value = value;
+ id.type = Animation::TYPE_BEZIER;
+ id.query = "property '" + p_property + "'";
+ id.advance = false;
+ //dialog insert
+ _query_insert(id);
+ inserted = true;
+ }
+ }
+
+ if (inserted || p_only_if_exists)
+ return;
+ InsertData id;
+ id.path = np;
+ id.track_idx = -1;
+ id.value = p_value;
+ id.type = Animation::TYPE_VALUE;
+ id.query = "property '" + p_property + "'";
+ id.advance = false;
+ //dialog insert
+ _query_insert(id);
+}
+
+void AnimationTrackEditor::insert_value_key(const String &p_property, const Variant &p_value, bool p_advance) {
+
+ EditorHistory *history = EditorNode::get_singleton()->get_editor_history();
+
+ ERR_FAIL_COND(!root);
+ //let's build a node path
+ ERR_FAIL_COND(history->get_path_size() == 0);
+ Object *obj = ObjectDB::get_instance(history->get_path_object(0));
+ ERR_FAIL_COND(!Object::cast_to<Node>(obj));
+
+ Node *node = Object::cast_to<Node>(obj);
+
+ String path = root->get_path_to(node);
+
+ if (Object::cast_to<AnimationPlayer>(node) && p_property == "current_animation") {
+ if (node == AnimationPlayerEditor::singleton->get_player()) {
+ EditorNode::get_singleton()->show_warning(TTR("AnimationPlayer can't animate itself, only other players."));
+ return;
+ }
+ _insert_animation_key(path, p_value);
+ return;
+ }
+
+ for (int i = 1; i < history->get_path_size(); i++) {
+
+ String prop = history->get_path_property(i);
+ ERR_FAIL_COND(prop == "");
+ path += ":" + prop;
+ }
+
+ path += ":" + p_property;
+
+ NodePath np = path;
+
+ //locate track
+
+ bool inserted = false;
+
+ for (int i = 0; i < animation->get_track_count(); i++) {
+
+ if (animation->track_get_type(i) == Animation::TYPE_VALUE) {
+ if (animation->track_get_path(i) != np)
+ continue;
+
+ InsertData id;
+ id.path = np;
+ id.track_idx = i;
+ id.value = p_value;
+ id.type = Animation::TYPE_VALUE;
+ id.query = "property '" + p_property + "'";
+ id.advance = p_advance;
+ //dialog insert
+ _query_insert(id);
+ inserted = true;
+ } else if (animation->track_get_type(i) == Animation::TYPE_BEZIER) {
+
+ Variant value;
+ if (animation->track_get_path(i) == np) {
+ value = p_value; //all good
+ } else {
+ String path = animation->track_get_path(i);
+ if (NodePath(path.get_basename()) == np) {
+ String subindex = path.get_extension();
+ value = p_value.get(subindex);
+ } else {
+ continue;
+ }
+ }
+
+ InsertData id;
+ id.path = animation->track_get_path(i);
+ id.track_idx = i;
+ id.value = value;
+ id.type = Animation::TYPE_BEZIER;
+ id.query = "property '" + p_property + "'";
+ id.advance = p_advance;
+ //dialog insert
+ _query_insert(id);
+ inserted = true;
+ }
+ }
+
+ if (!inserted) {
+ InsertData id;
+ id.path = np;
+ id.track_idx = -1;
+ id.value = p_value;
+ id.type = Animation::TYPE_VALUE;
+ id.query = "property '" + p_property + "'";
+ id.advance = p_advance;
+ //dialog insert
+ _query_insert(id);
+ }
+}
+
+void AnimationTrackEditor::_confirm_insert_list() {
+
+ undo_redo->create_action(TTR("Anim Create & Insert"));
+
+ int last_track = animation->get_track_count();
+ while (insert_data.size()) {
+
+ last_track = _confirm_insert(insert_data.front()->get(), last_track, insert_confirm_bezier->is_pressed());
+ insert_data.pop_front();
+ }
+
+ undo_redo->commit_action();
+}
+
+PropertyInfo AnimationTrackEditor::_find_hint_for_track(int p_idx, NodePath &r_base_path, Variant *r_current_val) {
+
+ r_base_path = NodePath();
+ ERR_FAIL_COND_V(!animation.is_valid(), PropertyInfo());
+ ERR_FAIL_INDEX_V(p_idx, animation->get_track_count(), PropertyInfo());
+
+ if (!root) {
+ return PropertyInfo();
+ }
+
+ NodePath path = animation->track_get_path(p_idx);
+
+ if (!root->has_node_and_resource(path)) {
+ return PropertyInfo();
+ }
+
+ RES res;
+ Vector<StringName> leftover_path;
+ Node *node = root->get_node_and_resource(path, res, leftover_path, true);
+
+ if (node) {
+ r_base_path = node->get_path();
+ }
+
+ if (leftover_path.empty()) {
+ if (r_current_val) {
+ if (res.is_valid()) {
+ *r_current_val = res;
+ } else if (node) {
+ *r_current_val = node;
+ }
+ }
+ return PropertyInfo();
+ }
+
+ Variant property_info_base;
+ if (res.is_valid()) {
+ property_info_base = res;
+ if (r_current_val) {
+ *r_current_val = res->get(leftover_path[leftover_path.size() - 1]);
+ }
+ } else if (node) {
+ property_info_base = node;
+ if (r_current_val) {
+ *r_current_val = node->get(leftover_path[leftover_path.size() - 1]);
+ }
+ }
+
+ for (int i = 0; i < leftover_path.size() - 1; i++) {
+ property_info_base = property_info_base.get_named(leftover_path[i]);
+ }
+
+ List<PropertyInfo> pinfo;
+ property_info_base.get_property_list(&pinfo);
+
+ for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) {
+
+ if (E->get().name == leftover_path[leftover_path.size() - 1]) {
+ return E->get();
+ }
+ }
+
+ return PropertyInfo();
+}
+
+static Vector<String> _get_bezier_subindices_for_type(Variant::Type p_type, bool *r_valid = NULL) {
+ Vector<String> subindices;
+ if (r_valid) {
+ *r_valid = true;
+ }
+ switch (p_type) {
+ case Variant::INT: {
+ subindices.push_back("");
+ } break;
+ case Variant::REAL: {
+ subindices.push_back("");
+ } break;
+ case Variant::VECTOR2: {
+ subindices.push_back(".x");
+ subindices.push_back(".y");
+ } break;
+ case Variant::VECTOR3: {
+ subindices.push_back(".x");
+ subindices.push_back(".y");
+ subindices.push_back(".z");
+ } break;
+ case Variant::QUAT: {
+ subindices.push_back(".x");
+ subindices.push_back(".y");
+ subindices.push_back(".z");
+ subindices.push_back(".w");
+ } break;
+ case Variant::COLOR: {
+ subindices.push_back(".r");
+ subindices.push_back(".g");
+ subindices.push_back(".b");
+ subindices.push_back(".a");
+ } break;
+ case Variant::PLANE: {
+ subindices.push_back(".x");
+ subindices.push_back(".y");
+ subindices.push_back(".z");
+ subindices.push_back(".d");
+ } break;
+ default: {
+ if (r_valid) {
+ *r_valid = false;
+ }
+ }
+ }
+
+ return subindices;
+}
+
+int AnimationTrackEditor::_confirm_insert(InsertData p_id, int p_last_track, bool p_create_beziers) {
+
+ if (p_last_track == -1)
+ p_last_track = animation->get_track_count();
+
+ bool created = false;
+ if (p_id.track_idx < 0) {
+
+ if (p_create_beziers && (p_id.value.get_type() == Variant::VECTOR2 ||
+ p_id.value.get_type() == Variant::VECTOR3 ||
+ p_id.value.get_type() == Variant::QUAT ||
+ p_id.value.get_type() == Variant::COLOR ||
+ p_id.value.get_type() == Variant::PLANE)) {
+
+ Vector<String> subindices = _get_bezier_subindices_for_type(p_id.value.get_type());
+
+ for (int i = 0; i < subindices.size(); i++) {
+ InsertData id = p_id;
+ id.type = Animation::TYPE_BEZIER;
+ id.value = p_id.value.get(subindices[i].substr(1, subindices[i].length()));
+ id.path = String(p_id.path) + subindices[i];
+ _confirm_insert(id, p_last_track + i);
+ }
+
+ return p_last_track + subindices.size() - 1;
+ }
+ created = true;
+ undo_redo->create_action(TTR("Anim Insert Track & Key"));
+ Animation::UpdateMode update_mode = Animation::UPDATE_DISCRETE;
+
+ if (p_id.type == Animation::TYPE_VALUE || p_id.type == Animation::TYPE_BEZIER) {
+ //wants a new tack
+
+ {
+ //hack
+ NodePath np;
+ animation->add_track(p_id.type);
+ animation->track_set_path(animation->get_track_count() - 1, p_id.path);
+ PropertyInfo h = _find_hint_for_track(animation->get_track_count() - 1, np);
+ animation->remove_track(animation->get_track_count() - 1); //hack
+
+ if (h.type == Variant::REAL ||
+ h.type == Variant::VECTOR2 ||
+ h.type == Variant::RECT2 ||
+ h.type == Variant::VECTOR3 ||
+ h.type == Variant::AABB ||
+ h.type == Variant::QUAT ||
+ h.type == Variant::COLOR ||
+ h.type == Variant::PLANE ||
+ h.type == Variant::TRANSFORM2D ||
+ h.type == Variant::TRANSFORM) {
+
+ update_mode = Animation::UPDATE_CONTINUOUS;
+ }
+
+ if (h.usage & PROPERTY_USAGE_ANIMATE_AS_TRIGGER) {
+ update_mode = Animation::UPDATE_TRIGGER;
+ }
+ }
+ }
+
+ p_id.track_idx = p_last_track;
+
+ undo_redo->add_do_method(animation.ptr(), "add_track", p_id.type);
+ undo_redo->add_do_method(animation.ptr(), "track_set_path", p_id.track_idx, p_id.path);
+ if (p_id.type == Animation::TYPE_VALUE)
+ undo_redo->add_do_method(animation.ptr(), "value_track_set_update_mode", p_id.track_idx, update_mode);
+
+ } else {
+ undo_redo->create_action(TTR("Anim Insert Key"));
+ }
+
+ float time = timeline->get_play_position();
+ Variant value;
+
+ switch (p_id.type) {
+
+ case Animation::TYPE_VALUE: {
+
+ value = p_id.value;
+
+ } break;
+ case Animation::TYPE_TRANSFORM: {
+
+ Transform tr = p_id.value;
+ Dictionary d;
+ d["location"] = tr.origin;
+ d["scale"] = tr.basis.get_scale();
+ d["rotation"] = Quat(tr.basis); //.orthonormalized();
+ value = d;
+ } break;
+ case Animation::TYPE_BEZIER: {
+ Array array;
+ array.resize(5);
+ array[0] = p_id.value;
+ array[1] = -0.25;
+ array[2] = 0;
+ array[3] = 0.25;
+ array[4] = 0;
+ value = array;
+
+ } break;
+ case Animation::TYPE_ANIMATION: {
+ value = p_id.value;
+ } break;
+ default: {}
+ }
+
+ undo_redo->add_do_method(animation.ptr(), "track_insert_key", p_id.track_idx, time, value);
+
+ if (created) {
+
+ //just remove the track
+ undo_redo->add_undo_method(animation.ptr(), "remove_track", p_last_track);
+ p_last_track++;
+ } else {
+
+ undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", p_id.track_idx, time);
+ int existing = animation->track_find_key(p_id.track_idx, time, true);
+ if (existing != -1) {
+ Variant v = animation->track_get_key_value(p_id.track_idx, existing);
+ float trans = animation->track_get_key_transition(p_id.track_idx, existing);
+ undo_redo->add_undo_method(animation.ptr(), "track_insert_key", p_id.track_idx, time, v, trans);
+ }
+ }
+
+ /*
+ undo_redo->add_do_method(this, "update_tracks");
+ undo_redo->add_undo_method(this, "update");
+ undo_redo->add_do_method(track_editor, "update");
+ undo_redo->add_undo_method(track_editor, "update");
+ undo_redo->add_do_method(track_pos, "update");
+ undo_redo->add_undo_method(track_pos, "update");
+*/
+ undo_redo->commit_action();
+
+ return p_last_track;
+}
+
+void AnimationTrackEditor::show_select_node_warning(bool p_show) {
+}
+
+bool AnimationTrackEditor::is_key_selected(int p_track, int p_key) const {
+
+ SelectedKey sk;
+ sk.key = p_key;
+ sk.track = p_track;
+
+ return selection.has(sk);
+}
+
+bool AnimationTrackEditor::is_selection_active() const {
+ return selection.size();
+}
+
+void AnimationTrackEditor::_update_tracks() {
+
+ int selected = _get_track_selected();
+
+ while (track_vbox->get_child_count()) {
+ memdelete(track_vbox->get_child(0));
+ }
+
+ track_edits.clear();
+ groups.clear();
+
+ if (animation.is_null())
+ return;
+
+ Map<String, VBoxContainer *> group_sort;
+
+ bool use_grouping = !view_group->is_pressed();
+ bool use_filter = selected_filter->is_pressed();
+
+ for (int i = 0; i < animation->get_track_count(); i++) {
+ AnimationTrackEdit *track_edit = NULL;
+
+ //find hint and info for plugin
+
+ if (use_filter) {
+ NodePath path = animation->track_get_path(i);
+
+ if (root && root->has_node(path)) {
+ Node *node = root->get_node(path);
+ if (!node) {
+ continue; // no node, no filter
+ }
+ if (!EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) {
+ continue; //skip track due to not selected
+ }
+ }
+ }
+
+ if (animation->track_get_type(i) == Animation::TYPE_VALUE) {
+
+ NodePath path = animation->track_get_path(i);
+
+ if (root && root->has_node_and_resource(path)) {
+ RES res;
+ Vector<StringName> leftover_path;
+ Node *node = root->get_node_and_resource(path, res, leftover_path, true);
+
+ Object *object = node;
+ if (res.is_valid()) {
+ object = res.ptr();
+ } else {
+ object = node;
+ }
+
+ if (object && !leftover_path.empty()) {
+ //not a property (value track?)
+ PropertyInfo pinfo;
+ pinfo.name = leftover_path[leftover_path.size() - 1];
+ //now let's see if we can get more info about it
+
+ List<PropertyInfo> plist;
+ object->get_property_list(&plist);
+
+ for (List<PropertyInfo>::Element *E = plist.front(); E; E = E->next()) {
+
+ if (E->get().name == leftover_path[leftover_path.size() - 1]) {
+ pinfo = E->get();
+ break;
+ }
+ }
+
+ for (int j = 0; j < track_edit_plugins.size(); j++) {
+ track_edit = track_edit_plugins[j]->create_value_track_edit(object, pinfo.type, pinfo.name, pinfo.hint, pinfo.hint_string, pinfo.usage);
+ if (track_edit) {
+ break;
+ }
+ }
+ }
+ }
+ }
+ if (animation->track_get_type(i) == Animation::TYPE_AUDIO) {
+
+ for (int j = 0; j < track_edit_plugins.size(); j++) {
+ track_edit = track_edit_plugins[j]->create_audio_track_edit();
+ if (track_edit) {
+ break;
+ }
+ }
+ }
+
+ if (animation->track_get_type(i) == Animation::TYPE_ANIMATION) {
+ NodePath path = animation->track_get_path(i);
+
+ Node *node = NULL;
+ if (root && root->has_node(path)) {
+ node = root->get_node(path);
+ }
+
+ if (node && Object::cast_to<AnimationPlayer>(node)) {
+ for (int j = 0; j < track_edit_plugins.size(); j++) {
+ track_edit = track_edit_plugins[j]->create_animation_track_edit(node);
+ if (track_edit) {
+ break;
+ }
+ }
+ }
+ }
+
+ if (track_edit == NULL) {
+ //no valid plugin_found
+ track_edit = memnew(AnimationTrackEdit);
+ }
+
+ track_edits.push_back(track_edit);
+
+ if (use_grouping) {
+ String base_path = animation->track_get_path(i);
+ base_path = base_path.get_slice(":", 0); // remove subpath
+
+ if (!group_sort.has(base_path)) {
+ AnimationTrackEditGroup *g = memnew(AnimationTrackEditGroup);
+ Ref<Texture> icon = get_icon("Node", "EditorIcons");
+ String name = base_path;
+ String tooltip;
+ if (root) {
+ Node *n = root->get_node(base_path);
+ if (n) {
+ if (has_icon(n->get_class(), "EditorIcons")) {
+ icon = get_icon(n->get_class(), "EditorIcons");
+ }
+ name = n->get_name();
+ tooltip = root->get_path_to(n);
+ }
+ }
+
+ g->set_type_and_name(icon, name, animation->track_get_path(i));
+ g->set_root(root);
+ g->set_tooltip(tooltip);
+ g->set_timeline(timeline);
+ groups.push_back(g);
+ VBoxContainer *vb = memnew(VBoxContainer);
+ vb->add_constant_override("separation", 0);
+ vb->add_child(g);
+ track_vbox->add_child(vb);
+ group_sort[base_path] = vb;
+ }
+
+ track_edit->set_in_group(true);
+ group_sort[base_path]->add_child(track_edit);
+
+ } else {
+ track_edit->set_in_group(false);
+ track_vbox->add_child(track_edit);
+ }
+
+ track_edit->set_undo_redo(undo_redo);
+ track_edit->set_timeline(timeline);
+ track_edit->set_block_animation_update_ptr(&block_animation_update);
+ track_edit->set_root(root);
+ track_edit->set_animation_and_track(animation, i);
+ track_edit->set_play_position(timeline->get_play_position());
+ track_edit->set_editor(this);
+
+ if (selected == i) {
+ track_edit->grab_focus();
+ }
+
+ track_edit->connect("timeline_changed", this, "_timeline_changed");
+ track_edit->connect("remove_request", this, "_track_remove_request", varray(), CONNECT_DEFERRED);
+ track_edit->connect("dropped", this, "_dropped_track", varray(), CONNECT_DEFERRED);
+ track_edit->connect("insert_key", this, "_insert_key_from_track", varray(i), CONNECT_DEFERRED);
+ track_edit->connect("select_key", this, "_key_selected", varray(i), CONNECT_DEFERRED);
+ track_edit->connect("deselect_key", this, "_key_deselected", varray(i), CONNECT_DEFERRED);
+ track_edit->connect("bezier_edit", this, "_bezier_edit", varray(i), CONNECT_DEFERRED);
+ track_edit->connect("clear_selection", this, "_clear_selection");
+ track_edit->connect("move_selection_begin", this, "_move_selection_begin");
+ track_edit->connect("move_selection", this, "_move_selection");
+ track_edit->connect("move_selection_commit", this, "_move_selection_commit");
+ track_edit->connect("move_selection_cancel", this, "_move_selection_cancel");
+
+ track_edit->connect("duplicate_request", this, "_edit_menu_pressed", varray(EDIT_DUPLICATE_SELECTION), CONNECT_DEFERRED);
+ track_edit->connect("duplicate_transpose_request", this, "_edit_menu_pressed", varray(EDIT_DUPLICATE_TRANSPOSED), CONNECT_DEFERRED);
+ track_edit->connect("delete_request", this, "_edit_menu_pressed", varray(EDIT_DELETE_SELECTION), CONNECT_DEFERRED);
+ }
+}
+
+void AnimationTrackEditor::_animation_changed() {
+
+ timeline->update_values();
+ if (block_animation_update) {
+ for (int i = 0; i < track_edits.size(); i++) {
+ track_edits[i]->update();
+ }
+ for (int i = 0; i < groups.size(); i++) {
+ groups[i]->update();
+ }
+ } else {
+ _update_tracks();
+ }
+
+ bezier_edit->update();
+
+ step->set_block_signals(true);
+ step->set_value(animation->get_step());
+ step->set_block_signals(false);
+}
+
+MenuButton *AnimationTrackEditor::get_edit_menu() {
+ return edit;
+}
+
+void AnimationTrackEditor::_notification(int p_what) {
+ if (p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_ENTER_TREE) {
+
+ zoom_icon->set_texture(get_icon("Zoom", "EditorIcons"));
+ snap->set_icon(get_icon("Snap", "EditorIcons"));
+ view_group->set_icon(get_icon(view_group->is_pressed() ? "AnimationTrackList" : "AnimationTrackGroup", "EditorIcons"));
+ selected_filter->set_icon(get_icon("AnimationFilter", "EditorIcons"));
+ }
+
+ if (p_what == NOTIFICATION_READY) {
+ EditorNode::get_singleton()->get_editor_selection()->connect("selection_changed", this, "_selection_changed");
+ }
+
+ if (p_what == NOTIFICATION_VISIBILITY_CHANGED) {
+
+ update_keying();
+ EditorNode::get_singleton()->update_keying();
+ emit_signal("keying_changed");
+ }
+}
+
+void AnimationTrackEditor::_update_scroll(double) {
+ for (int i = 0; i < track_edits.size(); i++) {
+ track_edits[i]->update();
+ }
+ for (int i = 0; i < groups.size(); i++) {
+ groups[i]->update();
+ }
+}
+
+void AnimationTrackEditor::_update_step(double p_new_step) {
+
+ undo_redo->create_action("Change animation step");
+ undo_redo->add_do_method(animation.ptr(), "set_step", p_new_step);
+ undo_redo->add_undo_method(animation.ptr(), "set_step", animation->get_step());
+ step->set_block_signals(true);
+ undo_redo->commit_action();
+ step->set_block_signals(false);
+ emit_signal("animation_step_changed", p_new_step);
+}
+
+void AnimationTrackEditor::_update_length(double p_new_len) {
+
+ emit_signal("animation_len_changed", p_new_len);
+}
+
+void AnimationTrackEditor::_dropped_track(int p_from_track, int p_to_track) {
+ if (p_to_track >= track_edits.size()) {
+ p_to_track = track_edits.size() - 1;
+ }
+
+ if (p_from_track == p_to_track)
+ return;
+
+ _clear_selection();
+ undo_redo->create_action("Rearrange tracks");
+ undo_redo->add_do_method(animation.ptr(), "track_swap", p_from_track, p_to_track);
+ undo_redo->add_undo_method(animation.ptr(), "track_swap", p_to_track, p_from_track);
+ undo_redo->commit_action();
+}
+
+void AnimationTrackEditor::_new_track_node_selected(NodePath p_path) {
+
+ ERR_FAIL_COND(!root);
+ Node *node = get_node(p_path);
+ ERR_FAIL_COND(!node);
+ NodePath path_to = root->get_path_to(node);
+
+ if (adding_track_type == Animation::TYPE_TRANSFORM && !node->is_class("Spatial")) {
+ EditorNode::get_singleton()->show_warning(TTR("Transform tracks only apply to Spatial-based nodes."));
+ return;
+ }
+
+ switch (adding_track_type) {
+ case Animation::TYPE_VALUE: {
+ adding_track_path = path_to;
+ prop_selector->set_type_filter(Vector<Variant::Type>());
+ prop_selector->select_property_from_instance(node);
+ } break;
+ case Animation::TYPE_TRANSFORM:
+ case Animation::TYPE_METHOD: {
+
+ undo_redo->create_action("Add Track");
+ undo_redo->add_do_method(animation.ptr(), "add_track", adding_track_type);
+ undo_redo->add_do_method(animation.ptr(), "track_set_path", animation->get_track_count(), path_to);
+ undo_redo->add_undo_method(animation.ptr(), "remove_track", animation->get_track_count());
+ undo_redo->commit_action();
+
+ } break;
+ case Animation::TYPE_BEZIER: {
+
+ Vector<Variant::Type> filter;
+ filter.push_back(Variant::INT);
+ filter.push_back(Variant::REAL);
+ filter.push_back(Variant::VECTOR2);
+ filter.push_back(Variant::VECTOR3);
+ filter.push_back(Variant::QUAT);
+ filter.push_back(Variant::PLANE);
+ filter.push_back(Variant::COLOR);
+
+ adding_track_path = path_to;
+ prop_selector->set_type_filter(filter);
+ prop_selector->select_property_from_instance(node);
+ } break;
+ case Animation::TYPE_AUDIO: {
+
+ if (!node->is_class("AudioStreamPlayer") && !node->is_class("AudioStreamPlayer2D") && !node->is_class("AudioStreamPlayer3D")) {
+ EditorNode::get_singleton()->show_warning(TTR("Audio tracks can only point to nodes of type:\n-AudioStreamPlayer\n-AudioStreamPlayer2D\n-AudioStreamPlayer3D"));
+ return;
+ }
+
+ undo_redo->create_action("Add Track");
+ undo_redo->add_do_method(animation.ptr(), "add_track", adding_track_type);
+ undo_redo->add_do_method(animation.ptr(), "track_set_path", animation->get_track_count(), path_to);
+ undo_redo->add_undo_method(animation.ptr(), "remove_track", animation->get_track_count());
+ undo_redo->commit_action();
+
+ } break;
+ case Animation::TYPE_ANIMATION: {
+
+ if (!node->is_class("AnimationPlayer")) {
+ EditorNode::get_singleton()->show_warning(TTR("Animation tracks can only point to AnimationPlayer nodes."));
+ return;
+ }
+
+ undo_redo->create_action("Add Track");
+ undo_redo->add_do_method(animation.ptr(), "add_track", adding_track_type);
+ undo_redo->add_do_method(animation.ptr(), "track_set_path", animation->get_track_count(), path_to);
+ undo_redo->add_undo_method(animation.ptr(), "remove_track", animation->get_track_count());
+ undo_redo->commit_action();
+
+ } break;
+ }
+}
+
+void AnimationTrackEditor::_add_track(int p_type) {
+ if (!root) {
+ EditorNode::get_singleton()->show_warning(TTR("Not possible to add a new track without a root"));
+ return;
+ }
+ adding_track_type = p_type;
+ pick_track->popup_centered_ratio();
+}
+
+void AnimationTrackEditor::_new_track_property_selected(String p_name) {
+
+ String full_path = String(adding_track_path) + ":" + p_name;
+
+ if (adding_track_type == Animation::TYPE_VALUE) {
+
+ Animation::UpdateMode update_mode = Animation::UPDATE_DISCRETE;
+ {
+ //hack
+ NodePath np;
+ animation->add_track(Animation::TYPE_VALUE);
+ animation->track_set_path(animation->get_track_count() - 1, full_path);
+ PropertyInfo h = _find_hint_for_track(animation->get_track_count() - 1, np);
+ animation->remove_track(animation->get_track_count() - 1); //hack
+ if (h.type == Variant::REAL ||
+ h.type == Variant::VECTOR2 ||
+ h.type == Variant::RECT2 ||
+ h.type == Variant::VECTOR3 ||
+ h.type == Variant::AABB ||
+ h.type == Variant::QUAT ||
+ h.type == Variant::COLOR ||
+ h.type == Variant::PLANE ||
+ h.type == Variant::TRANSFORM2D ||
+ h.type == Variant::TRANSFORM) {
+
+ update_mode = Animation::UPDATE_CONTINUOUS;
+ }
+
+ if (h.usage & PROPERTY_USAGE_ANIMATE_AS_TRIGGER) {
+ update_mode = Animation::UPDATE_TRIGGER;
+ }
+ }
+
+ undo_redo->create_action("Add Track");
+ undo_redo->add_do_method(animation.ptr(), "add_track", adding_track_type);
+ undo_redo->add_do_method(animation.ptr(), "track_set_path", animation->get_track_count(), full_path);
+ undo_redo->add_do_method(animation.ptr(), "value_track_set_update_mode", animation->get_track_count(), update_mode);
+ undo_redo->add_undo_method(animation.ptr(), "remove_track", animation->get_track_count());
+ undo_redo->commit_action();
+ } else {
+ Vector<String> subindices;
+ {
+ //hack
+ NodePath np;
+ animation->add_track(Animation::TYPE_VALUE);
+ animation->track_set_path(animation->get_track_count() - 1, full_path);
+ PropertyInfo h = _find_hint_for_track(animation->get_track_count() - 1, np);
+ animation->remove_track(animation->get_track_count() - 1); //hack
+ bool valid;
+ subindices = _get_bezier_subindices_for_type(h.type, &valid);
+ if (!valid) {
+ EditorNode::get_singleton()->show_warning("Invalid track for Bezier (no suitable sub-properties)");
+ return;
+ }
+ }
+
+ undo_redo->create_action("Add Bezier Track");
+ int base_track = animation->get_track_count();
+ for (int i = 0; i < subindices.size(); i++) {
+ undo_redo->add_do_method(animation.ptr(), "add_track", adding_track_type);
+ undo_redo->add_do_method(animation.ptr(), "track_set_path", base_track + i, full_path + subindices[i]);
+ undo_redo->add_undo_method(animation.ptr(), "remove_track", base_track + i);
+ }
+ undo_redo->commit_action();
+ }
+}
+
+void AnimationTrackEditor::_timeline_value_changed(double) {
+
+ timeline->update_play_position();
+
+ for (int i = 0; i < track_edits.size(); i++) {
+ track_edits[i]->update();
+ track_edits[i]->update_play_position();
+ }
+
+ for (int i = 0; i < groups.size(); i++) {
+ groups[i]->update();
+ }
+
+ bezier_edit->update();
+ bezier_edit->update_play_position();
+}
+
+int AnimationTrackEditor::_get_track_selected() {
+
+ for (int i = 0; i < track_edits.size(); i++) {
+ if (track_edits[i]->has_focus())
+ return i;
+ }
+
+ return -1;
+}
+
+void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) {
+
+ ERR_FAIL_INDEX(p_track, animation->get_track_count());
+
+ if (snap->is_pressed() && step->get_value() != 0) {
+ p_ofs = Math::stepify(p_ofs, step->get_value());
+ }
+ while (animation->track_find_key(p_track, p_ofs, true) != -1) { //make sure insertion point is valid
+ p_ofs += 0.001;
+ }
+
+ switch (animation->track_get_type(p_track)) {
+ case Animation::TYPE_TRANSFORM: {
+ if (!root->has_node(animation->track_get_path(p_track))) {
+ EditorNode::get_singleton()->show_warning(TTR("Track path is invalid, so can't add a key."));
+ return;
+ }
+ Spatial *base = Object::cast_to<Spatial>(root->get_node(animation->track_get_path(p_track)));
+
+ if (!base) {
+ EditorNode::get_singleton()->show_warning(TTR("Track is not of type Spatial, can't insert key"));
+ return;
+ }
+
+ Transform xf = base->get_transform();
+
+ Vector3 loc = xf.get_origin();
+ Vector3 scale = xf.basis.get_scale_local();
+ Quat rot = xf.basis;
+
+ undo_redo->create_action("Add Transform Track Key");
+ undo_redo->add_do_method(animation.ptr(), "transform_track_insert_key", p_track, p_ofs, loc, rot, scale);
+ undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", p_track, p_ofs);
+ undo_redo->commit_action();
+
+ } break;
+ case Animation::TYPE_VALUE: {
+
+ NodePath bp;
+ Variant value;
+ _find_hint_for_track(p_track, bp, &value);
+
+ undo_redo->create_action("Add Track Key");
+ undo_redo->add_do_method(animation.ptr(), "track_insert_key", p_track, p_ofs, value);
+ undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", p_track, p_ofs);
+ undo_redo->commit_action();
+
+ } break;
+ case Animation::TYPE_METHOD: {
+ if (!root->has_node(animation->track_get_path(p_track))) {
+ EditorNode::get_singleton()->show_warning(TTR("Track path is invalid, so can't add a method key."));
+ return;
+ }
+ Node *base = root->get_node(animation->track_get_path(p_track));
+
+ method_selector->select_method_from_instance(base);
+
+ insert_key_from_track_call_ofs = p_ofs;
+ insert_key_from_track_call_track = p_track;
+
+ } break;
+ case Animation::TYPE_BEZIER: {
+
+ NodePath bp;
+ Variant value;
+ _find_hint_for_track(p_track, bp, &value);
+ Array arr;
+ arr.resize(5);
+ arr[0] = value;
+ arr[1] = -0.25;
+ arr[2] = 0;
+ arr[3] = 0.25;
+ arr[4] = 0;
+
+ undo_redo->create_action("Add Track Key");
+ undo_redo->add_do_method(animation.ptr(), "track_insert_key", p_track, p_ofs, arr);
+ undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", p_track, p_ofs);
+ undo_redo->commit_action();
+
+ } break;
+ case Animation::TYPE_AUDIO: {
+
+ Dictionary ak;
+ ak["stream"] = RES();
+ ak["start_offset"] = 0;
+ ak["end_offset"] = 0;
+
+ undo_redo->create_action("Add Track Key");
+ undo_redo->add_do_method(animation.ptr(), "track_insert_key", p_track, p_ofs, ak);
+ undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", p_track, p_ofs);
+ undo_redo->commit_action();
+ } break;
+ case Animation::TYPE_ANIMATION: {
+
+ StringName anim = "[stop]";
+
+ undo_redo->create_action("Add Track Key");
+ undo_redo->add_do_method(animation.ptr(), "track_insert_key", p_track, p_ofs, anim);
+ undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", p_track, p_ofs);
+ undo_redo->commit_action();
+ } break;
+ }
+}
+
+void AnimationTrackEditor::_add_method_key(const String &p_method) {
+
+ if (!root->has_node(animation->track_get_path(insert_key_from_track_call_track))) {
+ EditorNode::get_singleton()->show_warning(TTR("Track path is invalid, so can't add a method key."));
+ return;
+ }
+ Node *base = root->get_node(animation->track_get_path(insert_key_from_track_call_track));
+
+ List<MethodInfo> minfo;
+ base->get_method_list(&minfo);
+
+ for (List<MethodInfo>::Element *E = minfo.front(); E; E = E->next()) {
+ if (E->get().name == p_method) {
+
+ Dictionary d;
+ d["method"] = p_method;
+ Array params;
+ int first_defarg = E->get().arguments.size() - E->get().default_arguments.size();
+
+ for (int i = 0; i < E->get().arguments.size(); i++) {
+
+ if (i >= first_defarg) {
+ Variant arg = E->get().default_arguments[i - first_defarg];
+ params.push_back(arg);
+ } else {
+ Variant::CallError ce;
+ Variant arg = Variant::construct(E->get().arguments[i].type, NULL, 0, ce);
+ params.push_back(arg);
+ }
+ }
+ d["args"] = params;
+
+ undo_redo->create_action("Add Method Track Key");
+ undo_redo->add_do_method(animation.ptr(), "track_insert_key", insert_key_from_track_call_track, insert_key_from_track_call_ofs, d);
+ undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", insert_key_from_track_call_track, insert_key_from_track_call_ofs);
+ undo_redo->commit_action();
+
+ return;
+ }
+ }
+
+ 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) {
+
+ ERR_FAIL_INDEX(p_track, animation->get_track_count());
+ ERR_FAIL_INDEX(p_key, animation->track_get_key_count(p_track));
+
+ SelectedKey sk;
+ sk.key = p_key;
+ sk.track = p_track;
+
+ if (p_single) {
+ _clear_selection();
+ }
+
+ KeyInfo ki;
+ ki.pos = animation->track_get_key_time(p_track, p_key);
+ selection[sk] = ki;
+
+ for (int i = 0; i < track_edits.size(); i++) {
+ track_edits[i]->update();
+ }
+
+ _update_key_edit();
+}
+
+void AnimationTrackEditor::_key_deselected(int p_key, int p_track) {
+
+ ERR_FAIL_INDEX(p_track, animation->get_track_count());
+ ERR_FAIL_INDEX(p_key, animation->track_get_key_count(p_track));
+
+ SelectedKey sk;
+ sk.key = p_key;
+ sk.track = p_track;
+
+ selection.erase(sk);
+
+ for (int i = 0; i < track_edits.size(); i++) {
+ track_edits[i]->update();
+ }
+
+ _update_key_edit();
+}
+
+void AnimationTrackEditor::_move_selection_begin() {
+ moving_selection = true;
+ moving_selection_offset = 0;
+}
+
+void AnimationTrackEditor::_move_selection(float p_offset) {
+ moving_selection_offset = p_offset;
+ if (snap->is_pressed() && step->get_value() != 0) {
+ moving_selection_offset = Math::stepify(moving_selection_offset, step->get_value());
+ }
+ for (int i = 0; i < track_edits.size(); i++) {
+ track_edits[i]->update();
+ }
+}
+
+struct _AnimMoveRestore {
+
+ int track;
+ float time;
+ Variant key;
+ float transition;
+};
+//used for undo/redo
+
+void AnimationTrackEditor::_clear_key_edit() {
+ if (key_edit) {
+ bool go_back = false;
+ if (EditorNode::get_singleton()->get_inspector()->get_edited_object() == key_edit) {
+ EditorNode::get_singleton()->push_item(NULL);
+ go_back = true;
+ }
+
+ memdelete(key_edit);
+ key_edit = NULL;
+
+ if (go_back) {
+ EditorNode::get_singleton()->get_inspector_dock()->go_back();
+ }
+ }
+}
+
+void AnimationTrackEditor::_clear_selection() {
+ selection.clear();
+ for (int i = 0; i < track_edits.size(); i++) {
+ track_edits[i]->update();
+ }
+ _clear_key_edit();
+}
+
+void AnimationTrackEditor::_update_key_edit() {
+
+ _clear_key_edit();
+ if (!animation.is_valid())
+ return;
+ if (selection.size() != 1) {
+ return;
+ }
+
+ key_edit = memnew(AnimationTrackKeyEdit);
+ key_edit->animation = animation;
+ key_edit->track = selection.front()->key().track;
+
+ float ofs = animation->track_get_key_time(key_edit->track, selection.front()->key().key);
+ key_edit->key_ofs = ofs;
+ key_edit->root_path = root;
+
+ NodePath np;
+ key_edit->hint = _find_hint_for_track(key_edit->track, np);
+ key_edit->undo_redo = undo_redo;
+ key_edit->base = np;
+
+ EditorNode::get_singleton()->push_item(key_edit);
+}
+
+void AnimationTrackEditor::_clear_selection_for_anim(const Ref<Animation> &p_anim) {
+
+ if (!(animation == p_anim))
+ return;
+ //selection.clear();
+ _clear_selection();
+}
+
+void AnimationTrackEditor::_select_at_anim(const Ref<Animation> &p_anim, int p_track, float p_pos) {
+
+ if (!(animation == p_anim))
+ return;
+
+ int idx = animation->track_find_key(p_track, p_pos, true);
+ ERR_FAIL_COND(idx < 0);
+
+ SelectedKey sk;
+ sk.track = p_track;
+ sk.key = idx;
+ KeyInfo ki;
+ ki.pos = p_pos;
+
+ selection.insert(sk, ki);
+}
+
+void AnimationTrackEditor::_move_selection_commit() {
+
+ undo_redo->create_action(TTR("Anim Move Keys"));
+
+ List<_AnimMoveRestore> to_restore;
+
+ float motion = moving_selection_offset;
+ // 1-remove the keys
+ for (Map<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()) {
+
+ float newtime = E->get().pos + motion;
+ int idx = animation->track_find_key(E->key().track, newtime, true);
+ if (idx == -1)
+ continue;
+ SelectedKey sk;
+ sk.key = idx;
+ sk.track = E->key().track;
+ if (selection.has(sk))
+ continue; //already in selection, don't save
+
+ undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_position", E->key().track, newtime);
+ _AnimMoveRestore amr;
+
+ amr.key = animation->track_get_key_value(E->key().track, idx);
+ amr.track = E->key().track;
+ amr.time = newtime;
+ amr.transition = animation->track_get_key_transition(E->key().track, idx);
+
+ to_restore.push_back(amr);
+ }
+
+ // 3-move the keys (re insert them)
+ for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
+
+ float newpos = E->get().pos + motion;
+ /*
+ if (newpos<0)
+ continue; //no add at the beginning
+ */
+ 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()) {
+
+ float newpos = E->get().pos + motion;
+ /*
+ if (newpos<0)
+ continue; //no remove what no inserted
+ */
+ undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", E->key().track, newpos);
+ }
+
+ // 5-(undo) reinsert keys
+ for (Map<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));
+ }
+
+ // 6-(undo) reinsert overlapped keys
+ for (List<_AnimMoveRestore>::Element *E = to_restore.front(); E; E = E->next()) {
+
+ _AnimMoveRestore &amr = E->get();
+ undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, amr.transition);
+ }
+
+ // 6-(undo) reinsert overlapped keys
+ for (List<_AnimMoveRestore>::Element *E = to_restore.front(); E; E = E->next()) {
+
+ _AnimMoveRestore &amr = E->get();
+ undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, amr.transition);
+ }
+
+ undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);
+ 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()) {
+
+ float oldpos = E->get().pos;
+ float newpos = oldpos + motion;
+ //if (newpos>=0)
+ undo_redo->add_do_method(this, "_select_at_anim", animation, E->key().track, newpos);
+ undo_redo->add_undo_method(this, "_select_at_anim", animation, E->key().track, oldpos);
+ }
+
+ undo_redo->commit_action();
+
+ moving_selection = false;
+ for (int i = 0; i < track_edits.size(); i++) {
+ track_edits[i]->update();
+ }
+}
+void AnimationTrackEditor::_move_selection_cancel() {
+
+ moving_selection = false;
+ for (int i = 0; i < track_edits.size(); i++) {
+ track_edits[i]->update();
+ }
+}
+
+bool AnimationTrackEditor::is_moving_selection() const {
+ return moving_selection;
+}
+float AnimationTrackEditor::get_moving_selection_offset() const {
+ return moving_selection_offset;
+}
+
+void AnimationTrackEditor::_box_selection_draw() {
+
+ Color color = get_color("accent_color", "Editor");
+ color.a = 0.2;
+ Rect2 rect = Rect2(Point2(), box_selection->get_size());
+ box_selection->draw_rect(rect, color);
+}
+
+void AnimationTrackEditor::_scroll_input(const Ref<InputEvent> &p_event) {
+
+ Ref<InputEventMouseButton> mb = p_event;
+
+ if (mb.is_valid() && mb->is_pressed() && mb->get_command() && mb->get_button_index() == BUTTON_WHEEL_DOWN) {
+
+ timeline->get_zoom()->set_value(timeline->get_zoom()->get_value() * 1.05);
+ scroll->accept_event();
+ }
+
+ if (mb.is_valid() && mb->is_pressed() && mb->get_command() && mb->get_button_index() == BUTTON_WHEEL_UP) {
+
+ timeline->get_zoom()->set_value(timeline->get_zoom()->get_value() / 1.05);
+ scroll->accept_event();
+ }
+
+ if (mb.is_valid() && mb->get_button_index() == BUTTON_LEFT) {
+ if (mb->is_pressed()) {
+ box_selecting = true;
+ box_selecting_from = scroll->get_global_transform().xform(mb->get_position());
+ box_select_rect = Rect2();
+ } else if (box_selecting) {
+
+ if (box_selection->is_visible_in_tree()) {
+ //only if moved
+ for (int i = 0; i < track_edits.size(); i++) {
+
+ Rect2 local_rect = box_select_rect;
+ local_rect.position -= track_edits[i]->get_global_position();
+ track_edits[i]->append_to_selection(local_rect);
+ }
+
+ if (_get_track_selected() == -1) { //minimal hack to make shortcuts work
+ track_edits[track_edits.size() - 1]->grab_focus();
+ }
+ } else {
+ _clear_selection(); //clear it
+ }
+
+ box_selection->hide();
+ box_selecting = false;
+ }
+ }
+
+ Ref<InputEventMouseMotion> mm = p_event;
+
+ if (mm.is_valid() && mm->get_button_mask() & BUTTON_MASK_MIDDLE) {
+
+ timeline->set_value(timeline->get_value() - mm->get_relative().x / timeline->get_zoom_scale());
+ }
+
+ if (mm.is_valid() && box_selecting) {
+
+ if (!(mm->get_button_mask() & BUTTON_MASK_LEFT)) {
+ //no longer
+ box_selection->hide();
+ box_selecting = false;
+ return;
+ }
+
+ if (!box_selection->is_visible_in_tree()) {
+ if (!mm->get_shift()) {
+ _clear_selection(); //only append if shift is pressed
+ }
+ box_selection->show();
+ }
+
+ Vector2 from = box_selecting_from;
+ Vector2 to = scroll->get_global_transform().xform(mm->get_position());
+
+ if (from.x > to.x) {
+ SWAP(from.x, to.x);
+ }
+
+ if (from.y > to.y) {
+ SWAP(from.y, to.y);
+ }
+
+ Rect2 rect(from, to - from);
+ Rect2 scroll_rect = Rect2(scroll->get_global_position(), scroll->get_size());
+ rect = scroll_rect.clip(rect);
+ box_selection->set_position(rect.position);
+ box_selection->set_size(rect.size);
+
+ box_select_rect = rect;
+
+ if (get_local_mouse_position().y < 0) {
+ //avoid box selection from going up and lose focus to viewport
+ warp_mouse(Vector2(mm->get_position().x, 0));
+ }
+ }
+}
+
+void AnimationTrackEditor::_cancel_bezier_edit() {
+ bezier_edit->hide();
+ scroll->show();
+}
+
+void AnimationTrackEditor::_bezier_edit(int p_for_track) {
+
+ _clear_selection(); //bezier probably wants to use a separate selection mode
+ bezier_edit->set_root(root);
+ bezier_edit->set_animation_and_track(animation, p_for_track);
+ scroll->hide();
+ bezier_edit->show();
+ //search everything within the track and curve- edit it
+}
+
+void AnimationTrackEditor::_anim_duplicate_keys(bool transpose) {
+ //duplicait!
+ 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()) {
+
+ const SelectedKey &sk = E->key();
+
+ float t = animation->track_get_key_time(sk.track, sk.key);
+ if (t < top_time)
+ top_time = t;
+ if (sk.track < top_track)
+ top_track = sk.track;
+ }
+ ERR_FAIL_COND(top_track == 0x7FFFFFFF || top_time == 1e10);
+
+ //
+
+ int start_track = transpose ? _get_track_selected() : top_track;
+
+ undo_redo->create_action(TTR("Anim Duplicate Keys"));
+
+ List<Pair<int, float> > new_selection_values;
+
+ for (Map<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);
+
+ float dst_time = t + (timeline->get_play_position() - top_time);
+ int dst_track = sk.track + (start_track - top_track);
+
+ if (dst_track < 0 || dst_track >= animation->get_track_count())
+ continue;
+
+ if (animation->track_get_type(dst_track) != animation->track_get_type(sk.track))
+ continue;
+
+ int existing_idx = animation->track_find_key(dst_track, dst_time, true);
+
+ undo_redo->add_do_method(animation.ptr(), "track_insert_key", dst_track, dst_time, animation->track_get_key_value(E->key().track, E->key().key), animation->track_get_key_transition(E->key().track, E->key().key));
+ undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", dst_track, dst_time);
+
+ Pair<int, float> p;
+ p.first = dst_track;
+ p.second = dst_time;
+ new_selection_values.push_back(p);
+
+ if (existing_idx != -1) {
+
+ undo_redo->add_undo_method(animation.ptr(), "track_insert_key", dst_track, dst_time, animation->track_get_key_value(dst_track, existing_idx), animation->track_get_key_transition(dst_track, existing_idx));
+ }
+ }
+
+ undo_redo->commit_action();
+
+ //reselect duplicated
+
+ Map<SelectedKey, KeyInfo> new_selection;
+ for (List<Pair<int, float> >::Element *E = new_selection_values.front(); E; E = E->next()) {
+
+ int track = E->get().first;
+ float time = E->get().second;
+
+ int existing_idx = animation->track_find_key(track, time, true);
+
+ if (existing_idx == -1)
+ continue;
+ SelectedKey sk2;
+ sk2.track = track;
+ sk2.key = existing_idx;
+
+ KeyInfo ki;
+ ki.pos = time;
+
+ new_selection[sk2] = ki;
+ }
+
+ selection = new_selection;
+ _update_tracks();
+ _update_key_edit();
+ }
+}
+void AnimationTrackEditor::_edit_menu_pressed(int p_option) {
+
+ last_menu_track_opt = p_option;
+ switch (p_option) {
+ case EDIT_COPY_TRACKS: {
+ track_copy_select->clear();
+ TreeItem *troot = track_copy_select->create_item();
+
+ for (int i = 0; i < animation->get_track_count(); i++) {
+
+ NodePath path = animation->track_get_path(i);
+ Node *node = NULL;
+
+ if (root && root->has_node(path)) {
+ node = root->get_node(path);
+ }
+
+ String text;
+ Ref<Texture> icon = get_icon("Node", "EditorIcons");
+ if (node) {
+ if (has_icon(node->get_class(), "EditorIcons")) {
+ icon = get_icon(node->get_class(), "EditorIcons");
+ }
+
+ text = node->get_name();
+ Vector<StringName> sn = path.get_subnames();
+ for (int i = 0; i < sn.size(); i++) {
+ text += ".";
+ text += sn[i];
+ }
+
+ path = NodePath(node->get_path().get_names(), path.get_subnames(), true); //store full path instead for copying
+ } else {
+ text = path;
+ int sep = text.find(":");
+ if (sep != -1) {
+ text = text.substr(sep + 1, text.length());
+ }
+ }
+
+ switch (animation->track_get_type(i)) {
+ case Animation::TYPE_TRANSFORM: text += " (Transform)"; break;
+ case Animation::TYPE_METHOD: text += " (Methods)"; break;
+ case Animation::TYPE_BEZIER: text += " (Bezier)"; break;
+ case Animation::TYPE_AUDIO: text += " (Audio)"; break;
+ default: {};
+ }
+
+ TreeItem *it = track_copy_select->create_item(troot);
+ it->set_editable(0, true);
+ it->set_selectable(0, true);
+ it->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
+ it->set_icon(0, icon);
+ it->set_text(0, text);
+ Dictionary md;
+ md["track_idx"] = i;
+ md["path"] = path;
+ it->set_metadata(0, md);
+ }
+
+ track_copy_dialog->popup_centered_minsize(Size2(300, 500) * EDSCALE);
+ } break;
+ case EDIT_COPY_TRACKS_CONFIRM: {
+
+ track_clipboard.clear();
+ TreeItem *root = track_copy_select->get_root();
+ if (root) {
+
+ TreeItem *it = root->get_children();
+ while (it) {
+ Dictionary md = it->get_metadata(0);
+ int idx = md["track_idx"];
+ if (it->is_checked(0) && idx >= 0 && idx < animation->get_track_count()) {
+ TrackClipboard tc;
+ tc.base_path = animation->track_get_path(idx);
+ tc.full_path = md["path"];
+ tc.track_type = animation->track_get_type(idx);
+ tc.interp_type = animation->track_get_interpolation_type(idx);
+ if (tc.track_type == Animation::TYPE_VALUE) {
+ tc.update_mode = animation->value_track_get_update_mode(idx);
+ }
+ tc.loop_wrap = animation->track_get_interpolation_loop_wrap(idx);
+ tc.enabled = animation->track_is_enabled(idx);
+ for (int i = 0; i < animation->track_get_key_count(idx); i++) {
+ TrackClipboard::Key k;
+ k.time = animation->track_get_key_time(idx, i);
+ k.value = animation->track_get_key_value(idx, i);
+ k.transition = animation->track_get_key_transition(idx, i);
+ tc.keys.push_back(k);
+ }
+ track_clipboard.push_back(tc);
+ }
+ it = it->get_next();
+ }
+ }
+ } break;
+ case EDIT_PASTE_TRACKS: {
+
+ if (track_clipboard.size() == 0) {
+ EditorNode::get_singleton()->show_warning(TTR("Clipboard is empty"));
+ break;
+ }
+
+ int base_track = animation->get_track_count();
+ undo_redo->create_action("Paste Tracks");
+ for (int i = 0; i < track_clipboard.size(); i++) {
+ undo_redo->add_do_method(animation.ptr(), "add_track", track_clipboard[i].track_type);
+ Node *exists = NULL;
+ NodePath path = track_clipboard[i].base_path;
+
+ if (root) {
+ NodePath np = track_clipboard[i].full_path;
+ exists = root->get_node(np);
+ if (exists) {
+ path = NodePath(root->get_path_to(exists).get_names(), track_clipboard[i].full_path.get_subnames(), false);
+ }
+ }
+
+ undo_redo->add_do_method(animation.ptr(), "track_set_path", base_track, path);
+ undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_type", base_track, track_clipboard[i].interp_type);
+ undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_loop_wrap", base_track, track_clipboard[i].loop_wrap);
+ undo_redo->add_do_method(animation.ptr(), "track_set_enabled", base_track, track_clipboard[i].enabled);
+ if (track_clipboard[i].track_type == Animation::TYPE_VALUE) {
+ undo_redo->add_do_method(animation.ptr(), "value_track_set_update_mode", base_track, track_clipboard[i].update_mode);
+ }
+
+ for (int j = 0; j < track_clipboard[i].keys.size(); j++) {
+ undo_redo->add_do_method(animation.ptr(), "track_insert_key", base_track, track_clipboard[i].keys[j].time, track_clipboard[i].keys[j].value, track_clipboard[i].keys[j].transition);
+ }
+
+ undo_redo->add_undo_method(animation.ptr(), "remove_track", animation->get_track_count());
+
+ base_track++;
+ }
+
+ undo_redo->commit_action();
+ } break;
+
+ case EDIT_SCALE_SELECTION:
+ case EDIT_SCALE_FROM_CURSOR: {
+ scale_dialog->popup_centered(Size2(200, 100) * EDSCALE);
+ } break;
+ case EDIT_SCALE_CONFIRM: {
+ if (selection.empty())
+ return;
+
+ float from_t = 1e20;
+ float to_t = -1e20;
+ float len = -1e20;
+ float pivot = 0;
+
+ for (Map<SelectedKey, KeyInfo>::Element *E = selection.front(); E; E = E->next()) {
+ float t = animation->track_get_key_time(E->key().track, E->key().key);
+ if (t < from_t)
+ from_t = t;
+ if (t > to_t)
+ to_t = t;
+ }
+
+ len = to_t - from_t;
+ if (last_menu_track_opt == EDIT_SCALE_FROM_CURSOR) {
+ pivot = timeline->get_play_position();
+
+ } else {
+
+ pivot = from_t;
+ }
+
+ float s = scale->get_value();
+ if (s == 0) {
+ ERR_PRINT("Can't scale to 0");
+ }
+
+ undo_redo->create_action(TTR("Anim Scale Keys"));
+
+ List<_AnimMoveRestore> to_restore;
+
+ // 1-remove the keys
+ for (Map<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()) {
+
+ float newtime = (E->get().pos - from_t) * s + from_t;
+ int idx = animation->track_find_key(E->key().track, newtime, true);
+ if (idx == -1)
+ continue;
+ SelectedKey sk;
+ sk.key = idx;
+ sk.track = E->key().track;
+ if (selection.has(sk))
+ continue; //already in selection, don't save
+
+ undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_position", E->key().track, newtime);
+ _AnimMoveRestore amr;
+
+ amr.key = animation->track_get_key_value(E->key().track, idx);
+ amr.track = E->key().track;
+ amr.time = newtime;
+ amr.transition = animation->track_get_key_transition(E->key().track, idx);
+
+ to_restore.push_back(amr);
+ }
+
+#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()) {
+
+ 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()) {
+
+ float newpos = _NEW_POS(E->get().pos);
+ undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", E->key().track, newpos);
+ }
+
+ // 5-(undo) reinsert keys
+ for (Map<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));
+ }
+
+ // 6-(undo) reinsert overlapped keys
+ for (List<_AnimMoveRestore>::Element *E = to_restore.front(); E; E = E->next()) {
+
+ _AnimMoveRestore &amr = E->get();
+ undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, amr.transition);
+ }
+
+ // 6-(undo) reinsert overlapped keys
+ for (List<_AnimMoveRestore>::Element *E = to_restore.front(); E; E = E->next()) {
+
+ _AnimMoveRestore &amr = E->get();
+ undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, amr.transition);
+ }
+
+ undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);
+ 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()) {
+
+ float oldpos = E->get().pos;
+ float newpos = _NEW_POS(oldpos);
+ if (newpos >= 0)
+ undo_redo->add_do_method(this, "_select_at_anim", animation, E->key().track, newpos);
+ undo_redo->add_undo_method(this, "_select_at_anim", animation, E->key().track, oldpos);
+ }
+#undef _NEW_POS
+ undo_redo->commit_action();
+ } break;
+ case EDIT_DUPLICATE_SELECTION: {
+
+ if (bezier_edit->is_visible()) {
+ bezier_edit->duplicate_selection();
+ break;
+ }
+ _anim_duplicate_keys(false);
+ } break;
+ case EDIT_DUPLICATE_TRANSPOSED: {
+ if (bezier_edit->is_visible()) {
+ EditorNode::get_singleton()->show_warning(TTR("This option does not work for Bezier editing, as it's only a single track."));
+ break;
+ }
+ _anim_duplicate_keys(true);
+ } break;
+ case EDIT_DELETE_SELECTION: {
+
+ if (bezier_edit->is_visible()) {
+ bezier_edit->delete_selection();
+ break;
+ }
+
+ if (selection.size()) {
+ undo_redo->create_action(TTR("Anim Delete Keys"));
+
+ for (Map<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));
+ }
+ undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);
+ undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
+ undo_redo->commit_action();
+ //selection.clear();
+ _update_key_edit();
+ }
+ } break;
+ case EDIT_GOTO_NEXT_STEP: {
+
+ if (animation.is_null())
+ break;
+ float step = animation->get_step();
+ if (step == 0)
+ step = 1;
+
+ float pos = timeline->get_play_position();
+
+ pos = Math::stepify(pos + step, step);
+ if (pos > animation->get_length())
+ pos = animation->get_length();
+ set_anim_pos(pos);
+
+ emit_signal("timeline_changed", pos, true);
+
+ } break;
+ case EDIT_GOTO_PREV_STEP: {
+ if (animation.is_null())
+ break;
+ float step = animation->get_step();
+ if (step == 0)
+ step = 1;
+
+ float pos = timeline->get_play_position();
+ pos = Math::stepify(pos - step, step);
+ if (pos < 0)
+ pos = 0;
+ set_anim_pos(pos);
+ emit_signal("timeline_changed", pos, true);
+
+ } break;
+ case EDIT_OPTIMIZE_ANIMATION: {
+ optimize_dialog->popup_centered(Size2(250, 180) * EDSCALE);
+
+ } break;
+ case EDIT_OPTIMIZE_ANIMATION_CONFIRM: {
+ animation->optimize(optimize_linear_error->get_value(), optimize_angular_error->get_value(), optimize_max_angle->get_value());
+ _update_tracks();
+ undo_redo->clear_history();
+
+ } break;
+ case EDIT_CLEAN_UP_ANIMATION: {
+ cleanup_dialog->popup_centered_minsize(Size2(300, 0) * EDSCALE);
+
+ } break;
+ case EDIT_CLEAN_UP_ANIMATION_CONFIRM: {
+ if (cleanup_all->is_pressed()) {
+ List<StringName> names;
+ AnimationPlayerEditor::singleton->get_player()->get_animation_list(&names);
+ for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
+ _cleanup_animation(AnimationPlayerEditor::singleton->get_player()->get_animation(E->get()));
+ }
+ } else {
+ _cleanup_animation(animation);
+ }
+
+ } break;
+ }
+}
+
+void AnimationTrackEditor::_cleanup_animation(Ref<Animation> p_animation) {
+
+ for (int i = 0; i < p_animation->get_track_count(); i++) {
+
+ bool prop_exists = false;
+ Variant::Type valid_type = Variant::NIL;
+ Object *obj = NULL;
+
+ RES res;
+ Vector<StringName> leftover_path;
+
+ Node *node = root->get_node_and_resource(p_animation->track_get_path(i), res, leftover_path);
+
+ if (res.is_valid()) {
+ obj = res.ptr();
+ } else if (node) {
+ obj = node;
+ }
+
+ if (obj && p_animation->track_get_type(i) == Animation::TYPE_VALUE) {
+ valid_type = obj->get_static_property_type_indexed(leftover_path, &prop_exists);
+ }
+
+ if (!obj && cleanup_tracks->is_pressed()) {
+
+ p_animation->remove_track(i);
+ i--;
+ continue;
+ }
+
+ if (!prop_exists || p_animation->track_get_type(i) != Animation::TYPE_VALUE || cleanup_keys->is_pressed() == false)
+ continue;
+
+ for (int j = 0; j < p_animation->track_get_key_count(i); j++) {
+
+ Variant v = p_animation->track_get_key_value(i, j);
+
+ if (!Variant::can_convert(v.get_type(), valid_type)) {
+ p_animation->track_remove_key(i, j);
+ j--;
+ }
+ }
+
+ if (p_animation->track_get_key_count(i) == 0 && cleanup_tracks->is_pressed()) {
+ p_animation->remove_track(i);
+ i--;
+ }
+ }
+
+ undo_redo->clear_history();
+ _update_tracks();
+}
+
+void AnimationTrackEditor::_view_group_toggle() {
+ _update_tracks();
+ view_group->set_icon(get_icon(view_group->is_pressed() ? "AnimationTrackList" : "AnimationTrackGroup", "EditorIcons"));
+}
+
+void AnimationTrackEditor::_selection_changed() {
+
+ if (selected_filter->is_pressed()) {
+ _update_tracks(); //needs updatin
+ } else {
+ for (int i = 0; i < track_edits.size(); i++) {
+ track_edits[i]->update();
+ }
+
+ for (int i = 0; i < groups.size(); i++) {
+ groups[i]->update();
+ }
+ }
+}
+
+float AnimationTrackEditor::snap_time(float p_value) {
+
+ if (snap->is_pressed()) {
+ p_value = Math::stepify(p_value, step->get_value());
+ }
+
+ return p_value;
+}
+
+void AnimationTrackEditor::_bind_methods() {
+
+ ClassDB::bind_method("_animation_changed", &AnimationTrackEditor::_animation_changed);
+ ClassDB::bind_method("_timeline_changed", &AnimationTrackEditor::_timeline_changed);
+ ClassDB::bind_method("_track_remove_request", &AnimationTrackEditor::_track_remove_request);
+ ClassDB::bind_method("_name_limit_changed", &AnimationTrackEditor::_name_limit_changed);
+ ClassDB::bind_method("_update_scroll", &AnimationTrackEditor::_update_scroll);
+ ClassDB::bind_method("_update_step", &AnimationTrackEditor::_update_step);
+ ClassDB::bind_method("_update_length", &AnimationTrackEditor::_update_length);
+ ClassDB::bind_method("_dropped_track", &AnimationTrackEditor::_dropped_track);
+ ClassDB::bind_method("_add_track", &AnimationTrackEditor::_add_track);
+ ClassDB::bind_method("_new_track_node_selected", &AnimationTrackEditor::_new_track_node_selected);
+ ClassDB::bind_method("_new_track_property_selected", &AnimationTrackEditor::_new_track_property_selected);
+ ClassDB::bind_method("_root_removed", &AnimationTrackEditor::_root_removed);
+ ClassDB::bind_method("_confirm_insert_list", &AnimationTrackEditor::_confirm_insert_list);
+ ClassDB::bind_method("_insert_delay", &AnimationTrackEditor::_insert_delay);
+ ClassDB::bind_method("_timeline_value_changed", &AnimationTrackEditor::_timeline_value_changed);
+ ClassDB::bind_method("_insert_key_from_track", &AnimationTrackEditor::_insert_key_from_track);
+ ClassDB::bind_method("_add_method_key", &AnimationTrackEditor::_add_method_key);
+ ClassDB::bind_method("_key_selected", &AnimationTrackEditor::_key_selected);
+ ClassDB::bind_method("_key_deselected", &AnimationTrackEditor::_key_deselected);
+ ClassDB::bind_method("_clear_selection", &AnimationTrackEditor::_clear_selection);
+ ClassDB::bind_method("_move_selection_begin", &AnimationTrackEditor::_move_selection_begin);
+ ClassDB::bind_method("_move_selection", &AnimationTrackEditor::_move_selection);
+ ClassDB::bind_method("_move_selection_commit", &AnimationTrackEditor::_move_selection_commit);
+ ClassDB::bind_method("_move_selection_cancel", &AnimationTrackEditor::_move_selection_cancel);
+ ClassDB::bind_method("_clear_selection_for_anim", &AnimationTrackEditor::_clear_selection_for_anim);
+ ClassDB::bind_method("_select_at_anim", &AnimationTrackEditor::_select_at_anim);
+ ClassDB::bind_method("_scroll_input", &AnimationTrackEditor::_scroll_input);
+ ClassDB::bind_method("_box_selection_draw", &AnimationTrackEditor::_box_selection_draw);
+ ClassDB::bind_method("_bezier_edit", &AnimationTrackEditor::_bezier_edit);
+ ClassDB::bind_method("_cancel_bezier_edit", &AnimationTrackEditor::_cancel_bezier_edit);
+ ClassDB::bind_method("_edit_menu_pressed", &AnimationTrackEditor::_edit_menu_pressed);
+ ClassDB::bind_method("_view_group_toggle", &AnimationTrackEditor::_view_group_toggle);
+ ClassDB::bind_method("_selection_changed", &AnimationTrackEditor::_selection_changed);
+
+ ADD_SIGNAL(MethodInfo("timeline_changed", PropertyInfo(Variant::REAL, "position"), PropertyInfo(Variant::BOOL, "drag")));
+ ADD_SIGNAL(MethodInfo("keying_changed"));
+ ADD_SIGNAL(MethodInfo("animation_len_changed", PropertyInfo(Variant::REAL, "len")));
+ ADD_SIGNAL(MethodInfo("animation_step_changed", PropertyInfo(Variant::REAL, "step")));
+}
+
+AnimationTrackEditor::AnimationTrackEditor() {
+ root = NULL;
+ block_animation_update = false;
+
+ undo_redo = EditorNode::get_singleton()->get_undo_redo();
+ HBoxContainer *timeline_scroll = memnew(HBoxContainer);
+ add_child(timeline_scroll);
+ timeline_scroll->set_v_size_flags(SIZE_EXPAND_FILL);
+
+ VBoxContainer *timeline_vbox = memnew(VBoxContainer);
+ timeline_scroll->add_child(timeline_vbox);
+ timeline_vbox->set_v_size_flags(SIZE_EXPAND_FILL);
+ timeline_vbox->set_h_size_flags(SIZE_EXPAND_FILL);
+ timeline_vbox->add_constant_override("separation", 0);
+
+ timeline = memnew(AnimationTimelineEdit);
+ timeline->set_block_animation_update_ptr(&block_animation_update);
+ timeline->set_undo_redo(undo_redo);
+ timeline_vbox->add_child(timeline);
+ timeline->connect("timeline_changed", this, "_timeline_changed");
+ timeline->connect("name_limit_changed", this, "_name_limit_changed");
+ timeline->connect("track_added", this, "_add_track");
+ timeline->connect("value_changed", this, "_timeline_value_changed");
+ timeline->connect("length_changed", this, "_update_length");
+
+ scroll = memnew(ScrollContainer);
+ timeline_vbox->add_child(scroll);
+ scroll->set_v_size_flags(SIZE_EXPAND_FILL);
+ VScrollBar *sb = scroll->get_v_scrollbar();
+ scroll->remove_child(sb);
+ timeline_scroll->add_child(sb); //move here so timeline and tracks are always aligned
+ scroll->connect("gui_input", this, "_scroll_input");
+
+ bezier_edit = memnew(AnimationBezierTrackEdit);
+ timeline_vbox->add_child(bezier_edit);
+ bezier_edit->set_block_animation_update_ptr(&block_animation_update);
+ bezier_edit->set_undo_redo(undo_redo);
+ bezier_edit->set_editor(this);
+ bezier_edit->set_timeline(timeline);
+ bezier_edit->hide();
+ bezier_edit->set_v_size_flags(SIZE_EXPAND_FILL);
+ bezier_edit->connect("close_request", this, "_cancel_bezier_edit");
+
+ timeline_vbox->set_custom_minimum_size(Size2(0, 150) * EDSCALE);
+
+ hscroll = memnew(HScrollBar);
+ timeline_vbox->add_child(hscroll);
+ hscroll->share(timeline);
+ hscroll->connect("value_changed", this, "_update_scroll");
+ timeline->set_hscroll(hscroll);
+
+ track_vbox = memnew(VBoxContainer);
+ scroll->add_child(track_vbox);
+ track_vbox->set_h_size_flags(SIZE_EXPAND_FILL);
+ scroll->set_enable_h_scroll(false);
+ scroll->set_enable_v_scroll(true);
+ track_vbox->add_constant_override("separation", 0);
+
+ timeline_vbox->add_child(memnew(HSeparator));
+ HBoxContainer *bottom_hb = memnew(HBoxContainer);
+ add_child(bottom_hb);
+ bottom_hb->add_spacer();
+
+ selected_filter = memnew(ToolButton);
+ selected_filter->connect("pressed", this, "_view_group_toggle"); //same function works the same
+ selected_filter->set_toggle_mode(true);
+ selected_filter->set_tooltip(TTR("Only show tracks from nodes selected in tree."));
+
+ bottom_hb->add_child(selected_filter);
+
+ view_group = memnew(ToolButton);
+ view_group->connect("pressed", this, "_view_group_toggle");
+ view_group->set_toggle_mode(true);
+ view_group->set_tooltip(TTR("Group tracks by node or display them as plain list."));
+
+ bottom_hb->add_child(view_group);
+ bottom_hb->add_child(memnew(VSeparator));
+
+ snap = memnew(ToolButton);
+ snap->set_text(TTR("Snap (s): "));
+ bottom_hb->add_child(snap);
+ snap->set_disabled(true);
+ snap->set_toggle_mode(true);
+ snap->set_pressed(true);
+
+ step = memnew(EditorSpinSlider);
+ step->set_min(0);
+ step->set_max(1000);
+ step->set_step(0.01);
+ step->set_hide_slider(true);
+ step->set_custom_minimum_size(Size2(100, 0) * EDSCALE);
+ bottom_hb->add_child(step);
+ step->connect("value_changed", this, "_update_step");
+ step->set_read_only(true);
+
+ bottom_hb->add_child(memnew(VSeparator));
+
+ zoom_icon = memnew(TextureRect);
+ zoom_icon->set_v_size_flags(SIZE_SHRINK_CENTER);
+ bottom_hb->add_child(zoom_icon);
+ zoom = memnew(HSlider);
+ zoom->set_step(0.01);
+ zoom->set_min(0.0);
+ zoom->set_max(2.0);
+ zoom->set_value(1.0);
+ zoom->set_custom_minimum_size(Size2(200, 0) * EDSCALE);
+ zoom->set_v_size_flags(SIZE_SHRINK_CENTER);
+ bottom_hb->add_child(zoom);
+ timeline->set_zoom(zoom);
+
+ edit = memnew(MenuButton);
+ edit->set_text(TTR("Edit"));
+ edit->set_flat(false);
+ edit->get_popup()->add_item(TTR("Copy Tracks"), EDIT_COPY_TRACKS);
+ edit->get_popup()->add_item(TTR("Paste Tracks"), EDIT_PASTE_TRACKS);
+ edit->get_popup()->add_separator();
+ edit->get_popup()->add_item(TTR("Scale Selection"), EDIT_SCALE_SELECTION);
+ edit->get_popup()->add_item(TTR("Scale From Cursor"), EDIT_SCALE_FROM_CURSOR);
+ edit->get_popup()->add_separator();
+ edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/duplicate_selection", TTR("Duplicate Selection"), KEY_MASK_CMD | KEY_D), EDIT_DUPLICATE_SELECTION);
+ edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/duplicate_selection_transposed", TTR("Duplicate Transposed"), KEY_MASK_SHIFT | KEY_MASK_CMD | KEY_D), EDIT_DUPLICATE_TRANSPOSED);
+ edit->get_popup()->set_item_shortcut_disabled(edit->get_popup()->get_item_index(EDIT_DUPLICATE_SELECTION), true);
+ edit->get_popup()->set_item_shortcut_disabled(edit->get_popup()->get_item_index(EDIT_DUPLICATE_TRANSPOSED), true);
+ edit->get_popup()->add_separator();
+ edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/delete_selection", TTR("Delete Selection"), KEY_DELETE), EDIT_DELETE_SELECTION);
+ edit->get_popup()->set_item_shortcut_disabled(edit->get_popup()->get_item_index(EDIT_DELETE_SELECTION), true);
+ //this shortcut will be checked from the track itself. so no need to enable it here (will conflict with scenetree dock)
+
+ edit->get_popup()->add_separator();
+ edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/goto_next_step", TTR("Goto Next Step"), KEY_MASK_CMD | KEY_RIGHT), EDIT_GOTO_NEXT_STEP);
+ edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/goto_prev_step", TTR("Goto Prev Step"), KEY_MASK_CMD | KEY_LEFT), EDIT_GOTO_PREV_STEP);
+ edit->get_popup()->add_separator();
+ edit->get_popup()->add_item(TTR("Optimize Animation"), EDIT_OPTIMIZE_ANIMATION);
+ edit->get_popup()->add_item(TTR("Clean-Up Animation"), EDIT_CLEAN_UP_ANIMATION);
+
+ edit->get_popup()->connect("id_pressed", this, "_edit_menu_pressed");
+
+ pick_track = memnew(SceneTreeDialog);
+ add_child(pick_track);
+ pick_track->set_title(TTR("Pick the node that will be animated:"));
+ pick_track->connect("selected", this, "_new_track_node_selected");
+ prop_selector = memnew(PropertySelector);
+ add_child(prop_selector);
+ prop_selector->connect("selected", this, "_new_track_property_selected");
+
+ method_selector = memnew(PropertySelector);
+ add_child(method_selector);
+ method_selector->connect("selected", this, "_add_method_key");
+
+ inserting = false;
+ insert_query = false;
+ insert_frame = 0;
+ insert_queue = false;
+
+ insert_confirm = memnew(ConfirmationDialog);
+ add_child(insert_confirm);
+ insert_confirm->connect("confirmed", this, "_confirm_insert_list");
+ VBoxContainer *icvb = memnew(VBoxContainer);
+ insert_confirm->add_child(icvb);
+ insert_confirm_text = memnew(Label);
+ icvb->add_child(insert_confirm_text);
+ insert_confirm_bezier = memnew(CheckBox);
+ insert_confirm_bezier->set_text(TTR("Use Bezier Curves"));
+ icvb->add_child(insert_confirm_bezier);
+ keying = false;
+ moving_selection = 0;
+ key_edit = NULL;
+
+ box_selection = memnew(Control);
+ add_child(box_selection);
+ box_selection->set_as_toplevel(true);
+ box_selection->set_mouse_filter(MOUSE_FILTER_IGNORE);
+ box_selection->hide();
+ box_selection->connect("draw", this, "_box_selection_draw");
+ box_selecting = false;
+
+ //default plugins
+
+ Ref<AnimationTrackEditDefaultPlugin> def_plugin;
+ def_plugin.instance();
+ add_track_edit_plugin(def_plugin);
+
+ //dialogs
+
+ optimize_dialog = memnew(ConfirmationDialog);
+ add_child(optimize_dialog);
+ optimize_dialog->set_title(TTR("Anim. Optimizer"));
+ VBoxContainer *optimize_vb = memnew(VBoxContainer);
+ optimize_dialog->add_child(optimize_vb);
+
+ optimize_linear_error = memnew(SpinBox);
+ optimize_linear_error->set_max(1.0);
+ optimize_linear_error->set_min(0.001);
+ optimize_linear_error->set_step(0.001);
+ optimize_linear_error->set_value(0.05);
+ optimize_vb->add_margin_child(TTR("Max. Linear Error:"), optimize_linear_error);
+ optimize_angular_error = memnew(SpinBox);
+ optimize_angular_error->set_max(1.0);
+ optimize_angular_error->set_min(0.001);
+ optimize_angular_error->set_step(0.001);
+ optimize_angular_error->set_value(0.01);
+
+ optimize_vb->add_margin_child(TTR("Max. Angular Error:"), optimize_angular_error);
+ optimize_max_angle = memnew(SpinBox);
+ optimize_vb->add_margin_child(TTR("Max Optimizable Angle:"), optimize_max_angle);
+ optimize_max_angle->set_max(360.0);
+ optimize_max_angle->set_min(0.0);
+ optimize_max_angle->set_step(0.1);
+ optimize_max_angle->set_value(22);
+
+ optimize_dialog->get_ok()->set_text(TTR("Optimize"));
+ optimize_dialog->connect("confirmed", this, "_edit_menu_pressed", varray(EDIT_CLEAN_UP_ANIMATION_CONFIRM));
+
+ //
+
+ cleanup_dialog = memnew(ConfirmationDialog);
+ add_child(cleanup_dialog);
+ VBoxContainer *cleanup_vb = memnew(VBoxContainer);
+ cleanup_dialog->add_child(cleanup_vb);
+
+ cleanup_keys = memnew(CheckButton);
+ cleanup_keys->set_text(TTR("Remove invalid keys"));
+ cleanup_keys->set_pressed(true);
+ cleanup_vb->add_child(cleanup_keys);
+
+ cleanup_tracks = memnew(CheckButton);
+ cleanup_tracks->set_text(TTR("Remove unresolved and empty tracks"));
+ cleanup_tracks->set_pressed(true);
+ cleanup_vb->add_child(cleanup_tracks);
+
+ cleanup_all = memnew(CheckButton);
+ cleanup_all->set_text(TTR("Clean-up all animations"));
+ cleanup_vb->add_child(cleanup_all);
+
+ cleanup_dialog->set_title(TTR("Clean-Up Animation(s) (NO UNDO!)"));
+ cleanup_dialog->get_ok()->set_text(TTR("Clean-Up"));
+
+ cleanup_dialog->connect("confirmed", this, "_edit_menu_pressed", varray(EDIT_CLEAN_UP_ANIMATION_CONFIRM));
+
+ //
+ scale_dialog = memnew(ConfirmationDialog);
+ VBoxContainer *vbc = memnew(VBoxContainer);
+ scale_dialog->add_child(vbc);
+
+ scale = memnew(SpinBox);
+ scale->set_min(-99999);
+ scale->set_max(99999);
+ scale->set_step(0.001);
+ vbc->add_margin_child(TTR("Scale Ratio:"), scale);
+ scale_dialog->connect("confirmed", this, "_edit_menu_pressed", varray(EDIT_SCALE_CONFIRM));
+ add_child(scale_dialog);
+
+ track_copy_dialog = memnew(ConfirmationDialog);
+ add_child(track_copy_dialog);
+ track_copy_dialog->set_title(TTR("Select tracks to copy:"));
+ track_copy_dialog->get_ok()->set_text(TTR("Copy"));
+
+ track_copy_select = memnew(Tree);
+ track_copy_select->set_hide_root(true);
+ track_copy_dialog->add_child(track_copy_select);
+ track_copy_dialog->connect("confirmed", this, "_edit_menu_pressed", varray(EDIT_COPY_TRACKS_CONFIRM));
+}
+
+AnimationTrackEditor::~AnimationTrackEditor() {
+ if (key_edit) {
+ memdelete(key_edit);
+ }
+}
diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h
new file mode 100644
index 0000000000..92caa8d408
--- /dev/null
+++ b/editor/animation_track_editor.h
@@ -0,0 +1,483 @@
+#ifndef ANIMATION_TRACK_EDITOR_H
+#define ANIMATION_TRACK_EDITOR_H
+
+#include "scene/gui/control.h"
+#include "scene/gui/file_dialog.h"
+#include "scene/gui/menu_button.h"
+#include "scene/gui/scroll_bar.h"
+#include "scene/gui/slider.h"
+#include "scene/gui/spin_box.h"
+#include "scene/gui/tab_container.h"
+#include "scene/gui/texture_rect.h"
+#include "scene/gui/tool_button.h"
+
+#include "editor/property_selector.h"
+#include "editor_data.h"
+#include "editor_spin_slider.h"
+#include "property_editor.h"
+#include "scene/animation/animation_cache.h"
+#include "scene/resources/animation.h"
+#include "scene_tree_editor.h"
+
+class AnimationTimelineEdit : public Range {
+ GDCLASS(AnimationTimelineEdit, Range)
+
+ Ref<Animation> animation;
+ int name_limit;
+ Range *zoom;
+ Range *h_scroll;
+ float play_position_pos;
+
+ HBoxContainer *len_hb;
+ EditorSpinSlider *length;
+ ToolButton *loop;
+ TextureRect *time_icon;
+
+ MenuButton *add_track;
+ Control *play_position; //separate control used to draw so updates for only position changed are much faster
+ HScrollBar *hscroll;
+
+ void _zoom_changed(double);
+ void _anim_length_changed(double p_new_len);
+ void _anim_loop_pressed();
+
+ void _play_position_draw();
+ UndoRedo *undo_redo;
+ Rect2 hsize_rect;
+
+ bool editing;
+ bool *block_animation_update_ptr; //used to block all tracks re-gen (speed up)
+
+ bool panning_timeline;
+ float panning_timeline_from;
+ float panning_timeline_at;
+ bool dragging_timeline;
+ bool dragging_hsize;
+ float dragging_hsize_from;
+ float dragging_hsize_at;
+
+ void _gui_input(const Ref<InputEvent> &p_event);
+ void _track_added(int p_track);
+
+protected:
+ static void _bind_methods();
+ void _notification(int p_what);
+
+public:
+ int get_name_limit() const;
+ int get_buttons_width() const;
+
+ float get_zoom_scale() const;
+
+ virtual Size2 get_minimum_size() const;
+ void set_animation(const Ref<Animation> &p_animation);
+ void set_zoom(Range *p_zoom);
+ Range *get_zoom() const { return zoom; }
+ void set_undo_redo(UndoRedo *p_undo_redo);
+ void set_block_animation_update_ptr(bool *p_block_ptr);
+
+ void set_play_position(float p_pos);
+ float get_play_position() const;
+ void update_play_position();
+
+ void update_values();
+
+ void set_hscroll(HScrollBar *p_hscroll);
+
+ AnimationTimelineEdit();
+};
+
+class AnimationTrackEditor;
+
+class AnimationTrackEdit : public Control {
+
+ GDCLASS(AnimationTrackEdit, Control)
+
+ enum {
+ MENU_CALL_MODE_CONTINUOUS,
+ MENU_CALL_MODE_DISCRETE,
+ MENU_CALL_MODE_TRIGGER,
+ MENU_CALL_MODE_CAPTURE,
+ MENU_INTERPOLATION_NEAREST,
+ MENU_INTERPOLATION_LINEAR,
+ MENU_INTERPOLATION_CUBIC,
+ MENU_LOOP_WRAP,
+ MENU_LOOP_CLAMP,
+ MENU_KEY_INSERT,
+ MENU_KEY_DUPLICATE,
+ MENU_KEY_DELETE
+ };
+ AnimationTimelineEdit *timeline;
+ UndoRedo *undo_redo;
+ LineEdit *path;
+ Node *root;
+ Control *play_position; //separate control used to draw so updates for only position changed are much faster
+ float play_position_pos;
+
+ Ref<Animation> animation;
+ int track;
+
+ Rect2 check_rect;
+ Rect2 path_rect;
+
+ Rect2 update_mode_rect;
+ Rect2 interp_mode_rect;
+ Rect2 loop_mode_rect;
+ Rect2 remove_rect;
+ Rect2 bezier_edit_rect;
+
+ Ref<Texture> type_icon;
+ Ref<Texture> selected_icon;
+
+ PopupMenu *menu;
+
+ bool clicking_on_name;
+
+ void _zoom_changed();
+
+ Ref<Texture> icon_cache;
+ String path_cache;
+
+ void _menu_selected(int p_index);
+
+ bool *block_animation_update_ptr; //used to block all tracks re-gen (speed up)
+
+ void _path_entered(const String &p_text);
+ void _play_position_draw();
+ mutable int dropping_at;
+
+ float insert_at_pos;
+ bool moving_selection_attempt;
+ int select_single_attempt;
+ bool moving_selection;
+ float moving_selection_from_ofs;
+
+ bool in_group;
+ AnimationTrackEditor *editor;
+
+protected:
+ static void _bind_methods();
+ void _notification(int p_what);
+
+ virtual void _gui_input(const Ref<InputEvent> &p_event);
+
+public:
+ virtual Variant get_drag_data(const Point2 &p_point);
+ virtual bool can_drop_data(const Point2 &p_point, const Variant &p_data) const;
+ virtual void drop_data(const Point2 &p_point, const Variant &p_data);
+
+ virtual String get_tooltip(const Point2 &p_pos) const;
+
+ virtual int get_key_height() const;
+ virtual Rect2 get_key_rect(int p_index, float p_pixels_sec);
+ virtual bool is_key_selectable_by_distance() const;
+ virtual void draw_key_link(int p_index, float p_pixels_sec, int p_x, int p_next_x, int p_clip_left, int p_clip_right);
+ virtual void draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right);
+ virtual void draw_bg(int p_clip_left, int p_clip_right);
+ virtual void draw_fg(int p_clip_left, int p_clip_right);
+
+ //helper
+ void draw_texture_clipped(const Ref<Texture> &p_texture, const Vector2 &p_pos);
+ void draw_texture_region_clipped(const Ref<Texture> &p_texture, const Rect2 &p_rect, const Rect2 &p_region);
+ void draw_rect_clipped(const Rect2 &p_rect, const Color &p_color, bool p_filled = true);
+
+ int get_track() const;
+ Ref<Animation> get_animation() const;
+ AnimationTimelineEdit *get_timeline() const { return timeline; }
+ AnimationTrackEditor *get_editor() const { return editor; }
+ UndoRedo *get_undo_redo() const { return undo_redo; }
+ bool *get_block_animation_update_ptr() { return block_animation_update_ptr; }
+
+ void set_animation_and_track(const Ref<Animation> &p_animation, int p_track);
+ virtual Size2 get_minimum_size() const;
+
+ void set_undo_redo(UndoRedo *p_undo_redo);
+ void set_timeline(AnimationTimelineEdit *p_timeline);
+ void set_editor(AnimationTrackEditor *p_editor);
+ void set_root(Node *p_root);
+
+ void set_block_animation_update_ptr(bool *p_block_ptr);
+
+ void set_play_position(float p_pos);
+ void update_play_position();
+ void cancel_drop();
+
+ void set_in_group(bool p_enable);
+ void append_to_selection(const Rect2 &p_box);
+
+ AnimationTrackEdit();
+};
+
+class AnimationTrackEditPlugin : public Reference {
+ GDCLASS(AnimationTrackEditPlugin, Reference)
+public:
+ virtual AnimationTrackEdit *create_value_track_edit(Object *p_object, Variant::Type p_type, const String &p_property, PropertyHint p_hint, const String &p_hint_string, int p_usage);
+ virtual AnimationTrackEdit *create_audio_track_edit();
+ virtual AnimationTrackEdit *create_animation_track_edit(Object *p_object);
+};
+
+class AnimationTrackKeyEdit;
+class AnimationBezierTrackEdit;
+
+class AnimationTrackEditGroup : public Control {
+ GDCLASS(AnimationTrackEditGroup, Control)
+ Ref<Texture> icon;
+ String node_name;
+ NodePath node;
+ Node *root;
+ AnimationTimelineEdit *timeline;
+
+ void _zoom_changed();
+
+protected:
+ static void _bind_methods();
+ void _notification(int p_what);
+
+public:
+ void set_type_and_name(const Ref<Texture> &p_type, const String &p_name, const NodePath &p_node);
+ virtual Size2 get_minimum_size() const;
+ void set_timeline(AnimationTimelineEdit *p_timeline);
+ void set_root(Node *p_root);
+
+ AnimationTrackEditGroup();
+};
+
+class AnimationTrackEditor : public VBoxContainer {
+ GDCLASS(AnimationTrackEditor, VBoxContainer)
+
+ enum {
+ EDIT_COPY_TRACKS,
+ EDIT_COPY_TRACKS_CONFIRM,
+ EDIT_PASTE_TRACKS,
+ EDIT_SCALE_SELECTION,
+ EDIT_SCALE_FROM_CURSOR,
+ EDIT_SCALE_CONFIRM,
+ EDIT_DUPLICATE_SELECTION,
+ EDIT_DUPLICATE_TRANSPOSED,
+ EDIT_DELETE_SELECTION,
+ EDIT_GOTO_NEXT_STEP,
+ EDIT_GOTO_PREV_STEP,
+ EDIT_OPTIMIZE_ANIMATION,
+ EDIT_OPTIMIZE_ANIMATION_CONFIRM,
+ EDIT_CLEAN_UP_ANIMATION,
+ EDIT_CLEAN_UP_ANIMATION_CONFIRM
+ };
+
+ Ref<Animation> animation;
+ Node *root;
+
+ MenuButton *edit;
+
+ HScrollBar *hscroll;
+ ScrollContainer *scroll;
+ VBoxContainer *track_vbox;
+ AnimationBezierTrackEdit *bezier_edit;
+
+ AnimationTimelineEdit *timeline;
+ HSlider *zoom;
+ EditorSpinSlider *step;
+ TextureRect *zoom_icon;
+ ToolButton *snap;
+
+ Vector<AnimationTrackEdit *> track_edits;
+ Vector<AnimationTrackEditGroup *> groups;
+
+ bool block_animation_update;
+
+ int _get_track_selected();
+ void _animation_changed();
+ void _update_tracks();
+
+ void _name_limit_changed();
+ void _timeline_changed(float p_new_pos, bool p_drag);
+ void _track_remove_request(int p_track);
+
+ UndoRedo *undo_redo;
+
+ void _update_scroll(double);
+ void _update_step(double p_new_step);
+ void _update_length(double p_new_step);
+ void _dropped_track(int p_from_track, int p_to_track);
+
+ void _add_track(int p_type);
+ void _new_track_node_selected(NodePath p_path);
+ void _new_track_property_selected(String p_name);
+
+ PropertySelector *prop_selector;
+ PropertySelector *method_selector;
+ SceneTreeDialog *pick_track;
+ int adding_track_type;
+ NodePath adding_track_path;
+
+ bool keying;
+
+ struct InsertData {
+
+ Animation::TrackType type;
+ NodePath path;
+ int track_idx;
+ Variant value;
+ String query;
+ bool advance;
+ }; /* insert_data;*/
+
+ Label *insert_confirm_text;
+ CheckBox *insert_confirm_bezier;
+ ConfirmationDialog *insert_confirm;
+ bool insert_queue;
+ bool inserting;
+ bool insert_query;
+ List<InsertData> insert_data;
+ uint64_t insert_frame;
+
+ void _query_insert(const InsertData &p_id);
+ void _confirm_insert_list();
+ int _confirm_insert(InsertData p_id, int p_last_track, bool p_create_beziers = false);
+ void _insert_delay();
+
+ void _root_removed(Node *p_root);
+
+ PropertyInfo _find_hint_for_track(int p_idx, NodePath &r_base_path, Variant *r_current_val = NULL);
+
+ void _timeline_value_changed(double);
+
+ float insert_key_from_track_call_ofs;
+ int insert_key_from_track_call_track;
+ void _insert_key_from_track(float p_ofs, int p_track);
+ void _add_method_key(const String &p_method);
+
+ void _clear_selection();
+ void _clear_selection_for_anim(const Ref<Animation> &p_anim);
+ void _select_at_anim(const Ref<Animation> &p_anim, int p_track, float p_pos);
+
+ //selection
+
+ struct SelectedKey {
+
+ int track;
+ int key;
+ bool operator<(const SelectedKey &p_key) const { return track == p_key.track ? key < p_key.key : track < p_key.track; };
+ };
+
+ struct KeyInfo {
+
+ float pos;
+ };
+
+ Map<SelectedKey, KeyInfo> selection;
+
+ void _key_selected(int p_key, bool p_single, int p_track);
+ void _key_deselected(int p_key, int p_track);
+
+ bool moving_selection;
+ float moving_selection_offset;
+ void _move_selection_begin();
+ void _move_selection(float p_offset);
+ void _move_selection_commit();
+ void _move_selection_cancel();
+
+ AnimationTrackKeyEdit *key_edit;
+ void _update_key_edit();
+
+ void _clear_key_edit();
+
+ Control *box_selection;
+ void _box_selection_draw();
+ bool box_selecting;
+ Vector2 box_selecting_from;
+ Rect2 box_select_rect;
+ void _scroll_input(const Ref<InputEvent> &p_event);
+
+ Vector<Ref<AnimationTrackEditPlugin> > track_edit_plugins;
+
+ void _cancel_bezier_edit();
+ void _bezier_edit(int p_for_track);
+
+ ////////////// edit menu stuff
+
+ ConfirmationDialog *optimize_dialog;
+ SpinBox *optimize_linear_error;
+ SpinBox *optimize_angular_error;
+ SpinBox *optimize_max_angle;
+
+ ConfirmationDialog *cleanup_dialog;
+ CheckButton *cleanup_keys;
+ CheckButton *cleanup_tracks;
+ CheckButton *cleanup_all;
+
+ ConfirmationDialog *scale_dialog;
+ SpinBox *scale;
+
+ void _edit_menu_pressed(int p_option);
+ int last_menu_track_opt;
+
+ void _cleanup_animation(Ref<Animation> p_animation);
+
+ void _anim_duplicate_keys(bool transpose);
+
+ void _view_group_toggle();
+ ToolButton *view_group;
+ ToolButton *selected_filter;
+
+ void _selection_changed();
+
+ ConfirmationDialog *track_copy_dialog;
+ Tree *track_copy_select;
+ struct TrackClipboard {
+ NodePath full_path;
+ NodePath base_path;
+ Animation::TrackType track_type;
+ Animation::InterpolationType interp_type;
+ Animation::UpdateMode update_mode;
+ bool loop_wrap;
+ bool enabled;
+
+ struct Key {
+ float time;
+ float transition;
+ Variant value;
+ };
+ Vector<Key> keys;
+ };
+
+ Vector<TrackClipboard> track_clipboard;
+
+ void _insert_animation_key(NodePath p_path, const Variant &p_value);
+
+protected:
+ static void _bind_methods();
+ void _notification(int p_what);
+
+public:
+ void add_track_edit_plugin(const Ref<AnimationTrackEditPlugin> &p_plugin);
+ void remove_track_edit_plugin(const Ref<AnimationTrackEditPlugin> &p_plugin);
+
+ void set_animation(const Ref<Animation> &p_anim);
+ Ref<Animation> get_current_animation() const;
+ void set_root(Node *p_root);
+ Node *get_root() const;
+ void update_keying();
+ bool has_keying() const;
+
+ void cleanup();
+
+ void set_anim_pos(float p_pos);
+ void insert_node_value_key(Node *p_node, const String &p_property, const Variant &p_value, bool p_only_if_exists = false);
+ void insert_value_key(const String &p_property, const Variant &p_value, bool p_advance);
+ void insert_transform_key(Spatial *p_node, const String &p_sub, const Transform &p_xform);
+
+ void show_select_node_warning(bool p_show);
+
+ bool is_key_selected(int p_track, int p_key) const;
+ bool is_selection_active() const;
+ bool is_moving_selection() const;
+ float get_moving_selection_offset() const;
+ bool is_snap_enabled();
+ float snap_time(float p_value);
+
+ MenuButton *get_edit_menu();
+ AnimationTrackEditor();
+ ~AnimationTrackEditor();
+};
+
+#endif // ANIMATION_TRACK_EDITOR_H
diff --git a/editor/animation_track_editor_plugins.cpp b/editor/animation_track_editor_plugins.cpp
new file mode 100644
index 0000000000..660c69f4a4
--- /dev/null
+++ b/editor/animation_track_editor_plugins.cpp
@@ -0,0 +1,1289 @@
+#include "animation_track_editor_plugins.h"
+#include "editor/audio_stream_preview.h"
+#include "editor_resource_preview.h"
+#include "editor_scale.h"
+#include "scene/2d/animated_sprite.h"
+#include "scene/2d/sprite.h"
+#include "scene/3d/sprite_3d.h"
+#include "scene/animation/animation_player.h"
+#include "servers/audio/audio_stream.h"
+/// BOOL ///
+int AnimationTrackEditBool::get_key_height() const {
+
+ Ref<Texture> checked = get_icon("checked", "CheckBox");
+ return checked->get_height();
+}
+Rect2 AnimationTrackEditBool::get_key_rect(int p_index, float p_pixels_sec) {
+
+ Ref<Texture> checked = get_icon("checked", "CheckBox");
+ return Rect2(0, 0, checked->get_width(), get_size().height);
+}
+
+bool AnimationTrackEditBool::is_key_selectable_by_distance() const {
+
+ return false;
+}
+void AnimationTrackEditBool::draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) {
+
+ Ref<Texture> icon;
+ bool checked = get_animation()->track_get_key_value(get_track(), p_index);
+
+ if (checked)
+ icon = get_icon("checked", "CheckBox");
+ else
+ icon = get_icon("unchecked", "CheckBox");
+
+ Vector2 ofs(p_x, int(get_size().height - icon->get_height()) / 2);
+
+ draw_texture_clipped(icon, ofs);
+
+ if (p_selected) {
+ Color color = get_color("accent_color", "Editor");
+ draw_rect_clipped(Rect2(ofs, icon->get_size()), color, false);
+ }
+}
+
+/// COLOR ///
+
+int AnimationTrackEditColor::get_key_height() const {
+
+ Ref<Font> font = get_font("font", "Label");
+ return font->get_height() * 0.8;
+}
+Rect2 AnimationTrackEditColor::get_key_rect(int p_index, float p_pixels_sec) {
+
+ Ref<Font> font = get_font("font", "Label");
+ int fh = font->get_height() * 0.8;
+ return Rect2(0, 0, fh, get_size().height);
+}
+
+bool AnimationTrackEditColor::is_key_selectable_by_distance() const {
+
+ return false;
+}
+
+void AnimationTrackEditColor::draw_key_link(int p_index, float p_pixels_sec, int p_x, int p_next_x, int p_clip_left, int p_clip_right) {
+
+ int x_from = p_x;
+ int x_to = p_next_x;
+
+ Ref<Font> font = get_font("font", "Label");
+ int fh = (font->get_height() * 0.8);
+
+ x_from += fh - 1;
+ x_to += 1;
+ fh /= 3;
+
+ if (x_from > p_clip_right)
+ return;
+
+ if (x_to < p_clip_left)
+ return;
+
+ Color color = get_animation()->track_get_key_value(get_track(), p_index);
+ Color color_next = get_animation()->track_get_key_value(get_track(), p_index + 1);
+
+ if (x_from < p_clip_left) {
+ float c = float(p_clip_left - x_from) / (x_to - x_from);
+ color = color.linear_interpolate(color_next, c);
+ x_from = p_clip_left;
+ }
+
+ if (x_to > p_clip_right) {
+ float c = float(p_clip_right - x_from) / (x_to - x_from);
+ color_next = color.linear_interpolate(color_next, c);
+ x_to = p_clip_right;
+ }
+
+ int y_from = (get_size().height - fh) / 2;
+
+ Vector<Vector2> points;
+ Vector<Color> colors;
+
+ points.push_back(Vector2(x_from, y_from));
+ colors.push_back(color);
+
+ points.push_back(Vector2(x_to, y_from));
+ colors.push_back(color_next);
+
+ points.push_back(Vector2(x_to, y_from + fh));
+ colors.push_back(color_next);
+
+ points.push_back(Vector2(x_from, y_from + fh));
+ colors.push_back(color);
+
+ draw_primitive(points, colors, Vector<Vector2>());
+}
+
+void AnimationTrackEditColor::draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) {
+
+ Color color = get_animation()->track_get_key_value(get_track(), p_index);
+
+ Ref<Font> font = get_font("font", "Label");
+ int fh = font->get_height() * 0.8;
+
+ Rect2 rect(Vector2(p_x, int(get_size().height - fh) / 2), Size2(fh, fh));
+
+ draw_rect_clipped(Rect2(rect.position, rect.size / 2), Color(0.4, 0.4, 0.4));
+ draw_rect_clipped(Rect2(rect.position + rect.size / 2, rect.size / 2), Color(0.4, 0.4, 0.4));
+ draw_rect_clipped(Rect2(rect.position + Vector2(rect.size.x / 2, 0), rect.size / 2), Color(0.6, 0.6, 0.6));
+ draw_rect_clipped(Rect2(rect.position + Vector2(0, rect.size.y / 2), rect.size / 2), Color(0.6, 0.6, 0.6));
+ draw_rect_clipped(rect, color);
+
+ if (p_selected) {
+ Color accent = get_color("accent_color", "Editor");
+ draw_rect_clipped(rect, accent, false);
+ }
+}
+
+/// AUDIO ///
+
+void AnimationTrackEditAudio::_preview_changed(ObjectID p_which) {
+
+ Object *object = ObjectDB::get_instance(id);
+
+ if (!object)
+ return;
+
+ Ref<AudioStream> stream = object->call("get_stream");
+
+ if (stream.is_valid() && stream->get_instance_id() == p_which) {
+ update();
+ }
+}
+
+int AnimationTrackEditAudio::get_key_height() const {
+
+ if (!ObjectDB::get_instance(id)) {
+ return AnimationTrackEdit::get_key_height();
+ }
+
+ Ref<Font> font = get_font("font", "Label");
+ return int(font->get_height() * 1.5);
+}
+Rect2 AnimationTrackEditAudio::get_key_rect(int p_index, float p_pixels_sec) {
+
+ Object *object = ObjectDB::get_instance(id);
+
+ if (!object) {
+ return AnimationTrackEdit::get_key_rect(p_index, p_pixels_sec);
+ }
+
+ Ref<AudioStream> stream = object->call("get_stream");
+
+ if (!stream.is_valid()) {
+ return AnimationTrackEdit::get_key_rect(p_index, p_pixels_sec);
+ }
+
+ bool play = get_animation()->track_get_key_value(get_track(), p_index);
+ if (play) {
+ float len = stream->get_length();
+
+ if (len == 0) {
+
+ Ref<AudioStreamPreview> preview = AudioStreamPreviewGenerator::get_singleton()->generate_preview(stream);
+ len = preview->get_length();
+ }
+
+ if (get_animation()->track_get_key_count(get_track()) > p_index + 1) {
+ len = MIN(len, get_animation()->track_get_key_time(get_track(), p_index + 1) - get_animation()->track_get_key_time(get_track(), p_index));
+ }
+
+ return Rect2(0, 0, len * p_pixels_sec, get_size().height);
+ } else {
+ Ref<Font> font = get_font("font", "Label");
+ int fh = font->get_height() * 0.8;
+ return Rect2(0, 0, fh, get_size().height);
+ }
+}
+
+bool AnimationTrackEditAudio::is_key_selectable_by_distance() const {
+
+ return false;
+}
+void AnimationTrackEditAudio::draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) {
+
+ Object *object = ObjectDB::get_instance(id);
+
+ if (!object) {
+ AnimationTrackEdit::draw_key(p_index, p_pixels_sec, p_x, p_selected, p_clip_left, p_clip_right);
+ return;
+ }
+
+ Ref<AudioStream> stream = object->call("get_stream");
+
+ if (!stream.is_valid()) {
+ AnimationTrackEdit::draw_key(p_index, p_pixels_sec, p_x, p_selected, p_clip_left, p_clip_right);
+ return;
+ }
+
+ Ref<Font> font = get_font("font", "Label");
+ float fh = int(font->get_height() * 1.5);
+
+ bool play = get_animation()->track_get_key_value(get_track(), p_index);
+ if (play) {
+ float len = stream->get_length();
+
+ Ref<AudioStreamPreview> preview = AudioStreamPreviewGenerator::get_singleton()->generate_preview(stream);
+
+ float preview_len = preview->get_length();
+
+ if (len == 0) {
+ len = preview_len;
+ }
+
+ int pixel_len = len * p_pixels_sec;
+
+ int pixel_begin = p_x;
+ int pixel_end = p_x + pixel_len;
+
+ if (pixel_end < p_clip_left)
+ return;
+
+ if (pixel_begin > p_clip_right)
+ return;
+
+ int from_x = MAX(pixel_begin, p_clip_left);
+ int to_x = MIN(pixel_end, p_clip_right);
+
+ if (get_animation()->track_get_key_count(get_track()) > p_index + 1) {
+ float limit = MIN(len, get_animation()->track_get_key_time(get_track(), p_index + 1) - get_animation()->track_get_key_time(get_track(), p_index));
+ int limit_x = pixel_begin + limit * p_pixels_sec;
+ to_x = MIN(limit_x, to_x);
+ }
+
+ if (to_x <= from_x)
+ return;
+
+ int h = get_size().height;
+ Rect2 rect = Rect2(from_x, (h - fh) / 2, to_x - from_x, fh);
+ draw_rect(rect, Color(0.25, 0.25, 0.25));
+
+ Vector<Vector2> lines;
+ lines.resize((to_x - from_x + 1) * 2);
+ preview_len = preview->get_length();
+
+ for (int i = from_x; i < to_x; i++) {
+
+ float ofs = (i - pixel_begin) * preview_len / pixel_len;
+ float ofs_n = ((i + 1) - pixel_begin) * preview_len / pixel_len;
+ float max = preview->get_max(ofs, ofs_n) * 0.5 + 0.5;
+ float min = preview->get_min(ofs, ofs_n) * 0.5 + 0.5;
+
+ int idx = i - from_x;
+ lines[idx * 2 + 0] = Vector2(i, rect.position.y + min * rect.size.y);
+ lines[idx * 2 + 1] = Vector2(i, rect.position.y + max * rect.size.y);
+ }
+
+ Vector<Color> color;
+ color.push_back(Color(0.75, 0.75, 0.75));
+
+ VS::get_singleton()->canvas_item_add_multiline(get_canvas_item(), lines, color);
+
+ if (p_selected) {
+ Color accent = get_color("accent_color", "Editor");
+ draw_rect(rect, accent, false);
+ }
+ } else {
+ Ref<Font> font = get_font("font", "Label");
+ int fh = font->get_height() * 0.8;
+ Rect2 rect(Vector2(p_x, int(get_size().height - fh) / 2), Size2(fh, fh));
+
+ Color color = get_color("font_color", "Label");
+ draw_rect(rect, color);
+
+ if (p_selected) {
+ Color accent = get_color("accent_color", "Editor");
+ draw_rect(rect, accent, false);
+ }
+ }
+}
+
+void AnimationTrackEditAudio::set_node(Object *p_object) {
+
+ id = p_object->get_instance_id();
+}
+
+void AnimationTrackEditAudio::_bind_methods() {
+ ClassDB::bind_method("_preview_changed", &AnimationTrackEditAudio::_preview_changed);
+}
+
+AnimationTrackEditAudio::AnimationTrackEditAudio() {
+ AudioStreamPreviewGenerator::get_singleton()->connect("preview_updated", this, "_preview_changed");
+}
+
+/// SPRITE FRAME ///
+
+int AnimationTrackEditSpriteFrame::get_key_height() const {
+
+ if (!ObjectDB::get_instance(id)) {
+ return AnimationTrackEdit::get_key_height();
+ }
+
+ Ref<Font> font = get_font("font", "Label");
+ return int(font->get_height() * 2);
+}
+Rect2 AnimationTrackEditSpriteFrame::get_key_rect(int p_index, float p_pixels_sec) {
+
+ Object *object = ObjectDB::get_instance(id);
+
+ if (!object) {
+ return AnimationTrackEdit::get_key_rect(p_index, p_pixels_sec);
+ }
+
+ Size2 size;
+
+ if (Object::cast_to<Sprite>(object) || Object::cast_to<Sprite3D>(object)) {
+
+ Ref<Texture> texture = object->call("get_texture");
+ if (!texture.is_valid()) {
+ return AnimationTrackEdit::get_key_rect(p_index, p_pixels_sec);
+ }
+
+ size = texture->get_size();
+
+ if (bool(object->call("is_region"))) {
+ size = Rect2(object->call("get_region_rect")).size;
+ }
+
+ int hframes = object->call("get_hframes");
+ int vframes = object->call("get_vframes");
+
+ if (hframes > 1) {
+ size.x /= hframes;
+ }
+ if (vframes > 1) {
+ size.y /= vframes;
+ }
+ } else if (Object::cast_to<AnimatedSprite>(object) || Object::cast_to<AnimatedSprite3D>(object)) {
+
+ int frame = get_animation()->track_get_key_value(get_track(), p_index);
+ String animation = "default"; //may be smart and go through other tracks to find if animation is set
+
+ Ref<SpriteFrames> sf = object->call("get_sprite_frames");
+ if (sf.is_null()) {
+ return AnimationTrackEdit::get_key_rect(p_index, p_pixels_sec);
+ }
+
+ Ref<Texture> texture = sf->get_frame(animation, frame);
+ if (!texture.is_valid()) {
+ return AnimationTrackEdit::get_key_rect(p_index, p_pixels_sec);
+ }
+
+ size = texture->get_size();
+ }
+
+ size = size.floor();
+
+ Ref<Font> font = get_font("font", "Label");
+ int height = int(font->get_height() * 2);
+ int width = height * size.width / size.height;
+
+ return Rect2(0, 0, width, get_size().height);
+}
+
+bool AnimationTrackEditSpriteFrame::is_key_selectable_by_distance() const {
+
+ return false;
+}
+void AnimationTrackEditSpriteFrame::draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) {
+
+ Object *object = ObjectDB::get_instance(id);
+
+ if (!object) {
+ AnimationTrackEdit::draw_key(p_index, p_pixels_sec, p_x, p_selected, p_clip_left, p_clip_right);
+ return;
+ }
+
+ int frame = get_animation()->track_get_key_value(get_track(), p_index);
+
+ Ref<Texture> texture;
+ Rect2 region;
+
+ if (Object::cast_to<Sprite>(object) || Object::cast_to<Sprite3D>(object)) {
+
+ texture = object->call("get_texture");
+ if (!texture.is_valid()) {
+ AnimationTrackEdit::draw_key(p_index, p_pixels_sec, p_x, p_selected, p_clip_left, p_clip_right);
+ return;
+ }
+
+ region.size = texture->get_size();
+
+ if (bool(object->call("is_region"))) {
+
+ region = Rect2(object->call("get_region_rect"));
+ }
+
+ int hframes = object->call("get_hframes");
+ int vframes = object->call("get_vframes");
+
+ if (hframes > 1) {
+ region.size.x /= hframes;
+ }
+ if (vframes > 1) {
+ region.size.y /= vframes;
+ }
+
+ region.position.x += region.size.x * (frame % hframes);
+ region.position.y += region.size.y * (frame / hframes);
+
+ } else if (Object::cast_to<AnimatedSprite>(object) || Object::cast_to<AnimatedSprite3D>(object)) {
+
+ int frame = get_animation()->track_get_key_value(get_track(), p_index);
+ String animation = "default"; //may be smart and go through other tracks to find if animation is set
+
+ Ref<SpriteFrames> sf = object->call("get_sprite_frames");
+ if (sf.is_null()) {
+ AnimationTrackEdit::draw_key(p_index, p_pixels_sec, p_x, p_selected, p_clip_left, p_clip_right);
+ return;
+ }
+
+ texture = sf->get_frame(animation, frame);
+ if (!texture.is_valid()) {
+ AnimationTrackEdit::draw_key(p_index, p_pixels_sec, p_x, p_selected, p_clip_left, p_clip_right);
+ return;
+ }
+
+ region.size = texture->get_size();
+ }
+
+ Ref<Font> font = get_font("font", "Label");
+ int height = int(font->get_height() * 2);
+
+ int width = height * region.size.width / region.size.height;
+
+ Rect2 rect(p_x, int(get_size().height - height) / 2, width, height);
+
+ if (rect.position.x + rect.size.x < p_clip_left)
+ return;
+
+ if (rect.position.x > p_clip_right)
+ return;
+
+ Color accent = get_color("accent_color", "Editor");
+ Color bg = accent;
+ bg.a = 0.15;
+
+ draw_rect_clipped(rect, bg);
+
+ draw_texture_region_clipped(texture, rect, region);
+
+ if (p_selected) {
+ draw_rect_clipped(rect, accent, false);
+ }
+}
+
+void AnimationTrackEditSpriteFrame::set_node(Object *p_object) {
+
+ id = p_object->get_instance_id();
+}
+
+/// SUB ANIMATION ///
+
+int AnimationTrackEditSubAnim::get_key_height() const {
+
+ if (!ObjectDB::get_instance(id)) {
+ return AnimationTrackEdit::get_key_height();
+ }
+
+ Ref<Font> font = get_font("font", "Label");
+ return int(font->get_height() * 1.5);
+}
+Rect2 AnimationTrackEditSubAnim::get_key_rect(int p_index, float p_pixels_sec) {
+
+ Object *object = ObjectDB::get_instance(id);
+
+ if (!object) {
+ return AnimationTrackEdit::get_key_rect(p_index, p_pixels_sec);
+ }
+
+ AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(object);
+
+ if (!ap) {
+ return AnimationTrackEdit::get_key_rect(p_index, p_pixels_sec);
+ }
+
+ String anim = get_animation()->track_get_key_value(get_track(), p_index);
+
+ if (anim != "[stop]" && ap->has_animation(anim)) {
+
+ float len = ap->get_animation(anim)->get_length();
+
+ if (get_animation()->track_get_key_count(get_track()) > p_index + 1) {
+ len = MIN(len, get_animation()->track_get_key_time(get_track(), p_index + 1) - get_animation()->track_get_key_time(get_track(), p_index));
+ }
+
+ return Rect2(0, 0, len * p_pixels_sec, get_size().height);
+ } else {
+ Ref<Font> font = get_font("font", "Label");
+ int fh = font->get_height() * 0.8;
+ return Rect2(0, 0, fh, get_size().height);
+ }
+}
+
+bool AnimationTrackEditSubAnim::is_key_selectable_by_distance() const {
+
+ return false;
+}
+void AnimationTrackEditSubAnim::draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) {
+
+ Object *object = ObjectDB::get_instance(id);
+
+ if (!object) {
+ AnimationTrackEdit::draw_key(p_index, p_pixels_sec, p_x, p_selected, p_clip_left, p_clip_right);
+ return;
+ }
+
+ AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(object);
+
+ if (!ap) {
+ AnimationTrackEdit::draw_key(p_index, p_pixels_sec, p_x, p_selected, p_clip_left, p_clip_right);
+ return;
+ }
+
+ String anim = get_animation()->track_get_key_value(get_track(), p_index);
+
+ if (anim != "[stop]" && ap->has_animation(anim)) {
+
+ float len = ap->get_animation(anim)->get_length();
+
+ if (get_animation()->track_get_key_count(get_track()) > p_index + 1) {
+ len = MIN(len, get_animation()->track_get_key_time(get_track(), p_index + 1) - get_animation()->track_get_key_time(get_track(), p_index));
+ }
+
+ int pixel_len = len * p_pixels_sec;
+
+ int pixel_begin = p_x;
+ int pixel_end = p_x + pixel_len;
+
+ if (pixel_end < p_clip_left)
+ return;
+
+ if (pixel_begin > p_clip_right)
+ return;
+
+ int from_x = MAX(pixel_begin, p_clip_left);
+ int to_x = MIN(pixel_end, p_clip_right);
+
+ if (to_x <= from_x)
+ return;
+
+ Ref<Font> font = get_font("font", "Label");
+ int fh = font->get_height() * 1.5;
+
+ Rect2 rect(from_x, int(get_size().height - fh) / 2, to_x - from_x, fh);
+
+ Color color = get_color("font_color", "Label");
+ Color bg = color;
+ bg.r = 1 - color.r;
+ bg.g = 1 - color.g;
+ bg.b = 1 - color.b;
+ draw_rect(rect, bg);
+
+ Vector<Vector2> lines;
+ Vector<Color> colorv;
+ {
+ Ref<Animation> animation = ap->get_animation(anim);
+
+ for (int i = 0; i < animation->get_track_count(); i++) {
+
+ float h = (rect.size.height - 2) / animation->get_track_count();
+
+ int y = 2 + h * i + h / 2;
+
+ for (int j = 0; j < animation->track_get_key_count(i); j++) {
+
+ float ofs = animation->track_get_key_time(i, j);
+ int x = p_x + ofs * p_pixels_sec + 2;
+
+ if (x < from_x || x >= (to_x - 4))
+ continue;
+
+ lines.push_back(Point2(x, y));
+ lines.push_back(Point2(x + 1, y));
+ }
+ }
+
+ colorv.push_back(color);
+ }
+
+ if (lines.size() > 2) {
+ VS::get_singleton()->canvas_item_add_multiline(get_canvas_item(), lines, colorv);
+ }
+
+ int limit = to_x - from_x - 4;
+ if (limit > 0) {
+ draw_string(font, Point2(from_x + 2, int(get_size().height - font->get_height()) / 2 + font->get_ascent()), anim, color);
+ }
+
+ if (p_selected) {
+ Color accent = get_color("accent_color", "Editor");
+ draw_rect(rect, accent, false);
+ }
+ } else {
+ Ref<Font> font = get_font("font", "Label");
+ int fh = font->get_height() * 0.8;
+ Rect2 rect(Vector2(p_x, int(get_size().height - fh) / 2), Size2(fh, fh));
+
+ Color color = get_color("font_color", "Label");
+ draw_rect(rect, color);
+
+ if (p_selected) {
+ Color accent = get_color("accent_color", "Editor");
+ draw_rect(rect, accent, false);
+ }
+ }
+}
+
+void AnimationTrackEditSubAnim::set_node(Object *p_object) {
+
+ id = p_object->get_instance_id();
+}
+
+//// VOLUME DB ////
+
+int AnimationTrackEditVolumeDB::get_key_height() const {
+
+ Ref<Texture> volume_texture = get_icon("ColorTrackVu", "EditorIcons");
+ return volume_texture->get_height() * 1.2;
+}
+
+void AnimationTrackEditVolumeDB::draw_bg(int p_clip_left, int p_clip_right) {
+
+ Ref<Texture> volume_texture = get_icon("ColorTrackVu", "EditorIcons");
+ int tex_h = volume_texture->get_height();
+
+ int y_from = (get_size().height - tex_h) / 2;
+ int y_size = tex_h;
+
+ Color color(1, 1, 1, 0.3);
+ draw_texture_rect(volume_texture, Rect2(p_clip_left, y_from, p_clip_right - p_clip_left, y_from + y_size), false, color);
+}
+
+void AnimationTrackEditVolumeDB::draw_fg(int p_clip_left, int p_clip_right) {
+
+ Ref<Texture> volume_texture = get_icon("ColorTrackVu", "EditorIcons");
+ int tex_h = volume_texture->get_height();
+ int y_from = (get_size().height - tex_h) / 2;
+ int db0 = y_from + (24 / 80.0) * tex_h;
+
+ draw_line(Vector2(p_clip_left, db0), Vector2(p_clip_right, db0), Color(1, 1, 1, 0.3));
+}
+
+void AnimationTrackEditVolumeDB::draw_key_link(int p_index, float p_pixels_sec, int p_x, int p_next_x, int p_clip_left, int p_clip_right) {
+
+ if (p_x > p_clip_right || p_next_x < p_clip_left)
+ return;
+
+ float db = get_animation()->track_get_key_value(get_track(), p_index);
+ float db_n = get_animation()->track_get_key_value(get_track(), p_index + 1);
+
+ db = CLAMP(db, -60, 24);
+ db_n = CLAMP(db_n, -60, 24);
+
+ float h = 1.0 - ((db + 60) / 84.0);
+ float h_n = 1.0 - ((db_n + 60) / 84.0);
+
+ int from_x = p_x;
+ int to_x = p_next_x;
+
+ if (from_x < p_clip_left) {
+ h = Math::lerp(h, h_n, float(p_clip_left - from_x) / float(to_x - from_x));
+ from_x = p_clip_left;
+ }
+
+ if (to_x > p_clip_right) {
+ h_n = Math::lerp(h, h_n, float(p_clip_right - from_x) / float(to_x - from_x));
+ to_x = p_clip_right;
+ }
+
+ Ref<Texture> volume_texture = get_icon("ColorTrackVu", "EditorIcons");
+ int tex_h = volume_texture->get_height();
+
+ int y_from = (get_size().height - tex_h) / 2;
+
+ Color color = get_color("font_color", "Label");
+ color.a *= 0.7;
+
+ draw_line(Point2(from_x, y_from + h * tex_h), Point2(to_x, y_from + h_n * tex_h), color, 2);
+}
+
+////////////////////////
+
+/// AUDIO ///
+
+void AnimationTrackEditTypeAudio::_preview_changed(ObjectID p_which) {
+
+ for (int i = 0; i < get_animation()->track_get_key_count(get_track()); i++) {
+ Ref<AudioStream> stream = get_animation()->audio_track_get_key_stream(get_track(), i);
+ if (stream.is_valid() && stream->get_instance_id() == p_which) {
+ update();
+ return;
+ }
+ }
+}
+
+int AnimationTrackEditTypeAudio::get_key_height() const {
+
+ Ref<Font> font = get_font("font", "Label");
+ return int(font->get_height() * 1.5);
+}
+Rect2 AnimationTrackEditTypeAudio::get_key_rect(int p_index, float p_pixels_sec) {
+
+ Ref<AudioStream> stream = get_animation()->audio_track_get_key_stream(get_track(), p_index);
+
+ if (!stream.is_valid()) {
+ return AnimationTrackEdit::get_key_rect(p_index, p_pixels_sec);
+ }
+
+ float start_ofs = get_animation()->audio_track_get_key_start_offset(get_track(), p_index);
+ float end_ofs = get_animation()->audio_track_get_key_end_offset(get_track(), p_index);
+
+ float len = stream->get_length();
+
+ if (len == 0) {
+
+ Ref<AudioStreamPreview> preview = AudioStreamPreviewGenerator::get_singleton()->generate_preview(stream);
+ len = preview->get_length();
+ }
+
+ len -= end_ofs;
+ len -= start_ofs;
+ if (len <= 0.001) {
+ len = 0.001;
+ }
+
+ if (get_animation()->track_get_key_count(get_track()) > p_index + 1) {
+ len = MIN(len, get_animation()->track_get_key_time(get_track(), p_index + 1) - get_animation()->track_get_key_time(get_track(), p_index));
+ }
+
+ return Rect2(0, 0, len * p_pixels_sec, get_size().height);
+}
+
+bool AnimationTrackEditTypeAudio::is_key_selectable_by_distance() const {
+
+ return false;
+}
+void AnimationTrackEditTypeAudio::draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) {
+
+ Ref<AudioStream> stream = get_animation()->audio_track_get_key_stream(get_track(), p_index);
+
+ if (!stream.is_valid()) {
+ AnimationTrackEdit::draw_key(p_index, p_pixels_sec, p_x, p_selected, p_clip_left, p_clip_right);
+ return;
+ }
+
+ float start_ofs = get_animation()->audio_track_get_key_start_offset(get_track(), p_index);
+ float end_ofs = get_animation()->audio_track_get_key_end_offset(get_track(), p_index);
+
+ if (len_resizing && p_index == len_resizing_index) {
+ float ofs_local = -len_resizing_rel / get_timeline()->get_zoom_scale();
+ if (len_resizing_start) {
+ start_ofs += ofs_local;
+ if (start_ofs < 0)
+ start_ofs = 0;
+ } else {
+ end_ofs += ofs_local;
+ if (end_ofs < 0)
+ end_ofs = 0;
+ }
+ }
+
+ Ref<Font> font = get_font("font", "Label");
+ float fh = int(font->get_height() * 1.5);
+
+ float len = stream->get_length();
+
+ Ref<AudioStreamPreview> preview = AudioStreamPreviewGenerator::get_singleton()->generate_preview(stream);
+
+ float preview_len = preview->get_length();
+
+ if (len == 0) {
+ len = preview_len;
+ }
+
+ int pixel_total_len = len * p_pixels_sec;
+
+ len -= end_ofs;
+ len -= start_ofs;
+
+ if (len <= 0.001) {
+ len = 0.001;
+ }
+
+ int pixel_len = len * p_pixels_sec;
+
+ int pixel_begin = p_x;
+ int pixel_end = p_x + pixel_len;
+
+ if (pixel_end < p_clip_left)
+ return;
+
+ if (pixel_begin > p_clip_right)
+ return;
+
+ int from_x = MAX(pixel_begin, p_clip_left);
+ int to_x = MIN(pixel_end, p_clip_right);
+
+ if (get_animation()->track_get_key_count(get_track()) > p_index + 1) {
+ float limit = MIN(len, get_animation()->track_get_key_time(get_track(), p_index + 1) - get_animation()->track_get_key_time(get_track(), p_index));
+ int limit_x = pixel_begin + limit * p_pixels_sec;
+ to_x = MIN(limit_x, to_x);
+ }
+
+ if (to_x <= from_x) {
+ to_x = from_x + 1;
+ }
+
+ int h = get_size().height;
+ Rect2 rect = Rect2(from_x, (h - fh) / 2, to_x - from_x, fh);
+ draw_rect(rect, Color(0.25, 0.25, 0.25));
+
+ Vector<Vector2> lines;
+ lines.resize((to_x - from_x + 1) * 2);
+ preview_len = preview->get_length();
+
+ for (int i = from_x; i < to_x; i++) {
+
+ float ofs = (i - pixel_begin) * preview_len / pixel_total_len;
+ float ofs_n = ((i + 1) - pixel_begin) * preview_len / pixel_total_len;
+ ofs += start_ofs;
+ ofs_n += start_ofs;
+
+ float max = preview->get_max(ofs, ofs_n) * 0.5 + 0.5;
+ float min = preview->get_min(ofs, ofs_n) * 0.5 + 0.5;
+
+ int idx = i - from_x;
+ lines[idx * 2 + 0] = Vector2(i, rect.position.y + min * rect.size.y);
+ lines[idx * 2 + 1] = Vector2(i, rect.position.y + max * rect.size.y);
+ }
+
+ Vector<Color> color;
+ color.push_back(Color(0.75, 0.75, 0.75));
+
+ VS::get_singleton()->canvas_item_add_multiline(get_canvas_item(), lines, color);
+
+ Color cut_color = get_color("accent_color", "Editor");
+ cut_color.a = 0.7;
+ if (start_ofs > 0 && pixel_begin > p_clip_left) {
+ draw_rect(Rect2(pixel_begin, rect.position.y, 1, rect.size.y), cut_color);
+ }
+ if (end_ofs > 0 && pixel_end < p_clip_right) {
+ draw_rect(Rect2(pixel_end, rect.position.y, 1, rect.size.y), cut_color);
+ }
+
+ if (p_selected) {
+ Color accent = get_color("accent_color", "Editor");
+ draw_rect(rect, accent, false);
+ }
+}
+
+void AnimationTrackEditTypeAudio::_bind_methods() {
+ ClassDB::bind_method("_preview_changed", &AnimationTrackEditTypeAudio::_preview_changed);
+}
+
+AnimationTrackEditTypeAudio::AnimationTrackEditTypeAudio() {
+ AudioStreamPreviewGenerator::get_singleton()->connect("preview_updated", this, "_preview_changed");
+ len_resizing = false;
+}
+
+bool AnimationTrackEditTypeAudio::can_drop_data(const Point2 &p_point, const Variant &p_data) const {
+
+ if (p_point.x > get_timeline()->get_name_limit() && p_point.x < get_size().width - get_timeline()->get_buttons_width()) {
+
+ Dictionary drag_data = p_data;
+ if (drag_data.has("type") && String(drag_data["type"]) == "resource") {
+ Ref<AudioStream> res = drag_data["resource"];
+ if (res.is_valid()) {
+ return true;
+ }
+ }
+
+ if (drag_data.has("type") && String(drag_data["type"]) == "files") {
+
+ Vector<String> files = drag_data["files"];
+
+ if (files.size() == 1) {
+ String file = files[0];
+ Ref<AudioStream> res = ResourceLoader::load(file);
+ if (res.is_valid()) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return AnimationTrackEdit::can_drop_data(p_point, p_data);
+}
+void AnimationTrackEditTypeAudio::drop_data(const Point2 &p_point, const Variant &p_data) {
+
+ if (p_point.x > get_timeline()->get_name_limit() && p_point.x < get_size().width - get_timeline()->get_buttons_width()) {
+
+ Ref<AudioStream> stream;
+ Dictionary drag_data = p_data;
+ if (drag_data.has("type") && String(drag_data["type"]) == "resource") {
+ stream = drag_data["resource"];
+ } else if (drag_data.has("type") && String(drag_data["type"]) == "files") {
+
+ Vector<String> files = drag_data["files"];
+
+ if (files.size() == 1) {
+ String file = files[0];
+ stream = ResourceLoader::load(file);
+ }
+ }
+
+ if (stream.is_valid()) {
+
+ int x = p_point.x - get_timeline()->get_name_limit();
+ float ofs = x / get_timeline()->get_zoom_scale();
+ ofs += get_timeline()->get_value();
+
+ ofs = get_editor()->snap_time(ofs);
+
+ while (get_animation()->track_find_key(get_track(), ofs, true) != -1) { //make sure insertion point is valid
+ ofs += 0.001;
+ }
+
+ print_line("inserting");
+
+ *get_block_animation_update_ptr() = true;
+ get_undo_redo()->create_action("Add Audio Track Clip");
+ get_undo_redo()->add_do_method(get_animation().ptr(), "audio_track_insert_key", get_track(), ofs, stream);
+ get_undo_redo()->add_undo_method(get_animation().ptr(), "track_remove_key_at_position", get_track(), ofs);
+ get_undo_redo()->commit_action();
+ *get_block_animation_update_ptr() = false;
+
+ update();
+ return;
+ }
+ }
+
+ return AnimationTrackEdit::drop_data(p_point, p_data);
+}
+
+void AnimationTrackEditTypeAudio::_gui_input(const Ref<InputEvent> &p_event) {
+
+ Ref<InputEventMouseMotion> mm = p_event;
+ if (!len_resizing && mm.is_valid()) {
+ bool use_hsize_cursor = false;
+ for (int i = 0; i < get_animation()->track_get_key_count(get_track()); i++) {
+
+ Ref<AudioStream> stream = get_animation()->audio_track_get_key_stream(get_track(), i);
+
+ if (!stream.is_valid()) {
+ continue;
+ }
+
+ float start_ofs = get_animation()->audio_track_get_key_start_offset(get_track(), i);
+ float end_ofs = get_animation()->audio_track_get_key_end_offset(get_track(), i);
+ float len = stream->get_length();
+
+ if (len == 0) {
+ Ref<AudioStreamPreview> preview = AudioStreamPreviewGenerator::get_singleton()->generate_preview(stream);
+ float preview_len = preview->get_length();
+ len = preview_len;
+ }
+
+ len -= end_ofs;
+ len -= start_ofs;
+ if (len <= 0.001) {
+ len = 0.001;
+ }
+
+ if (get_animation()->track_get_key_count(get_track()) > i + 1) {
+ len = MIN(len, get_animation()->track_get_key_time(get_track(), i + 1) - get_animation()->track_get_key_time(get_track(), i));
+ }
+
+ float ofs = get_animation()->track_get_key_time(get_track(), i);
+
+ ofs -= get_timeline()->get_value();
+ ofs *= get_timeline()->get_zoom_scale();
+ ofs += get_timeline()->get_name_limit();
+
+ int end = ofs + len * get_timeline()->get_zoom_scale();
+
+ if (end >= get_timeline()->get_name_limit() && end <= get_size().width - get_timeline()->get_buttons_width() && ABS(mm->get_position().x - end) < 5 * EDSCALE) {
+ use_hsize_cursor = true;
+ len_resizing_index = i;
+ }
+ }
+
+ if (use_hsize_cursor) {
+ set_default_cursor_shape(CURSOR_HSIZE);
+ } else {
+ set_default_cursor_shape(CURSOR_ARROW);
+ }
+ }
+
+ if (len_resizing && mm.is_valid()) {
+ len_resizing_rel += mm->get_relative().x;
+ len_resizing_start = mm->get_shift();
+ update();
+ accept_event();
+ return;
+ }
+
+ Ref<InputEventMouseButton> mb = p_event;
+ if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT && get_default_cursor_shape() == CURSOR_HSIZE) {
+
+ len_resizing = true;
+ len_resizing_start = mb->get_shift();
+ len_resizing_from_px = mb->get_position().x;
+ len_resizing_rel = 0;
+ update();
+ accept_event();
+ return;
+ }
+
+ if (len_resizing && mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) {
+
+ float ofs_local = -len_resizing_rel / get_timeline()->get_zoom_scale();
+ if (len_resizing_start) {
+ float prev_ofs = get_animation()->audio_track_get_key_start_offset(get_track(), len_resizing_index);
+ *get_block_animation_update_ptr() = true;
+ get_undo_redo()->create_action("Change Audio Track Clip Start Offset");
+ get_undo_redo()->add_do_method(get_animation().ptr(), "audio_track_set_key_start_offset", get_track(), len_resizing_index, prev_ofs + ofs_local);
+ get_undo_redo()->add_undo_method(get_animation().ptr(), "audio_track_set_key_start_offset", get_track(), len_resizing_index, prev_ofs);
+ get_undo_redo()->commit_action();
+ *get_block_animation_update_ptr() = false;
+
+ } else {
+ float prev_ofs = get_animation()->audio_track_get_key_end_offset(get_track(), len_resizing_index);
+ *get_block_animation_update_ptr() = true;
+ get_undo_redo()->create_action("Change Audio Track Clip End Offset");
+ get_undo_redo()->add_do_method(get_animation().ptr(), "audio_track_set_key_end_offset", get_track(), len_resizing_index, prev_ofs + ofs_local);
+ get_undo_redo()->add_undo_method(get_animation().ptr(), "audio_track_set_key_end_offset", get_track(), len_resizing_index, prev_ofs);
+ get_undo_redo()->commit_action();
+ *get_block_animation_update_ptr() = false;
+ }
+
+ len_resizing = false;
+ len_resizing_index = -1;
+ update();
+ accept_event();
+ return;
+ }
+
+ AnimationTrackEdit::_gui_input(p_event);
+}
+
+////////////////////
+/// SUB ANIMATION ///
+
+int AnimationTrackEditTypeAnimation::get_key_height() const {
+
+ if (!ObjectDB::get_instance(id)) {
+ return AnimationTrackEdit::get_key_height();
+ }
+
+ Ref<Font> font = get_font("font", "Label");
+ return int(font->get_height() * 1.5);
+}
+Rect2 AnimationTrackEditTypeAnimation::get_key_rect(int p_index, float p_pixels_sec) {
+
+ Object *object = ObjectDB::get_instance(id);
+
+ if (!object) {
+ return AnimationTrackEdit::get_key_rect(p_index, p_pixels_sec);
+ }
+
+ AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(object);
+
+ if (!ap) {
+ return AnimationTrackEdit::get_key_rect(p_index, p_pixels_sec);
+ }
+
+ String anim = get_animation()->animation_track_get_key_animation(get_track(), p_index);
+ print_line("anim " + anim + " has " + itos(ap->has_animation(anim)));
+
+ if (anim != "[stop]" && ap->has_animation(anim)) {
+
+ float len = ap->get_animation(anim)->get_length();
+
+ if (get_animation()->track_get_key_count(get_track()) > p_index + 1) {
+ len = MIN(len, get_animation()->track_get_key_time(get_track(), p_index + 1) - get_animation()->track_get_key_time(get_track(), p_index));
+ }
+
+ return Rect2(0, 0, len * p_pixels_sec, get_size().height);
+ } else {
+ Ref<Font> font = get_font("font", "Label");
+ int fh = font->get_height() * 0.8;
+ return Rect2(0, 0, fh, get_size().height);
+ }
+}
+
+bool AnimationTrackEditTypeAnimation::is_key_selectable_by_distance() const {
+
+ return false;
+}
+void AnimationTrackEditTypeAnimation::draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) {
+
+ Object *object = ObjectDB::get_instance(id);
+
+ if (!object) {
+ AnimationTrackEdit::draw_key(p_index, p_pixels_sec, p_x, p_selected, p_clip_left, p_clip_right);
+ return;
+ }
+
+ AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(object);
+
+ if (!ap) {
+ AnimationTrackEdit::draw_key(p_index, p_pixels_sec, p_x, p_selected, p_clip_left, p_clip_right);
+ return;
+ }
+
+ String anim = get_animation()->animation_track_get_key_animation(get_track(), p_index);
+
+ if (anim != "[stop]" && ap->has_animation(anim)) {
+
+ float len = ap->get_animation(anim)->get_length();
+
+ if (get_animation()->track_get_key_count(get_track()) > p_index + 1) {
+ len = MIN(len, get_animation()->track_get_key_time(get_track(), p_index + 1) - get_animation()->track_get_key_time(get_track(), p_index));
+ }
+
+ int pixel_len = len * p_pixels_sec;
+
+ int pixel_begin = p_x;
+ int pixel_end = p_x + pixel_len;
+
+ if (pixel_end < p_clip_left)
+ return;
+
+ if (pixel_begin > p_clip_right)
+ return;
+
+ int from_x = MAX(pixel_begin, p_clip_left);
+ int to_x = MIN(pixel_end, p_clip_right);
+
+ if (to_x <= from_x)
+ return;
+
+ Ref<Font> font = get_font("font", "Label");
+ int fh = font->get_height() * 1.5;
+
+ Rect2 rect(from_x, int(get_size().height - fh) / 2, to_x - from_x, fh);
+
+ Color color = get_color("font_color", "Label");
+ Color bg = color;
+ bg.r = 1 - color.r;
+ bg.g = 1 - color.g;
+ bg.b = 1 - color.b;
+ draw_rect(rect, bg);
+
+ Vector<Vector2> lines;
+ Vector<Color> colorv;
+ {
+ Ref<Animation> animation = ap->get_animation(anim);
+
+ for (int i = 0; i < animation->get_track_count(); i++) {
+
+ float h = (rect.size.height - 2) / animation->get_track_count();
+
+ int y = 2 + h * i + h / 2;
+
+ for (int j = 0; j < animation->track_get_key_count(i); j++) {
+
+ float ofs = animation->track_get_key_time(i, j);
+ int x = p_x + ofs * p_pixels_sec + 2;
+
+ if (x < from_x || x >= (to_x - 4))
+ continue;
+
+ lines.push_back(Point2(x, y));
+ lines.push_back(Point2(x + 1, y));
+ }
+ }
+
+ colorv.push_back(color);
+ }
+
+ if (lines.size() > 2) {
+ VS::get_singleton()->canvas_item_add_multiline(get_canvas_item(), lines, colorv);
+ }
+
+ int limit = to_x - from_x - 4;
+ if (limit > 0) {
+ draw_string(font, Point2(from_x + 2, int(get_size().height - font->get_height()) / 2 + font->get_ascent()), anim, color);
+ }
+
+ if (p_selected) {
+ Color accent = get_color("accent_color", "Editor");
+ draw_rect(rect, accent, false);
+ }
+ } else {
+ Ref<Font> font = get_font("font", "Label");
+ int fh = font->get_height() * 0.8;
+ Rect2 rect(Vector2(p_x, int(get_size().height - fh) / 2), Size2(fh, fh));
+
+ Color color = get_color("font_color", "Label");
+ draw_rect(rect, color);
+
+ if (p_selected) {
+ Color accent = get_color("accent_color", "Editor");
+ draw_rect(rect, accent, false);
+ }
+ }
+}
+
+void AnimationTrackEditTypeAnimation::set_node(Object *p_object) {
+
+ id = p_object->get_instance_id();
+}
+
+AnimationTrackEditTypeAnimation::AnimationTrackEditTypeAnimation() {
+}
+
+/////////
+AnimationTrackEdit *AnimationTrackEditDefaultPlugin::create_value_track_edit(Object *p_object, Variant::Type p_type, const String &p_property, PropertyHint p_hint, const String &p_hint_string, int p_usage) {
+
+ if (p_property == "playing" && (p_object->is_class("AudioStreamPlayer") || p_object->is_class("AudioStreamPlayer2D") || p_object->is_class("AudioStreamPlayer3D"))) {
+
+ AnimationTrackEditAudio *audio = memnew(AnimationTrackEditAudio);
+ audio->set_node(p_object);
+ return audio;
+ }
+
+ if (p_property == "frame" && (p_object->is_class("Sprite") || p_object->is_class("Sprite3D") || p_object->is_class("AnimatedSprite") || p_object->is_class("AnimatedSprite3D"))) {
+
+ AnimationTrackEditSpriteFrame *sprite = memnew(AnimationTrackEditSpriteFrame);
+ sprite->set_node(p_object);
+ return sprite;
+ }
+
+ if (p_property == "current_animation" && (p_object->is_class("AnimationPlayer"))) {
+
+ AnimationTrackEditSubAnim *player = memnew(AnimationTrackEditSubAnim);
+ player->set_node(p_object);
+ return player;
+ }
+
+ if (p_property == "volume_db") {
+
+ AnimationTrackEditVolumeDB *vu = memnew(AnimationTrackEditVolumeDB);
+ return vu;
+ }
+
+ if (p_type == Variant::BOOL) {
+ return memnew(AnimationTrackEditBool);
+ }
+ if (p_type == Variant::COLOR) {
+ return memnew(AnimationTrackEditColor);
+ }
+
+ return NULL;
+}
+
+AnimationTrackEdit *AnimationTrackEditDefaultPlugin::create_audio_track_edit() {
+
+ return memnew(AnimationTrackEditTypeAudio);
+}
+
+AnimationTrackEdit *AnimationTrackEditDefaultPlugin::create_animation_track_edit(Object *p_object) {
+
+ AnimationTrackEditTypeAnimation *an = memnew(AnimationTrackEditTypeAnimation);
+ an->set_node(p_object);
+ return an;
+}
diff --git a/editor/animation_track_editor_plugins.h b/editor/animation_track_editor_plugins.h
new file mode 100644
index 0000000000..59604412d9
--- /dev/null
+++ b/editor/animation_track_editor_plugins.h
@@ -0,0 +1,139 @@
+#ifndef ANIMATION_TRACK_EDITOR_PLUGINS_H
+#define ANIMATION_TRACK_EDITOR_PLUGINS_H
+
+#include "editor/animation_track_editor.h"
+
+class AnimationTrackEditBool : public AnimationTrackEdit {
+ GDCLASS(AnimationTrackEditBool, AnimationTrackEdit)
+ Ref<Texture> icon_checked;
+ Ref<Texture> icon_unchecked;
+
+public:
+ virtual int get_key_height() const;
+ virtual Rect2 get_key_rect(int p_index, float p_pixels_sec);
+ virtual bool is_key_selectable_by_distance() const;
+ virtual void draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right);
+};
+
+class AnimationTrackEditColor : public AnimationTrackEdit {
+ GDCLASS(AnimationTrackEditColor, AnimationTrackEdit)
+
+public:
+ virtual int get_key_height() const;
+ virtual Rect2 get_key_rect(int p_index, float p_pixels_sec);
+ virtual bool is_key_selectable_by_distance() const;
+ virtual void draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right);
+ virtual void draw_key_link(int p_index, float p_pixels_sec, int p_x, int p_next_x, int p_clip_left, int p_clip_right);
+};
+
+class AnimationTrackEditAudio : public AnimationTrackEdit {
+ GDCLASS(AnimationTrackEditAudio, AnimationTrackEdit)
+
+ ObjectID id;
+
+ void _preview_changed(ObjectID p_which);
+
+protected:
+ static void _bind_methods();
+
+public:
+ virtual int get_key_height() const;
+ virtual Rect2 get_key_rect(int p_index, float p_pixels_sec);
+ virtual bool is_key_selectable_by_distance() const;
+ virtual void draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right);
+
+ void set_node(Object *p_object);
+
+ AnimationTrackEditAudio();
+};
+
+class AnimationTrackEditSpriteFrame : public AnimationTrackEdit {
+ GDCLASS(AnimationTrackEditSpriteFrame, AnimationTrackEdit)
+
+ ObjectID id;
+
+public:
+ virtual int get_key_height() const;
+ virtual Rect2 get_key_rect(int p_index, float p_pixels_sec);
+ virtual bool is_key_selectable_by_distance() const;
+ virtual void draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right);
+
+ void set_node(Object *p_object);
+};
+
+class AnimationTrackEditSubAnim : public AnimationTrackEdit {
+ GDCLASS(AnimationTrackEditSubAnim, AnimationTrackEdit)
+
+ ObjectID id;
+
+public:
+ virtual int get_key_height() const;
+ virtual Rect2 get_key_rect(int p_index, float p_pixels_sec);
+ virtual bool is_key_selectable_by_distance() const;
+ virtual void draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right);
+
+ void set_node(Object *p_object);
+};
+
+class AnimationTrackEditTypeAudio : public AnimationTrackEdit {
+ GDCLASS(AnimationTrackEditTypeAudio, AnimationTrackEdit)
+
+ void _preview_changed(ObjectID p_which);
+
+ bool len_resizing;
+ bool len_resizing_start;
+ int len_resizing_index;
+ float len_resizing_from_px;
+ float len_resizing_rel;
+
+protected:
+ static void _bind_methods();
+
+public:
+ virtual void _gui_input(const Ref<InputEvent> &p_event);
+
+ virtual bool can_drop_data(const Point2 &p_point, const Variant &p_data) const;
+ virtual void drop_data(const Point2 &p_point, const Variant &p_data);
+
+ virtual int get_key_height() const;
+ virtual Rect2 get_key_rect(int p_index, float p_pixels_sec);
+ virtual bool is_key_selectable_by_distance() const;
+ virtual void draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right);
+
+ AnimationTrackEditTypeAudio();
+};
+
+class AnimationTrackEditTypeAnimation : public AnimationTrackEdit {
+ GDCLASS(AnimationTrackEditTypeAnimation, AnimationTrackEdit)
+
+ ObjectID id;
+
+public:
+ virtual int get_key_height() const;
+ virtual Rect2 get_key_rect(int p_index, float p_pixels_sec);
+ virtual bool is_key_selectable_by_distance() const;
+ virtual void draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right);
+
+ void set_node(Object *p_object);
+ AnimationTrackEditTypeAnimation();
+};
+
+class AnimationTrackEditVolumeDB : public AnimationTrackEdit {
+ GDCLASS(AnimationTrackEditVolumeDB, AnimationTrackEdit)
+
+public:
+ virtual void draw_bg(int p_clip_left, int p_clip_right);
+ virtual void draw_fg(int p_clip_left, int p_clip_right);
+ virtual int get_key_height() const;
+ virtual void draw_key_link(int p_index, float p_pixels_sec, int p_x, int p_next_x, int p_clip_left, int p_clip_right);
+};
+
+class AnimationTrackEditDefaultPlugin : public AnimationTrackEditPlugin {
+ GDCLASS(AnimationTrackEditDefaultPlugin, AnimationTrackEditPlugin)
+public:
+ virtual AnimationTrackEdit *create_value_track_edit(Object *p_object, Variant::Type p_type, const String &p_property, PropertyHint p_hint, const String &p_hint_string, int p_usage);
+ virtual AnimationTrackEdit *create_audio_track_edit();
+ virtual AnimationTrackEdit *create_animation_track_edit(Object *p_object);
+};
+
+#endif // ANIMATION_TRACK_EDITOR_PLUGINS_H
diff --git a/editor/audio_stream_preview.cpp b/editor/audio_stream_preview.cpp
new file mode 100644
index 0000000000..6ee4d7f4b0
--- /dev/null
+++ b/editor/audio_stream_preview.cpp
@@ -0,0 +1,211 @@
+#include "audio_stream_preview.h"
+
+/////////////////////
+
+float AudioStreamPreview::get_length() const {
+ return length;
+}
+float AudioStreamPreview::get_max(float p_time, float p_time_next) const {
+
+ if (length == 0)
+ return 0;
+
+ int max = preview.size() / 2;
+ int time_from = p_time / length * max;
+ int time_to = p_time_next / length * max;
+ time_from = CLAMP(time_from, 0, max - 1);
+ time_to = CLAMP(time_to, 0, max - 1);
+
+ if (time_to <= time_from) {
+ time_to = time_from + 1;
+ }
+
+ uint8_t vmax;
+
+ for (int i = time_from; i < time_to; i++) {
+
+ uint8_t v = preview[i * 2 + 1];
+ if (i == 0 || v > vmax) {
+ vmax = v;
+ }
+ }
+
+ return (vmax / 255.0) * 2.0 - 1.0;
+}
+float AudioStreamPreview::get_min(float p_time, float p_time_next) const {
+
+ if (length == 0)
+ return 0;
+
+ int max = preview.size() / 2;
+ int time_from = p_time / length * max;
+ int time_to = p_time_next / length * max;
+ time_from = CLAMP(time_from, 0, max - 1);
+ time_to = CLAMP(time_to, 0, max - 1);
+
+ if (time_to <= time_from) {
+ time_to = time_from + 1;
+ }
+
+ uint8_t vmin;
+
+ for (int i = time_from; i < time_to; i++) {
+
+ uint8_t v = preview[i * 2];
+ if (i == 0 || v < vmin) {
+ vmin = v;
+ }
+ }
+
+ return (vmin / 255.0) * 2.0 - 1.0;
+}
+
+AudioStreamPreview::AudioStreamPreview() {
+ length = 0;
+}
+
+////
+
+void AudioStreamPreviewGenerator::_update_emit(ObjectID p_id) {
+ emit_signal("preview_updated", p_id);
+}
+
+void AudioStreamPreviewGenerator::_preview_thread(void *p_preview) {
+
+ Preview *preview = (Preview *)p_preview;
+
+ float muxbuff_chunk_s = 0.25;
+
+ int mixbuff_chunk_frames = AudioServer::get_singleton()->get_mix_rate() * muxbuff_chunk_s;
+
+ Vector<AudioFrame> mix_chunk;
+ mix_chunk.resize(mixbuff_chunk_frames);
+
+ int frames_total = AudioServer::get_singleton()->get_mix_rate() * preview->preview->length;
+ int frames_todo = frames_total;
+
+ preview->playback->start();
+
+ while (frames_todo) {
+
+ int ofs_write = uint64_t(frames_total - frames_todo) * uint64_t(preview->preview->preview.size() / 2) / uint64_t(frames_total);
+ int to_read = MIN(frames_todo, mixbuff_chunk_frames);
+ int to_write = uint64_t(to_read) * uint64_t(preview->preview->preview.size() / 2) / uint64_t(frames_total);
+ to_write = MIN(to_write, (preview->preview->preview.size() / 2) - ofs_write);
+
+ preview->playback->mix(mix_chunk.ptrw(), 1.0, to_read);
+
+ for (int i = 0; i < to_write; i++) {
+ float max = -1000;
+ float min = 1000;
+ int from = uint64_t(i) * to_read / to_write;
+ int to = uint64_t(i + 1) * to_read / to_write;
+ to = MIN(to, to_read);
+ from = MIN(from, to_read - 1);
+ if (to == from) {
+ to = from + 1;
+ }
+
+ for (int j = from; j < to; j++) {
+
+ max = MAX(max, mix_chunk[j].l);
+ max = MAX(max, mix_chunk[j].r);
+
+ min = MIN(min, mix_chunk[j].l);
+ min = MIN(min, mix_chunk[j].r);
+ }
+
+ uint8_t pfrom = CLAMP((min * 0.5 + 0.5) * 255, 0, 255);
+ uint8_t pto = CLAMP((max * 0.5 + 0.5) * 255, 0, 255);
+
+ preview->preview->preview[(ofs_write + i) * 2 + 0] = pfrom;
+ preview->preview->preview[(ofs_write + i) * 2 + 1] = pto;
+ }
+
+ frames_todo -= to_read;
+ singleton->call_deferred("_update_emit", preview->id);
+ }
+
+ preview->playback->stop();
+
+ preview->generating = false;
+}
+
+Ref<AudioStreamPreview> AudioStreamPreviewGenerator::generate_preview(const Ref<AudioStream> &p_stream) {
+ ERR_FAIL_COND_V(p_stream.is_null(), Ref<AudioStreamPreview>());
+
+ if (previews.has(p_stream->get_instance_id())) {
+ return previews[p_stream->get_instance_id()].preview;
+ }
+
+ //no preview exists
+
+ previews[p_stream->get_instance_id()] = Preview();
+
+ Preview *preview = &previews[p_stream->get_instance_id()];
+ preview->base_stream = p_stream;
+ preview->playback = preview->base_stream->instance_playback();
+ preview->generating = true;
+ preview->id = p_stream->get_instance_id();
+
+ float len_s = preview->base_stream->get_length();
+ if (len_s == 0) {
+ len_s = 60 * 5; //five minutes
+ }
+
+ int frames = AudioServer::get_singleton()->get_mix_rate() * len_s;
+
+ Vector<uint8_t> maxmin;
+ int pw = frames / 20;
+ maxmin.resize(pw * 2);
+ {
+ uint8_t *ptr = maxmin.ptrw();
+ for (int i = 0; i < pw * 2; i++) {
+ ptr[i] = 127;
+ }
+ }
+
+ preview->preview.instance();
+ preview->preview->preview = maxmin;
+ preview->preview->length = len_s;
+
+ preview->thread = Thread::create(_preview_thread, preview);
+
+ return preview->preview;
+}
+
+void AudioStreamPreviewGenerator::_bind_methods() {
+ ClassDB::bind_method("_update_emit", &AudioStreamPreviewGenerator::_update_emit);
+ ClassDB::bind_method(D_METHOD("generate_preview", "stream"), &AudioStreamPreviewGenerator::generate_preview);
+
+ ADD_SIGNAL(MethodInfo("preview_updated", PropertyInfo(Variant::INT, "obj_id")));
+}
+
+AudioStreamPreviewGenerator *AudioStreamPreviewGenerator::singleton = NULL;
+
+void AudioStreamPreviewGenerator::_notification(int p_what) {
+ if (p_what == NOTIFICATION_PROCESS) {
+ List<ObjectID> to_erase;
+ for (Map<ObjectID, Preview>::Element *E = previews.front(); E; E = E->next()) {
+ if (!E->get().generating) {
+ if (E->get().thread) {
+ Thread::wait_to_finish(E->get().thread);
+ E->get().thread = NULL;
+ }
+ if (!ObjectDB::get_instance(E->key())) { //no longer in use, get rid of preview
+ to_erase.push_back(E->key());
+ }
+ }
+ }
+
+ while (to_erase.front()) {
+ previews.erase(to_erase.front()->get());
+ to_erase.pop_front();
+ }
+ }
+}
+
+AudioStreamPreviewGenerator::AudioStreamPreviewGenerator() {
+ singleton = this;
+ set_process(true);
+}
diff --git a/editor/audio_stream_preview.h b/editor/audio_stream_preview.h
new file mode 100644
index 0000000000..cfe1667e9d
--- /dev/null
+++ b/editor/audio_stream_preview.h
@@ -0,0 +1,56 @@
+#ifndef AUDIO_STREAM_PREVIEW_H
+#define AUDIO_STREAM_PREVIEW_H
+
+#include "os/thread.h"
+#include "scene/main/node.h"
+#include "servers/audio/audio_stream.h"
+
+class AudioStreamPreview : public Reference {
+ GDCLASS(AudioStreamPreview, Reference)
+ friend class AudioStream;
+ Vector<uint8_t> preview;
+ float length;
+
+ friend class AudioStreamPreviewGenerator;
+
+public:
+ float get_length() const;
+ float get_max(float p_time, float p_time_next) const;
+ float get_min(float p_time, float p_time_next) const;
+
+ AudioStreamPreview();
+};
+
+class AudioStreamPreviewGenerator : public Node {
+ GDCLASS(AudioStreamPreviewGenerator, Node)
+
+ static AudioStreamPreviewGenerator *singleton;
+
+ struct Preview {
+ Ref<AudioStreamPreview> preview;
+ Ref<AudioStream> base_stream;
+ Ref<AudioStreamPlayback> playback;
+ volatile bool generating;
+ ObjectID id;
+ Thread *thread;
+ };
+
+ Map<ObjectID, Preview> previews;
+
+ static void _preview_thread(void *p_preview);
+
+ void _update_emit(ObjectID p_id);
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+ static AudioStreamPreviewGenerator *get_singleton() { return singleton; }
+
+ Ref<AudioStreamPreview> generate_preview(const Ref<AudioStream> &p_preview);
+
+ AudioStreamPreviewGenerator();
+};
+
+#endif // AUDIO_STREAM_PREVIEW_H
diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp
index a6f159ce34..ebfb63b1f3 100644
--- a/editor/editor_inspector.cpp
+++ b/editor/editor_inspector.cpp
@@ -1331,8 +1331,9 @@ void EditorInspector::update_tree() {
} else if (!(p.usage & PROPERTY_USAGE_EDITOR))
continue;
- if (hide_script && p.name == "script")
+ if (p.name == "script" && (hide_script || bool(object->call("_hide_script_from_inspector")))) {
continue;
+ }
String basename = p.name;
if (group != "") {
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 7e3af2b755..067c451012 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -51,7 +51,6 @@
#include "scene/resources/packed_scene.h"
#include "servers/physics_2d_server.h"
-#include "editor/animation_editor.h"
#include "editor/editor_audio_buses.h"
#include "editor/editor_file_system.h"
#include "editor/editor_help.h"
@@ -3038,6 +3037,7 @@ void EditorNode::register_editor_types() {
ClassDB::register_class<EditorInspector>();
ClassDB::register_class<EditorInspectorPlugin>();
ClassDB::register_class<EditorProperty>();
+ ClassDB::register_class<AnimationTrackEditPlugin>();
// FIXME: Is this stuff obsolete, or should it be ported to new APIs?
ClassDB::register_class<EditorScenePostImport>();
@@ -5300,6 +5300,8 @@ EditorNode::EditorNode() {
file->connect("file_selected", this, "_dialog_action");
file_templates->connect("file_selected", this, "_dialog_action");
+ preview_gen = memnew(AudioStreamPreviewGenerator);
+ add_child(preview_gen);
//plugin stuff
file_server = memnew(EditorFileServer);
@@ -5382,8 +5384,7 @@ EditorNode::EditorNode() {
resource_preview->add_preview_generator(Ref<EditorPackedScenePreviewPlugin>(memnew(EditorPackedScenePreviewPlugin)));
resource_preview->add_preview_generator(Ref<EditorMaterialPreviewPlugin>(memnew(EditorMaterialPreviewPlugin)));
resource_preview->add_preview_generator(Ref<EditorScriptPreviewPlugin>(memnew(EditorScriptPreviewPlugin)));
- // FIXME: Needs to be rewritten for AudioStream in Godot 3.0+
- //resource_preview->add_preview_generator( Ref<EditorSamplePreviewPlugin>( memnew(EditorSamplePreviewPlugin )));
+ resource_preview->add_preview_generator(Ref<EditorAudioStreamPreviewPlugin>(memnew(EditorAudioStreamPreviewPlugin)));
resource_preview->add_preview_generator(Ref<EditorMeshPreviewPlugin>(memnew(EditorMeshPreviewPlugin)));
resource_preview->add_preview_generator(Ref<EditorBitmapPreviewPlugin>(memnew(EditorBitmapPreviewPlugin)));
resource_preview->add_preview_generator(Ref<EditorFontPreviewPlugin>(memnew(EditorFontPreviewPlugin)));
diff --git a/editor/editor_node.h b/editor/editor_node.h
index bef5bc816c..416ec9b31e 100644
--- a/editor/editor_node.h
+++ b/editor/editor_node.h
@@ -32,6 +32,7 @@
#define EDITOR_NODE_H
#include "core/print_string.h"
+#include "editor/audio_stream_preview.h"
#include "editor/connections_dialog.h"
#include "editor/create_dialog.h"
#include "editor/editor_about.h"
@@ -81,6 +82,7 @@
#include "scene/gui/tool_button.h"
#include "scene/gui/tree.h"
#include "scene/gui/viewport_container.h"
+
/**
@author Juan Linietsky <reduzio@gmail.com>
*/
@@ -298,6 +300,7 @@ private:
Vector<ToolButton *> main_editor_buttons;
Vector<EditorPlugin *> editor_table;
+ AudioStreamPreviewGenerator *preview_gen;
ProgressDialog *progress_dialog;
BackgroundProgress *progress_hb;
diff --git a/editor/editor_spin_slider.cpp b/editor/editor_spin_slider.cpp
index 087dcd649f..0852a42794 100644
--- a/editor/editor_spin_slider.cpp
+++ b/editor/editor_spin_slider.cpp
@@ -37,6 +37,9 @@ String EditorSpinSlider::get_text_value() const {
}
void EditorSpinSlider::_gui_input(const Ref<InputEvent> &p_event) {
+ if (read_only)
+ return;
+
Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid() && mb->get_button_index() == BUTTON_LEFT) {
@@ -301,10 +304,23 @@ void EditorSpinSlider::_grabber_mouse_exited() {
update();
}
+void EditorSpinSlider::set_read_only(bool p_enable) {
+
+ read_only = p_enable;
+ update();
+}
+
+bool EditorSpinSlider::is_read_only() const {
+ return read_only;
+}
+
void EditorSpinSlider::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_label", "label"), &EditorSpinSlider::set_label);
ClassDB::bind_method(D_METHOD("get_label"), &EditorSpinSlider::get_label);
+ ClassDB::bind_method(D_METHOD("set_read_only", "read_only"), &EditorSpinSlider::set_read_only);
+ ClassDB::bind_method(D_METHOD("is_read_only"), &EditorSpinSlider::is_read_only);
+
ClassDB::bind_method(D_METHOD("_gui_input"), &EditorSpinSlider::_gui_input);
ClassDB::bind_method(D_METHOD("_grabber_mouse_entered"), &EditorSpinSlider::_grabber_mouse_entered);
ClassDB::bind_method(D_METHOD("_grabber_mouse_exited"), &EditorSpinSlider::_grabber_mouse_exited);
@@ -313,6 +329,7 @@ void EditorSpinSlider::_bind_methods() {
ClassDB::bind_method(D_METHOD("_value_input_entered"), &EditorSpinSlider::_value_input_entered);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "label"), "set_label", "get_label");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "read_only"), "set_read_only", "is_read_only");
}
EditorSpinSlider::EditorSpinSlider() {
@@ -342,4 +359,5 @@ EditorSpinSlider::EditorSpinSlider() {
value_input->connect("modal_closed", this, "_value_input_closed");
value_input->connect("text_entered", this, "_value_input_entered");
hide_slider = false;
+ read_only = false;
}
diff --git a/editor/editor_spin_slider.h b/editor/editor_spin_slider.h
index 4956990dc2..37d8a5f128 100644
--- a/editor/editor_spin_slider.h
+++ b/editor/editor_spin_slider.h
@@ -55,6 +55,8 @@ class EditorSpinSlider : public Range {
bool grabbing_spinner_attempt;
bool grabbing_spinner;
+
+ bool read_only;
Vector2 grabbing_spinner_mouse_pos;
LineEdit *value_input;
@@ -80,6 +82,9 @@ public:
void set_hide_slider(bool p_hide);
bool is_hiding_slider() const;
+ void set_read_only(bool p_enable);
+ bool is_read_only() const;
+
virtual Size2 get_minimum_size() const;
EditorSpinSlider();
};
diff --git a/editor/icons/icon_animation_filter.svg b/editor/icons/icon_animation_filter.svg
new file mode 100644
index 0000000000..4f8e881ea8
--- /dev/null
+++ b/editor/icons/icon_animation_filter.svg
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16"
+ height="16"
+ version="1.1"
+ viewBox="0 0 16 16"
+ id="svg6"
+ sodipodi:docname="icon_animation_filter.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs10" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1089"
+ inkscape:window-height="480"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="10.429825"
+ inkscape:cx="-5.6414698"
+ inkscape:cy="10.961343"
+ inkscape:window-x="67"
+ inkscape:window-y="27"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="g10" />
+ <g
+ transform="matrix(0.02719109,0,0,0.02719109,1.3153462,1.0022864)"
+ id="g12">
+ <g
+ id="g10">
+ <path
+ inkscape:connector-curvature="0"
+ d="M 495.289,20.143 H 16.709 c -14.938,0 -22.344,18.205 -11.666,28.636 l 169.7,165.778 v 260.587 c 0,14.041 16.259,21.739 27.131,13.031 L 331.017,384.743 c 3.956,-3.169 6.258,-7.962 6.258,-13.031 V 214.556 L 506.955,48.779 c 10.688,-10.44 3.259,-28.636 -11.666,-28.636 z"
+ id="path8"
+ style="fill:#e0e0e0;fill-opacity:1" />
+ </g>
+ </g>
+</svg>
diff --git a/editor/icons/icon_animation_track_group.svg b/editor/icons/icon_animation_track_group.svg
new file mode 100644
index 0000000000..9c4748a528
--- /dev/null
+++ b/editor/icons/icon_animation_track_group.svg
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16"
+ height="16"
+ version="1.1"
+ viewBox="0 0 16 16"
+ id="svg6"
+ sodipodi:docname="icon_animation_track_group.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs10" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1089"
+ inkscape:window-height="480"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="10.429825"
+ inkscape:cx="6.2135985"
+ inkscape:cy="6.5622523"
+ inkscape:window-x="67"
+ inkscape:window-y="27"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg6" />
+ <path
+ style="fill:#e0e0e0"
+ inkscape:connector-curvature="0"
+ id="path2"
+ d="M 5.0508475,2 V 4 H 14 V 2 Z m -3.322034,-0.016949 v 2 h 2 v -2 z M 8.9830508,7 V 9 H 14 V 7 Z m -3.5254237,5 v 2 h 2 v -2 z m 3.5254237,0 v 2 H 14 v -2 z"
+ sodipodi:nodetypes="ccccccccccccccccccccccccc" />
+ <path
+ style="fill:#e0e0e0"
+ inkscape:connector-curvature="0"
+ id="path2-3"
+ d="m 5.4915255,6.9322039 v 1.999999 h 2 v -1.999999 z"
+ sodipodi:nodetypes="ccccc" />
+</svg>
diff --git a/editor/icons/icon_animation_track_list.svg b/editor/icons/icon_animation_track_list.svg
new file mode 100644
index 0000000000..40e8414598
--- /dev/null
+++ b/editor/icons/icon_animation_track_list.svg
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16"
+ height="16"
+ version="1.1"
+ viewBox="0 0 16 16"
+ id="svg6"
+ sodipodi:docname="icon_animation_track_list.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs10" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1089"
+ inkscape:window-height="480"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="14.75"
+ inkscape:cx="8"
+ inkscape:cy="8"
+ inkscape:window-x="67"
+ inkscape:window-y="27"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg6" />
+ <g
+ transform="translate(0 -1036.4)"
+ id="g4">
+ <path
+ transform="translate(0 1036.4)"
+ d="m2 2v2h2v-2h-2zm4 0v2h8v-2h-8zm-4 5v2h2v-2h-2zm4 0v2h8v-2h-8zm-4 5v2h2v-2h-2zm4 0v2h8v-2h-8z"
+ fill="#e0e0e0"
+ id="path2" />
+ </g>
+</svg>
diff --git a/editor/icons/icon_bezier_handles_balanced.svg b/editor/icons/icon_bezier_handles_balanced.svg
new file mode 100644
index 0000000000..8ab99d79bb
--- /dev/null
+++ b/editor/icons/icon_bezier_handles_balanced.svg
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16"
+ height="16"
+ version="1.1"
+ viewBox="0 0 16 16"
+ id="svg6"
+ sodipodi:docname="icon_bezier_handles_balanced.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs10" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1417"
+ inkscape:window-height="685"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="20.85965"
+ inkscape:cx="4.2910315"
+ inkscape:cy="11.857644"
+ inkscape:window-x="67"
+ inkscape:window-y="27"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg6" />
+ <path
+ style="fill:none;stroke:#84c2ff;stroke-width:1.70000005;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1"
+ d="m 1.7627119,13.627119 c 0,0 1.2881355,-6.847458 6.5762712,-8.1355935 5.0847459,0.9491522 5.9661009,8.1355925 5.9661009,8.1355925"
+ id="path4526"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccc" />
+ <ellipse
+ style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:5.64574671;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1"
+ id="path5846"
+ cx="1.8983043"
+ cy="13.491526"
+ rx="1.2675855"
+ ry="1.1997888" />
+ <ellipse
+ style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:5.64574671;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1"
+ id="path5846-3"
+ cx="14.237288"
+ cy="13.491526"
+ rx="1.2675855"
+ ry="1.1997888" />
+ <path
+ style="fill:none;stroke:#84c2ff;stroke-width:0.61799997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 7.4559186,5.1473018 2.7203863,6.7014816"
+ id="path5878"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#84c2ff;stroke-width:0.61489719;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 10.790357,4.2063094 8.2893822,5.149623"
+ id="path5878-7"
+ inkscape:connector-curvature="0" />
+ <ellipse
+ style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:5.64574671;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1"
+ id="path5846-3-6"
+ cx="8.2711868"
+ cy="4.7796612"
+ rx="1.2675855"
+ ry="1.1997888" />
+ <path
+ style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:5.64574671;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1"
+ d="M 1.7157324,5.8754878 A 1.2675855,1.1997888 0 0 0 0.44815434,7.0747066 1.2675855,1.1997888 0 0 0 1.7157324,8.2739253 1.2675855,1.1997888 0 0 0 2.9833105,7.0747066 1.2675855,1.1997888 0 0 0 1.7157324,5.8754878 Z m 0.00195,0.4238282 A 0.84677333,0.80148375 0 0 1 2.5653417,7.1000972 0.84677333,0.80148375 0 0 1 1.7176855,7.9008784 0.84677333,0.80148375 0 0 1 0.87002934,7.1000972 0.84677333,0.80148375 0 0 1 1.7176855,6.299316 Z"
+ id="path5846-5"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:5.7567277;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1"
+ d="M 11.909414,2.4642073 A 1.2836218,1.231838 0 0 0 10.6258,3.6954601 1.2836218,1.231838 0 0 0 11.909414,4.9267128 1.2836218,1.231838 0 0 0 13.193028,3.6954601 1.2836218,1.231838 0 0 0 11.909414,2.4642073 Z m 0.002,0.4351497 a 0.85748593,0.82289328 0 0 1 0.858383,0.8221719 0.85748593,0.82289328 0 0 1 -0.85838,0.822172 0.85748593,0.82289328 0 0 1 -0.858379,-0.822172 0.85748593,0.82289328 0 0 1 0.858379,-0.8221719 z"
+ id="path5846-5-6" />
+</svg>
diff --git a/editor/icons/icon_bezier_handles_free.svg b/editor/icons/icon_bezier_handles_free.svg
new file mode 100644
index 0000000000..e5dfb8d0fc
--- /dev/null
+++ b/editor/icons/icon_bezier_handles_free.svg
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16"
+ height="16"
+ version="1.1"
+ viewBox="0 0 16 16"
+ id="svg6"
+ sodipodi:docname="icon_bezier_handles_separate.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs10" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1417"
+ inkscape:window-height="685"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="20.85965"
+ inkscape:cx="4.2910315"
+ inkscape:cy="11.857644"
+ inkscape:window-x="67"
+ inkscape:window-y="27"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg6" />
+ <path
+ style="fill:none;stroke:#84c2ff;stroke-width:1.70000005;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1"
+ d="m 1.7627119,13.627119 c 0,0 1.2881355,-6.847458 6.5762712,-8.1355935 5.0847459,0.9491522 5.9661009,8.1355925 5.9661009,8.1355925"
+ id="path4526"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccc" />
+ <ellipse
+ style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:5.64574671;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1"
+ id="path5846"
+ cx="1.8983043"
+ cy="13.491526"
+ rx="1.2675855"
+ ry="1.1997888" />
+ <ellipse
+ style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:5.64574671;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1"
+ id="path5846-3"
+ cx="14.237288"
+ cy="13.491526"
+ rx="1.2675855"
+ ry="1.1997888" />
+ <path
+ style="fill:none;stroke:#84c2ff;stroke-width:0.80513805;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 7.6850253,4.7560401 3.9088983,5.4168"
+ id="path5878"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#84c2ff;stroke-width:0.73079807;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 11.695505,2.3941651 8.696384,4.6876729"
+ id="path5878-7"
+ inkscape:connector-curvature="0" />
+ <ellipse
+ style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:5.64574671;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1"
+ id="path5846-3-6"
+ cx="8.2711868"
+ cy="4.7796612"
+ rx="1.2675855"
+ ry="1.1997888" />
+ <path
+ style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:5.64574671;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1"
+ d="M 2.4961199,4.3976698 A 1.1997888,1.2675855 80.074672 0 0 1.4542161,5.7974257 1.1997888,1.2675855 80.074672 0 0 2.9095255,6.7602105 1.1997888,1.2675855 80.074672 0 0 3.9514292,5.3604547 1.1997888,1.2675855 80.074672 0 0 2.4961199,4.3976698 Z m 0.074974,0.4171488 A 0.80148375,0.84677333 80.074672 0 1 3.5440925,5.4575082 0.80148375,0.84677333 80.074672 0 1 2.8471493,6.3924102 0.80148375,0.84677333 80.074672 0 1 1.8741535,5.74972 0.80148375,0.84677333 80.074672 0 1 2.5710967,4.814818 Z"
+ id="path5846-5"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:5.7567277;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1"
+ d="m 11.838896,0.64428913 a 1.231838,1.2836218 52.593897 0 0 -0.271701,1.75779027 1.231838,1.2836218 52.593897 0 0 1.767576,0.1983008 1.231838,1.2836218 52.593897 0 0 0.271701,-1.75779027 1.231838,1.2836218 52.593897 0 0 -1.767576,-0.1983008 z m 0.265925,0.3444462 A 0.82289328,0.85748593 52.593897 0 1 13.286115,1.1203938 0.82289328,0.85748593 52.593897 0 1 13.103698,2.2949179 0.82289328,0.85748593 52.593897 0 1 11.922407,2.163257 0.82289328,0.85748593 52.593897 0 1 12.104824,0.98873353 Z"
+ id="path5846-5-6" />
+</svg>
diff --git a/editor/icons/icon_bezier_handles_mirror.svg b/editor/icons/icon_bezier_handles_mirror.svg
new file mode 100644
index 0000000000..682c898368
--- /dev/null
+++ b/editor/icons/icon_bezier_handles_mirror.svg
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16"
+ height="16"
+ version="1.1"
+ viewBox="0 0 16 16"
+ id="svg6"
+ sodipodi:docname="icon_bezier_handles_mirror.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs10" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1417"
+ inkscape:window-height="685"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="20.85965"
+ inkscape:cx="4.2910315"
+ inkscape:cy="11.857644"
+ inkscape:window-x="67"
+ inkscape:window-y="27"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg6" />
+ <path
+ style="fill:none;stroke:#84c2ff;stroke-width:1.70000005;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1"
+ d="m 1.7627119,13.627119 c 0,0 1.2881355,-6.847458 6.5762712,-8.1355935 5.0847459,0.9491522 5.9661009,8.1355925 5.9661009,8.1355925"
+ id="path4526"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccc" />
+ <ellipse
+ style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:5.64574671;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1"
+ id="path5846"
+ cx="1.8983043"
+ cy="13.491526"
+ rx="1.2675855"
+ ry="1.1997888" />
+ <ellipse
+ style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:5.64574671;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1"
+ id="path5846-3"
+ cx="14.237288"
+ cy="13.491526"
+ rx="1.2675855"
+ ry="1.1997888" />
+ <path
+ style="fill:none;stroke:#84c2ff;stroke-width:0.80513805;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 8.2033896,4.6779662 H 4.3698875"
+ id="path5878"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#84c2ff;stroke-width:0.71670938;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 11.931789,4.6440679 H 8.2033896"
+ id="path5878-7"
+ inkscape:connector-curvature="0" />
+ <ellipse
+ style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:5.64574671;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1"
+ id="path5846-3-6"
+ cx="8.2711868"
+ cy="4.7796612"
+ rx="1.2675855"
+ ry="1.1997888" />
+ <path
+ style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:5.64574671;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1"
+ d="M 3.1539157,3.4305762 A 1.2675855,1.1997888 0 0 0 1.8863376,4.629795 1.2675855,1.1997888 0 0 0 3.1539157,5.8290137 1.2675855,1.1997888 0 0 0 4.4214938,4.629795 1.2675855,1.1997888 0 0 0 3.1539157,3.4305762 Z m 0.00195,0.4238282 A 0.84677333,0.80148375 0 0 1 4.003525,4.6551856 0.84677333,0.80148375 0 0 1 3.1558688,5.4559668 0.84677333,0.80148375 0 0 1 2.3082126,4.6551856 0.84677333,0.80148375 0 0 1 3.1558688,3.8544044 Z"
+ id="path5846-5"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:5.64574671;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1"
+ d="m 13.093969,3.3750567 a 1.2675855,1.1997888 0 0 0 -1.267578,1.1992188 1.2675855,1.1997888 0 0 0 1.267578,1.1992187 1.2675855,1.1997888 0 0 0 1.267578,-1.1992187 1.2675855,1.1997888 0 0 0 -1.267578,-1.1992188 z m 0.002,0.4238282 a 0.84677333,0.80148375 0 0 1 0.847659,0.8007812 0.84677333,0.80148375 0 0 1 -0.847656,0.8007812 0.84677333,0.80148375 0 0 1 -0.847656,-0.8007812 0.84677333,0.80148375 0 0 1 0.847656,-0.8007812 z"
+ id="path5846-5-6" />
+</svg>
diff --git a/editor/icons/icon_color_track_vu.svg b/editor/icons/icon_color_track_vu.svg
new file mode 100644
index 0000000000..cad76d0234
--- /dev/null
+++ b/editor/icons/icon_color_track_vu.svg
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16"
+ height="24"
+ version="1.1"
+ viewBox="0 0 16 24"
+ id="svg6"
+ sodipodi:docname="icon_color_track_vu.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs10">
+ <linearGradient
+ id="linearGradient4583"
+ inkscape:collect="always">
+ <stop
+ id="stop4579"
+ offset="0"
+ style="stop-color:#f70000;stop-opacity:1" />
+ <stop
+ id="stop4581"
+ offset="1"
+ style="stop-color:#eec315;stop-opacity:1" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient4549">
+ <stop
+ style="stop-color:#288027;stop-opacity:1"
+ offset="0"
+ id="stop4545" />
+ <stop
+ style="stop-color:#dbee15;stop-opacity:1"
+ offset="1"
+ id="stop4547" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4549"
+ id="linearGradient4551"
+ x1="7.7288136"
+ y1="16.474577"
+ x2="7.7288136"
+ y2="3.8644071"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0931873,0,0,1.4762899,-0.98021429,0.08553021)" />
+ <linearGradient
+ gradientTransform="matrix(1.1036585,0,0,0.47778193,-16.507235,-7.9018165)"
+ inkscape:collect="always"
+ xlink:href="#linearGradient4583"
+ id="linearGradient4551-7"
+ x1="7.7288136"
+ y1="16.474577"
+ x2="7.7288136"
+ y2="3.8644071"
+ gradientUnits="userSpaceOnUse" />
+ </defs>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1170"
+ inkscape:window-height="712"
+ id="namedview8"
+ showgrid="false"
+ showguides="false"
+ inkscape:zoom="14.75"
+ inkscape:cx="5.3261277"
+ inkscape:cy="13.681053"
+ inkscape:window-x="397"
+ inkscape:window-y="233"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg6" />
+ <rect
+ style="fill:url(#linearGradient4551);fill-opacity:1;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;paint-order:fill markers stroke"
+ id="rect822"
+ width="18.232145"
+ height="18.416088"
+ x="-1.3507863"
+ y="5.9906898"
+ ry="0.84580106" />
+ <rect
+ style="fill:url(#linearGradient4551-7);fill-opacity:1;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;paint-order:fill markers stroke"
+ id="rect822-5"
+ width="18.406782"
+ height="5.9601259"
+ x="-16.881357"
+ y="-5.9906898"
+ ry="0.27373245"
+ transform="scale(-1)" />
+</svg>
diff --git a/editor/icons/icon_edit_bezier.svg b/editor/icons/icon_edit_bezier.svg
new file mode 100644
index 0000000000..542ff52aac
--- /dev/null
+++ b/editor/icons/icon_edit_bezier.svg
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16"
+ height="16"
+ version="1.1"
+ viewBox="0 0 16 16"
+ id="svg6"
+ sodipodi:docname="icon_edit_bezier.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs10" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1039"
+ inkscape:window-height="585"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="20.85965"
+ inkscape:cx="11.65471"
+ inkscape:cy="9.0988062"
+ inkscape:window-x="277"
+ inkscape:window-y="113"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="g4" />
+ <g
+ transform="translate(0 -1036.4)"
+ id="g4">
+ <path
+ style="fill:none;stroke:#84c2ff;stroke-width:2.20000005;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1"
+ d="m 1.4758015,1050.3064 c 11.6492855,0.7191 3.1098343,-11.4976 12.2331255,-11.3475"
+ id="path4526"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <circle
+ style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:5.64574671;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1"
+ id="path5846-3"
+ cy="1038.7133"
+ cx="13.470984"
+ r="1.8230016" />
+ <circle
+ r="1.8230016"
+ cx="2.4449117"
+ cy="1050.1708"
+ id="circle1374"
+ style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:5.64574671;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+</svg>
diff --git a/editor/icons/icon_key_animation.svg b/editor/icons/icon_key_animation.svg
new file mode 100644
index 0000000000..a09567498f
--- /dev/null
+++ b/editor/icons/icon_key_animation.svg
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="8"
+ height="8"
+ version="1.1"
+ viewBox="0 0 8 8"
+ id="svg6"
+ sodipodi:docname="icon_key_animation.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs10" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1852"
+ inkscape:window-height="781"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="29.5"
+ inkscape:cx="-10.271186"
+ inkscape:cy="3.4149032"
+ inkscape:window-x="68"
+ inkscape:window-y="117"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="g4" />
+ <g
+ transform="translate(0 -1044.4)"
+ id="g4">
+ <rect
+ transform="rotate(-45)"
+ x="-741.53"
+ y="741.08"
+ width="6.1027"
+ height="6.1027"
+ ry=".76286"
+ fill="#ea686c"
+ id="rect2"
+ style="fill:#b76ef0;fill-opacity:1" />
+ </g>
+</svg>
diff --git a/editor/icons/icon_key_audio.svg b/editor/icons/icon_key_audio.svg
new file mode 100644
index 0000000000..7c728bfd01
--- /dev/null
+++ b/editor/icons/icon_key_audio.svg
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="8"
+ height="8"
+ version="1.1"
+ viewBox="0 0 8 8"
+ id="svg6"
+ sodipodi:docname="icon_key_audio.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs10" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1053"
+ inkscape:window-height="591"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="29.5"
+ inkscape:cx="4"
+ inkscape:cy="4"
+ inkscape:window-x="67"
+ inkscape:window-y="27"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="g4" />
+ <g
+ transform="translate(0 -1044.4)"
+ id="g4">
+ <rect
+ transform="rotate(-45)"
+ x="-741.53"
+ y="741.08"
+ width="6.1027"
+ height="6.1027"
+ ry=".76286"
+ fill="#ea686c"
+ id="rect2"
+ style="fill:#eae668;fill-opacity:1" />
+ </g>
+</svg>
diff --git a/editor/icons/icon_key_bezier.svg b/editor/icons/icon_key_bezier.svg
new file mode 100644
index 0000000000..62af6fdb34
--- /dev/null
+++ b/editor/icons/icon_key_bezier.svg
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="8"
+ height="8"
+ version="1.1"
+ viewBox="0 0 8 8"
+ id="svg6"
+ sodipodi:docname="icon_key_bezier.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs10" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1852"
+ inkscape:window-height="781"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="29.5"
+ inkscape:cx="-17.152542"
+ inkscape:cy="3.4149032"
+ inkscape:window-x="67"
+ inkscape:window-y="27"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="g4" />
+ <g
+ transform="translate(0 -1044.4)"
+ id="g4">
+ <rect
+ transform="rotate(-45)"
+ x="-741.53"
+ y="741.08"
+ width="6.1027"
+ height="6.1027"
+ ry=".76286"
+ fill="#ea686c"
+ id="rect2"
+ style="fill:#5792f6;fill-opacity:1" />
+ </g>
+</svg>
diff --git a/editor/icons/icon_key_bezier_handle.svg b/editor/icons/icon_key_bezier_handle.svg
new file mode 100644
index 0000000000..d7b22d0905
--- /dev/null
+++ b/editor/icons/icon_key_bezier_handle.svg
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="8"
+ height="8"
+ version="1.1"
+ viewBox="0 0 8 8"
+ id="svg6"
+ sodipodi:docname="icon_key_bezier_handle.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs10" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1853"
+ inkscape:window-height="1016"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="59"
+ inkscape:cx="2.0952442"
+ inkscape:cy="4.6061633"
+ inkscape:window-x="67"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="g4" />
+ <g
+ transform="translate(0 -1044.4)"
+ id="g4">
+ <path
+ style="fill:#e0e0e0;fill-opacity:1"
+ d="M 3.9960938 -0.037109375 C 3.8010931 -0.037109375 3.6064535 0.038077731 3.4570312 0.1875 L 0.22070312 3.4238281 C -0.078134343 3.7226656 -0.078141414 4.2050617 0.22070312 4.5039062 L 3.4570312 7.7402344 C 3.7558687 8.0390718 4.2382719 8.0390718 4.5371094 7.7402344 L 7.7734375 4.5039062 C 8.072282 4.2050617 8.072275 3.7226656 7.7734375 3.4238281 L 4.5371094 0.1875 C 4.3876871 0.038077731 4.1910944 -0.037109375 3.9960938 -0.037109375 z M 4.0253906 0.81445312 C 4.1770098 0.81445312 4.3291322 0.87241756 4.4453125 0.98828125 L 6.9609375 3.4960938 C 7.193298 3.7278211 7.193298 4.102257 6.9609375 4.3339844 L 4.4453125 6.84375 C 4.212952 7.0754774 3.8378293 7.0754774 3.6054688 6.84375 L 1.0898438 4.3339844 C 0.85748323 4.102257 0.85748323 3.7278211 1.0898438 3.4960938 L 3.6054688 0.98828125 C 3.721649 0.87241756 3.8737714 0.81445312 4.0253906 0.81445312 z "
+ transform="translate(0,1044.4)"
+ id="rect2" />
+ </g>
+</svg>
diff --git a/editor/icons/icon_key_bezier_point.svg b/editor/icons/icon_key_bezier_point.svg
new file mode 100644
index 0000000000..aa33063c95
--- /dev/null
+++ b/editor/icons/icon_key_bezier_point.svg
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="8"
+ height="8"
+ version="1.1"
+ viewBox="0 0 8 8"
+ id="svg6"
+ sodipodi:docname="icon_key_bezier_point.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs10" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="836"
+ inkscape:window-height="480"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="29.5"
+ inkscape:cx="4"
+ inkscape:cy="4"
+ inkscape:window-x="67"
+ inkscape:window-y="27"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg6" />
+ <g
+ transform="translate(0 -1044.4)"
+ id="g4">
+ <rect
+ transform="rotate(-45)"
+ x="-741.53"
+ y="741.08"
+ width="6.1027"
+ height="6.1027"
+ ry=".76286"
+ fill="#e0e0e0"
+ id="rect2" />
+ </g>
+</svg>
diff --git a/editor/icons/icon_key_bezier_selected.svg b/editor/icons/icon_key_bezier_selected.svg
new file mode 100644
index 0000000000..e3f967707a
--- /dev/null
+++ b/editor/icons/icon_key_bezier_selected.svg
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="8"
+ height="8"
+ version="1.1"
+ viewBox="0 0 8 8"
+ id="svg6"
+ sodipodi:docname="icon_key_bezier_selected.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs10" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="836"
+ inkscape:window-height="480"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="29.5"
+ inkscape:cx="4"
+ inkscape:cy="4"
+ inkscape:window-x="67"
+ inkscape:window-y="27"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg6" />
+ <g
+ transform="translate(0 -1044.4)"
+ id="g4">
+ <rect
+ transform="rotate(-45)"
+ x="-741.53"
+ y="741.08"
+ width="6.1027"
+ height="6.1027"
+ ry=".76286"
+ fill="#84c2ff"
+ id="rect2" />
+ </g>
+</svg>
diff --git a/editor/icons/icon_key_call.svg b/editor/icons/icon_key_call.svg
index 7fcc65801a..e702898288 100644
--- a/editor/icons/icon_key_call.svg
+++ b/editor/icons/icon_key_call.svg
@@ -1,5 +1,64 @@
-<svg width="8" height="8" version="1.1" viewBox="0 0 8 8" xmlns="http://www.w3.org/2000/svg">
-<g transform="translate(0 -1044.4)">
-<rect transform="rotate(-45)" x="-741.53" y="741.08" width="6.1027" height="6.1027" ry=".76286" fill="#adf18f"/>
-</g>
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="8"
+ height="8"
+ version="1.1"
+ viewBox="0 0 8 8"
+ id="svg6"
+ sodipodi:docname="icon_key_call.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs10" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="836"
+ inkscape:window-height="480"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="29.5"
+ inkscape:cx="4"
+ inkscape:cy="4"
+ inkscape:window-x="67"
+ inkscape:window-y="27"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="g4" />
+ <g
+ transform="translate(0 -1044.4)"
+ id="g4">
+ <rect
+ transform="rotate(-45)"
+ x="-741.53"
+ y="741.08"
+ width="6.1027"
+ height="6.1027"
+ ry=".76286"
+ fill="#adf18f"
+ id="rect2"
+ style="fill:#66f376;fill-opacity:1" />
+ </g>
</svg>
diff --git a/editor/icons/icon_key_selected.svg b/editor/icons/icon_key_selected.svg
index c73d31981d..2842fd93eb 100644
--- a/editor/icons/icon_key_selected.svg
+++ b/editor/icons/icon_key_selected.svg
@@ -1,5 +1,76 @@
-<svg width="8" height="8" version="1.1" viewBox="0 0 8 8" xmlns="http://www.w3.org/2000/svg">
-<g transform="translate(0 -1044.4)">
-<rect transform="rotate(-45)" x="-741.53" y="741.08" width="6.1027" height="6.1027" ry=".76286" fill="#84c2ff"/>
-</g>
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="8"
+ height="8"
+ version="1.1"
+ viewBox="0 0 8 8"
+ id="svg6"
+ sodipodi:docname="icon_key_selected.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs10" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1568"
+ inkscape:window-height="767"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="41.7193"
+ inkscape:cx="-0.48848775"
+ inkscape:cy="3.5639274"
+ inkscape:window-x="67"
+ inkscape:window-y="27"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="g4-3" />
+ <g
+ transform="translate(0 -1044.4)"
+ id="g4">
+ <rect
+ transform="rotate(-45)"
+ x="-741.53"
+ y="741.08"
+ width="6.1027"
+ height="6.1027"
+ ry=".76286"
+ fill="#84c2ff"
+ id="rect2" />
+ </g>
+ <g
+ transform="translate(0,-1044.4)"
+ id="g4-3">
+ <rect
+ style="fill:#003e7a;fill-opacity:1;stroke-width:0.56281364"
+ transform="matrix(0.71728847,-0.69677633,0.71728847,0.69677633,0,0)"
+ x="-751.20953"
+ y="753.42743"
+ width="3.4346831"
+ height="3.4346831"
+ ry="0.42934799"
+ id="rect2-6" />
+ </g>
</svg>
diff --git a/editor/icons/icon_key_xform.svg b/editor/icons/icon_key_xform.svg
index 7b73715771..fd22b67f52 100644
--- a/editor/icons/icon_key_xform.svg
+++ b/editor/icons/icon_key_xform.svg
@@ -1,5 +1,64 @@
-<svg width="8" height="8" version="1.1" viewBox="0 0 8 8" xmlns="http://www.w3.org/2000/svg">
-<g transform="translate(0 -1044.4)">
-<rect transform="rotate(-45)" x="-741.53" y="741.08" width="6.1027" height="6.1027" ry=".76286" fill="#ea686c"/>
-</g>
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="8"
+ height="8"
+ version="1.1"
+ viewBox="0 0 8 8"
+ id="svg6"
+ sodipodi:docname="icon_key_xform.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs10" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="836"
+ inkscape:window-height="480"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="29.5"
+ inkscape:cx="4"
+ inkscape:cy="4"
+ inkscape:window-x="67"
+ inkscape:window-y="27"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="g4" />
+ <g
+ transform="translate(0 -1044.4)"
+ id="g4">
+ <rect
+ transform="rotate(-45)"
+ x="-741.53"
+ y="741.08"
+ width="6.1027"
+ height="6.1027"
+ ry=".76286"
+ fill="#ea686c"
+ id="rect2"
+ style="fill:#ea9568;fill-opacity:1" />
+ </g>
</svg>
diff --git a/editor/icons/icon_time.svg b/editor/icons/icon_time.svg
new file mode 100644
index 0000000000..d50c9570b3
--- /dev/null
+++ b/editor/icons/icon_time.svg
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16"
+ height="16"
+ version="1.1"
+ viewBox="0 0 16 16"
+ id="svg6"
+ sodipodi:docname="icon_time.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs10" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="836"
+ inkscape:window-height="480"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="7.375"
+ inkscape:cx="4.4999435"
+ inkscape:cy="13.04848"
+ inkscape:window-x="744"
+ inkscape:window-y="280"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="g4" />
+ <g
+ transform="translate(0 -1036.4)"
+ id="g4">
+ <g
+ id="g8"
+ style="fill:#e0e0e0;fill-opacity:1"
+ transform="matrix(0.0279396,0,0,0.02755726,0.91401567,1037.1343)">
+ <g
+ id="g6"
+ style="fill:#e0e0e0;fill-opacity:1">
+ <path
+ d="M 276.193,58.507 V 40.389 h 14.578 c 11.153,0 20.194,-9.042 20.194,-20.194 C 310.965,9.043 301.923,0 290.771,0 h -69.544 c -11.153,0 -20.194,9.042 -20.194,20.194 0,11.152 9.042,20.194 20.194,20.194 h 14.578 V 58.506 C 119.952,68.76 28.799,166.327 28.799,284.799 28.799,410.078 130.721,512 256,512 381.279,512 483.201,410.078 483.201,284.799 483.2,166.327 392.046,68.76 276.193,58.507 Z m 0,412.009 v -20.124 c 0,-11.153 -9.042,-20.194 -20.194,-20.194 -11.153,0 -20.194,9.042 -20.194,20.194 v 20.124 C 148.895,461.131 79.668,391.902 70.283,304.994 h 20.124 c 11.153,0 20.194,-9.042 20.194,-20.194 0,-11.152 -9.042,-20.194 -20.194,-20.194 H 70.282 c 9.385,-86.91 78.614,-156.137 165.522,-165.523 v 20.124 c 0,11.153 9.042,20.194 20.194,20.194 11.153,0 20.194,-9.042 20.194,-20.194 V 99.081 c 86.91,9.385 156.137,78.614 165.522,165.523 H 421.59 c -11.153,0 -20.194,9.042 -20.194,20.194 0,11.152 9.042,20.194 20.194,20.194 h 20.126 c -9.385,86.911 -78.613,156.14 -165.523,165.524 z"
+ id="path2"
+ style="fill:#e0e0e0;fill-opacity:1"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 317.248,194.99 -58.179,58.18 c -1.011,-0.097 -2.034,-0.151 -3.071,-0.151 -17.552,0 -31.779,14.229 -31.779,31.779 0,17.552 14.228,31.779 31.779,31.779 17.551,0 31.779,-14.229 31.779,-31.779 0,-1.037 -0.054,-2.06 -0.151,-3.07 l 58.178,-58.18 c 7.887,-7.885 7.887,-20.672 0,-28.559 -7.882,-7.886 -20.669,-7.886 -28.556,0.001 z"
+ id="path4"
+ style="fill:#e0e0e0;fill-opacity:1"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/editor/icons/icon_track_capture.svg b/editor/icons/icon_track_capture.svg
new file mode 100644
index 0000000000..da6a662746
--- /dev/null
+++ b/editor/icons/icon_track_capture.svg
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16"
+ height="8"
+ version="1.1"
+ viewBox="0 0 16 8"
+ id="svg6"
+ sodipodi:docname="icon_track_capture.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs10" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1350"
+ inkscape:window-height="593"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="27.577164"
+ inkscape:cx="8.347146"
+ inkscape:cy="4.4012076"
+ inkscape:window-x="67"
+ inkscape:window-y="27"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg6" />
+ <path
+ style="fill:#e0e0e0;fill-opacity:1"
+ d="m 2.1665128,0.99764963 c -0.422625,0 -0.763672,0.34104737 -0.763672,0.76367187 v 4.5742187 c 0,0.4226242 0.341047,0.7617192 0.763672,0.7617192 h 4.472656 c 0.422625,0 0.763672,-0.339095 0.763672,-0.7617192 V 5.347259 h -3.300781 c -0.1662,0 -0.298828,-0.3390943 -0.298828,-0.7617188 V 3.3609308 c 0,-0.4226244 0.132628,-0.7636718 0.298828,-0.7636718 h 3.300781 V 1.7613215 c 0,-0.4226245 -0.341047,-0.76367187 -0.763672,-0.76367187 z"
+ id="rect1389"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#e0e0e0;fill-opacity:1;stroke:#e0e0e0;stroke-width:0.80299997;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 9.1827441,4.7953408 C 9.6993662,3.7537783 10.278269,2.5835979 10.469195,2.1949398 l 0.347137,-0.7066511 0.679654,0.00665 0.679654,0.00665 0.956945,2.3125 c 0.526319,1.271875 1.007254,2.4334375 1.068744,2.5812497 l 0.1118,0.26875 H 13.715914 13.1187 L 12.785851,6.0203387 12.453002,5.3765887 H 11.319176 10.185351 L 9.8066761,6.032702 9.4280014,6.6888154 l -0.5922856,1.37e-4 -0.592285,1.36e-4 z m 3.1779349,-0.369483 c 0.0042,-0.00346 -0.233487,-0.4884588 -0.528245,-1.0777779 l -0.535922,-1.0714891 -0.03691,0.0875 c -0.0203,0.048125 -0.183516,0.425 -0.362699,0.8375 -0.179182,0.4125 -0.355738,0.85125 -0.392346,0.975 -0.03661,0.12375 -0.07127,0.2390723 -0.07703,0.2562715 -0.0083,0.024853 0.188215,0.027989 0.957503,0.015278 0.532385,-0.0088 0.971429,-0.018823 0.975651,-0.022283 z"
+ id="path1424"
+ inkscape:connector-curvature="0" />
+</svg>
diff --git a/editor/inspector_dock.cpp b/editor/inspector_dock.cpp
index 4159a3658e..6c7843273e 100644
--- a/editor/inspector_dock.cpp
+++ b/editor/inspector_dock.cpp
@@ -292,14 +292,14 @@ void InspectorDock::_menu_expandall() {
}
void InspectorDock::_property_keyed(const String &p_keyed, const Variant &p_value, bool p_advance) {
- AnimationPlayerEditor::singleton->get_key_editor()->insert_value_key(p_keyed, p_value, p_advance);
+ AnimationPlayerEditor::singleton->get_track_editor()->insert_value_key(p_keyed, p_value, p_advance);
}
void InspectorDock::_transform_keyed(Object *sp, const String &p_sub, const Transform &p_key) {
Spatial *s = Object::cast_to<Spatial>(sp);
if (!s)
return;
- AnimationPlayerEditor::singleton->get_key_editor()->insert_transform_key(s, p_sub, p_key);
+ AnimationPlayerEditor::singleton->get_track_editor()->insert_transform_key(s, p_sub, p_key);
}
void InspectorDock::_warning_pressed() {
@@ -435,10 +435,14 @@ void InspectorDock::update(Object *p_object) {
}
}
+void InspectorDock::go_back() {
+ _edit_back();
+}
+
void InspectorDock::update_keying() {
bool valid = false;
- if (AnimationPlayerEditor::singleton->get_key_editor()->has_keying()) {
+ if (AnimationPlayerEditor::singleton->get_track_editor()->has_keying()) {
EditorHistory *editor_history = EditorNode::get_singleton()->get_editor_history();
if (editor_history->get_path_size() >= 1) {
diff --git a/editor/inspector_dock.h b/editor/inspector_dock.h
index 688c8beed7..f347056158 100644
--- a/editor/inspector_dock.h
+++ b/editor/inspector_dock.h
@@ -31,7 +31,7 @@
#ifndef INSPECTOR_DOCK_H
#define INSPECTOR_DOCK_H
-#include "editor/animation_editor.h"
+#include "editor/animation_track_editor.h"
#include "editor/connections_dialog.h"
#include "editor/create_dialog.h"
#include "editor/editor_data.h"
@@ -121,6 +121,7 @@ protected:
static void _bind_methods();
public:
+ void go_back();
void update_keying();
void edit_resource(const Ref<Resource> &p_resource);
void open_resource(const String &p_type);
diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp
index 23c5e36a92..536d5c3208 100644
--- a/editor/plugins/animation_player_editor_plugin.cpp
+++ b/editor/plugins/animation_player_editor_plugin.cpp
@@ -30,7 +30,7 @@
#include "animation_player_editor_plugin.h"
-#include "editor/animation_editor.h"
+#include "editor/animation_track_editor.h"
#include "editor/editor_settings.h"
#include "io/resource_loader.h"
#include "io/resource_saver.h"
@@ -50,9 +50,9 @@ void AnimationPlayerEditor::_node_removed(Node *p_node) {
set_process(false);
- key_editor->set_animation(Ref<Animation>());
- key_editor->set_root(NULL);
- key_editor->show_select_node_warning(true);
+ track_editor->set_animation(Ref<Animation>());
+ track_editor->set_root(NULL);
+ track_editor->show_select_node_warning(true);
_update_player();
//editor->animation_editor_make_visible(false);
}
@@ -84,7 +84,7 @@ void AnimationPlayerEditor::_notification(int p_what) {
}
}
frame->set_value(player->get_current_animation_position());
- key_editor->set_anim_pos(player->get_current_animation_position());
+ track_editor->set_anim_pos(player->get_current_animation_position());
EditorNode::get_singleton()->get_inspector()->refresh();
} else if (last_active) {
@@ -101,8 +101,6 @@ void AnimationPlayerEditor::_notification(int p_what) {
case NOTIFICATION_ENTER_TREE: {
- save_anim->get_popup()->connect("id_pressed", this, "_animation_save_menu");
-
tool_anim->get_popup()->connect("id_pressed", this, "_animation_tool_menu");
onion_skinning->get_popup()->connect("id_pressed", this, "_onion_skinning_menu");
@@ -121,16 +119,8 @@ void AnimationPlayerEditor::_notification(int p_what) {
case NOTIFICATION_THEME_CHANGED: {
- add_anim->set_icon(get_icon("New", "EditorIcons"));
- rename_anim->set_icon(get_icon("Rename", "EditorIcons"));
- duplicate_anim->set_icon(get_icon("Duplicate", "EditorIcons"));
autoplay->set_icon(get_icon("AutoPlay", "EditorIcons"));
- load_anim->set_icon(get_icon("Folder", "EditorIcons"));
- save_anim->set_icon(get_icon("Save", "EditorIcons"));
-
- remove_anim->set_icon(get_icon("Remove", "EditorIcons"));
- blend_anim->set_icon(get_icon("Blend", "EditorIcons"));
play->set_icon(get_icon("PlayStart", "EditorIcons"));
play_from->set_icon(get_icon("Play", "EditorIcons"));
play_bw->set_icon(get_icon("PlayStartBackwards", "EditorIcons"));
@@ -138,11 +128,26 @@ void AnimationPlayerEditor::_notification(int p_what) {
autoplay_icon = get_icon("AutoPlay", "EditorIcons");
stop->set_icon(get_icon("Stop", "EditorIcons"));
- resource_edit_anim->set_icon(get_icon("EditResource", "EditorIcons"));
+
pin->set_icon(get_icon("Pin", "EditorIcons"));
- tool_anim->set_icon(get_icon("Tools", "EditorIcons"));
onion_skinning->set_icon(get_icon("Onion", "EditorIcons"));
+ tool_anim->add_style_override("normal", get_stylebox("normal", "Button"));
+ track_editor->get_edit_menu()->add_style_override("normal", get_stylebox("normal", "Button"));
+
+#define ITEM_ICON(m_item, m_icon) tool_anim->get_popup()->set_item_icon(tool_anim->get_popup()->get_item_index(m_item), get_icon(m_icon, "EditorIcons"))
+
+ ITEM_ICON(TOOL_NEW_ANIM, "New");
+ ITEM_ICON(TOOL_LOAD_ANIM, "Load");
+ ITEM_ICON(TOOL_SAVE_ANIM, "Save");
+ ITEM_ICON(TOOL_SAVE_AS_ANIM, "Save");
+ ITEM_ICON(TOOL_DUPLICATE_ANIM, "Duplicate");
+ ITEM_ICON(TOOL_RENAME_ANIM, "Rename");
+ ITEM_ICON(TOOL_EDIT_TRANSITIONS, "Blend");
+ ITEM_ICON(TOOL_REMOVE_ANIM, "Remove");
+ //ITEM_ICON(TOOL_COPY_ANIM, "Copy");
+ //ITEM_ICON(TOOL_PASTE_ANIM, "Paste");
+
} break;
}
}
@@ -304,10 +309,10 @@ void AnimationPlayerEditor::_animation_selected(int p_which) {
Ref<Animation> anim = player->get_animation(current);
{
- key_editor->set_animation(anim);
+ track_editor->set_animation(anim);
Node *root = player->get_node(player->get_root());
if (root) {
- key_editor->set_root(root);
+ track_editor->set_root(root);
}
}
frame->set_max(anim->get_length());
@@ -317,8 +322,8 @@ void AnimationPlayerEditor::_animation_selected(int p_which) {
frame->set_step(0.00001);
} else {
- key_editor->set_animation(Ref<Animation>());
- key_editor->set_root(NULL);
+ track_editor->set_animation(Ref<Animation>());
+ track_editor->set_root(NULL);
}
autoplay->set_pressed(current == player->get_autoplay());
@@ -704,16 +709,16 @@ void AnimationPlayerEditor::_animation_edit() {
if (animation->get_item_count()) {
String current = animation->get_item_text(animation->get_selected());
Ref<Animation> anim = player->get_animation(current);
- key_editor->set_animation(anim);
+ track_editor->set_animation(anim);
Node *root = player->get_node(player->get_root());
if (root) {
- key_editor->set_root(root);
+ track_editor->set_root(root);
}
} else {
- key_editor->set_animation(Ref<Animation>());
- key_editor->set_root(NULL);
+ track_editor->set_animation(Ref<Animation>());
+ track_editor->set_root(NULL);
}
}
void AnimationPlayerEditor::_dialog_action(String p_file) {
@@ -810,8 +815,16 @@ void AnimationPlayerEditor::_update_player() {
animation->clear();
- add_anim->set_disabled(player == NULL);
- load_anim->set_disabled(player == NULL);
+#define ITEM_DISABLED(m_item, m_disabled) tool_anim->get_popup()->set_item_disabled(tool_anim->get_popup()->get_item_index(m_item), m_disabled)
+
+ ITEM_DISABLED(TOOL_SAVE_ANIM, animlist.size() == 0);
+ ITEM_DISABLED(TOOL_SAVE_AS_ANIM, animlist.size() == 0);
+ ITEM_DISABLED(TOOL_DUPLICATE_ANIM, animlist.size() == 0);
+ ITEM_DISABLED(TOOL_RENAME_ANIM, animlist.size() == 0);
+ ITEM_DISABLED(TOOL_EDIT_TRANSITIONS, animlist.size() == 0);
+ ITEM_DISABLED(TOOL_COPY_ANIM, animlist.size() == 0);
+ ITEM_DISABLED(TOOL_REMOVE_ANIM, animlist.size() == 0);
+
stop->set_disabled(animlist.size() == 0);
play->set_disabled(animlist.size() == 0);
play_bw->set_disabled(animlist.size() == 0);
@@ -820,12 +833,6 @@ void AnimationPlayerEditor::_update_player() {
frame->set_editable(animlist.size() != 0);
animation->set_disabled(animlist.size() == 0);
autoplay->set_disabled(animlist.size() == 0);
- duplicate_anim->set_disabled(animlist.size() == 0);
- rename_anim->set_disabled(animlist.size() == 0);
- blend_anim->set_disabled(animlist.size() == 0);
- remove_anim->set_disabled(animlist.size() == 0);
- resource_edit_anim->set_disabled(animlist.size() == 0);
- save_anim->set_disabled(animlist.size() == 0);
tool_anim->set_disabled(player == NULL);
onion_skinning->set_disabled(player == NULL);
pin->set_disabled(player == NULL);
@@ -863,10 +870,10 @@ void AnimationPlayerEditor::_update_player() {
if (animation->get_item_count()) {
String current = animation->get_item_text(animation->get_selected());
Ref<Animation> anim = player->get_animation(current);
- key_editor->set_animation(anim);
+ track_editor->set_animation(anim);
Node *root = player->get_node(player->get_root());
if (root) {
- key_editor->set_root(root);
+ track_editor->set_root(root);
}
}
@@ -884,9 +891,9 @@ void AnimationPlayerEditor::edit(AnimationPlayer *p_player) {
if (player) {
_update_player();
- key_editor->show_select_node_warning(false);
+ track_editor->show_select_node_warning(false);
} else {
- key_editor->show_select_node_warning(true);
+ track_editor->show_select_node_warning(true);
//hide();
}
@@ -1024,7 +1031,7 @@ void AnimationPlayerEditor::_seek_value_changed(float p_value, bool p_set) {
player->seek(pos, true);
}
- key_editor->set_anim_pos(pos);
+ track_editor->set_anim_pos(pos);
updating = true;
};
@@ -1084,16 +1091,55 @@ void AnimationPlayerEditor::_hide_anim_editors() {
hide();
set_process(false);
- key_editor->set_animation(Ref<Animation>());
- key_editor->set_root(NULL);
- key_editor->show_select_node_warning(true);
+ track_editor->set_animation(Ref<Animation>());
+ track_editor->set_root(NULL);
+ track_editor->show_select_node_warning(true);
//editor->animation_editor_make_visible(false);
}
+void AnimationPlayerEditor::_animation_about_to_show_menu() {
+}
+
void AnimationPlayerEditor::_animation_tool_menu(int p_option) {
+ String current = animation->get_item_text(animation->get_selected());
+ Ref<Animation> anim;
+ if (current != "") {
+ anim = player->get_animation(current);
+ }
+
switch (p_option) {
+ case TOOL_NEW_ANIM: {
+ _animation_new();
+ } break;
+
+ case TOOL_LOAD_ANIM: {
+ _animation_load();
+ break;
+ } break;
+ case TOOL_SAVE_ANIM: {
+ if (anim.is_valid()) {
+ _animation_save(anim);
+ }
+ } break;
+ case TOOL_SAVE_AS_ANIM: {
+ if (anim.is_valid()) {
+ _animation_save_as(anim);
+ }
+ } break;
+ case TOOL_DUPLICATE_ANIM: {
+ _animation_duplicate();
+ } break;
+ case TOOL_RENAME_ANIM: {
+ _animation_rename();
+ } break;
+ case TOOL_EDIT_TRANSITIONS: {
+ _animation_blend();
+ } break;
+ case TOOL_REMOVE_ANIM: {
+ _animation_remove();
+ } break;
case TOOL_COPY_ANIM: {
if (!animation->get_item_count()) {
@@ -1156,23 +1202,6 @@ void AnimationPlayerEditor::_animation_tool_menu(int p_option) {
}
}
-void AnimationPlayerEditor::_animation_save_menu(int p_option) {
-
- String current = animation->get_item_text(animation->get_selected());
- if (current != "") {
- Ref<Animation> anim = player->get_animation(current);
-
- switch (p_option) {
- case ANIM_SAVE:
- _animation_save(anim);
- break;
- case ANIM_SAVE_AS:
- _animation_save_as(anim);
- break;
- }
- }
-}
-
void AnimationPlayerEditor::_onion_skinning_menu(int p_option) {
PopupMenu *menu = onion_skinning->get_popup();
@@ -1431,7 +1460,7 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() {
float pos = cpos + step_off * anim->get_step();
- bool valid = anim->has_loop() || pos >= 0 && pos <= anim->get_length();
+ bool valid = anim->has_loop() || (pos >= 0 && pos <= anim->get_length());
onion.captures_valid[cidx] = valid;
if (valid) {
player->seek(pos, true);
@@ -1494,6 +1523,10 @@ void AnimationPlayerEditor::_stop_onion_skinning() {
}
}
+void AnimationPlayerEditor::_pin_pressed() {
+ EditorNode::get_singleton()->get_scene_tree_dock()->get_tree_editor()->update_tree();
+}
+
void AnimationPlayerEditor::_bind_methods() {
ClassDB::bind_method(D_METHOD("_gui_input"), &AnimationPlayerEditor::_gui_input);
@@ -1532,11 +1565,13 @@ void AnimationPlayerEditor::_bind_methods() {
ClassDB::bind_method(D_METHOD("_blend_editor_next_changed"), &AnimationPlayerEditor::_blend_editor_next_changed);
ClassDB::bind_method(D_METHOD("_unhandled_key_input"), &AnimationPlayerEditor::_unhandled_key_input);
ClassDB::bind_method(D_METHOD("_animation_tool_menu"), &AnimationPlayerEditor::_animation_tool_menu);
- ClassDB::bind_method(D_METHOD("_animation_save_menu"), &AnimationPlayerEditor::_animation_save_menu);
+
ClassDB::bind_method(D_METHOD("_onion_skinning_menu"), &AnimationPlayerEditor::_onion_skinning_menu);
ClassDB::bind_method(D_METHOD("_editor_visibility_changed"), &AnimationPlayerEditor::_editor_visibility_changed);
ClassDB::bind_method(D_METHOD("_prepare_onion_layers_1"), &AnimationPlayerEditor::_prepare_onion_layers_1);
ClassDB::bind_method(D_METHOD("_prepare_onion_layers_2"), &AnimationPlayerEditor::_prepare_onion_layers_2);
+
+ ClassDB::bind_method(D_METHOD("_pin_pressed"), &AnimationPlayerEditor::_pin_pressed);
}
AnimationPlayerEditor *AnimationPlayerEditor::singleton = NULL;
@@ -1606,26 +1641,6 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay
scale->set_tooltip(TTR("Scale animation playback globally for the node."));
scale->hide();
- add_anim = memnew(ToolButton);
- ED_SHORTCUT("animation_player_editor/add_animation", TTR("Create new animation in player."));
- add_anim->set_shortcut(ED_GET_SHORTCUT("animation_player_editor/add_animation"));
- add_anim->set_tooltip(TTR("Create new animation in player."));
-
- hb->add_child(add_anim);
-
- load_anim = memnew(ToolButton);
- ED_SHORTCUT("animation_player_editor/load_from_disk", TTR("Load animation from disk."));
- add_anim->set_shortcut(ED_GET_SHORTCUT("animation_player_editor/load_from_disk"));
- load_anim->set_tooltip(TTR("Load an animation from disk."));
- hb->add_child(load_anim);
-
- save_anim = memnew(MenuButton);
- save_anim->set_tooltip(TTR("Save the current animation."));
- save_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/save", TTR("Save")), ANIM_SAVE);
- save_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/save_as", TTR("Save As")), ANIM_SAVE_AS);
- save_anim->set_focus_mode(Control::FOCUS_NONE);
- hb->add_child(save_anim);
-
accept = memnew(AcceptDialog);
add_child(accept);
accept->connect("confirmed", this, "_menu_confirm_current");
@@ -1634,23 +1649,27 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay
add_child(delete_dialog);
delete_dialog->connect("confirmed", this, "_animation_remove_confirmed");
- duplicate_anim = memnew(ToolButton);
- hb->add_child(duplicate_anim);
- ED_SHORTCUT("animation_player_editor/duplicate_animation", TTR("Duplicate Animation"));
- duplicate_anim->set_shortcut(ED_GET_SHORTCUT("animation_player_editor/duplicate_animation"));
- duplicate_anim->set_tooltip(TTR("Duplicate Animation"));
-
- rename_anim = memnew(ToolButton);
- hb->add_child(rename_anim);
- ED_SHORTCUT("animation_player_editor/rename_animation", TTR("Rename Animation"));
- rename_anim->set_shortcut(ED_GET_SHORTCUT("animation_player_editor/rename_animation"));
- rename_anim->set_tooltip(TTR("Rename Animation"));
-
- remove_anim = memnew(ToolButton);
- hb->add_child(remove_anim);
- ED_SHORTCUT("animation_player_editor/remove_animation", TTR("Remove Animation"));
- remove_anim->set_shortcut(ED_GET_SHORTCUT("animation_player_editor/remove_animation"));
- remove_anim->set_tooltip(TTR("Remove Animation"));
+ tool_anim = memnew(MenuButton);
+ tool_anim->set_flat(false);
+ //tool_anim->set_flat(false);
+ tool_anim->set_tooltip(TTR("Animation Tools"));
+ tool_anim->set_text("Animation");
+ tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/new_animation", TTR("New...")), TOOL_NEW_ANIM);
+ tool_anim->get_popup()->add_separator();
+ tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/open_animation", TTR("Load...")), TOOL_LOAD_ANIM);
+ tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/save_animation", TTR("Save")), TOOL_SAVE_ANIM);
+ tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/save_as_animation", TTR("Save As..")), TOOL_SAVE_AS_ANIM);
+ tool_anim->get_popup()->add_separator();
+ tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/copy_animation", TTR("Copy")), TOOL_COPY_ANIM);
+ tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/paste_animation", TTR("Paste")), TOOL_PASTE_ANIM);
+ tool_anim->get_popup()->add_separator();
+ tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/duplicate_animation", TTR("Duplicate")), TOOL_DUPLICATE_ANIM);
+ tool_anim->get_popup()->add_separator();
+ tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/rename_animation", TTR("Rename...")), TOOL_RENAME_ANIM);
+ tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/edit_transitions", TTR("Edit Transitions...")), TOOL_EDIT_TRANSITIONS);
+ tool_anim->get_popup()->add_separator();
+ tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/remove_animation", TTR("Remove")), TOOL_REMOVE_ANIM);
+ hb->add_child(tool_anim);
animation = memnew(OptionButton);
hb->add_child(animation);
@@ -1662,18 +1681,12 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay
hb->add_child(autoplay);
autoplay->set_tooltip(TTR("Autoplay on Load"));
- blend_anim = memnew(ToolButton);
- hb->add_child(blend_anim);
- blend_anim->set_tooltip(TTR("Edit Target Blend Times"));
-
- tool_anim = memnew(MenuButton);
- //tool_anim->set_flat(false);
- tool_anim->set_tooltip(TTR("Animation Tools"));
- tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/copy_animation", TTR("Copy Animation")), TOOL_COPY_ANIM);
- tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/paste_animation", TTR("Paste Animation")), TOOL_PASTE_ANIM);
//tool_anim->get_popup()->add_separator();
//tool_anim->get_popup()->add_item("Edit Anim Resource",TOOL_PASTE_ANIM);
- hb->add_child(tool_anim);
+
+ track_editor = memnew(AnimationTrackEditor);
+
+ hb->add_child(track_editor->get_edit_menu());
onion_skinning = memnew(MenuButton);
//onion_skinning->set_flat(false);
@@ -1702,10 +1715,7 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay
pin->set_toggle_mode(true);
pin->set_tooltip(TTR("Pin AnimationPlayer"));
hb->add_child(pin);
-
- resource_edit_anim = memnew(Button);
- hb->add_child(resource_edit_anim);
- resource_edit_anim->hide();
+ pin->connect("pressed", this, "_pin_pressed");
file = memnew(EditorFileDialog);
add_child(file);
@@ -1758,16 +1768,10 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay
play_bw_from->connect("pressed", this, "_play_bw_from_pressed");
stop->connect("pressed", this, "_stop_pressed");
//pause->connect("pressed", this,"_pause_pressed");
- add_anim->connect("pressed", this, "_animation_new");
- rename_anim->connect("pressed", this, "_animation_rename");
- load_anim->connect("pressed", this, "_animation_load");
- duplicate_anim->connect("pressed", this, "_animation_duplicate");
//frame->connect("text_entered", this,"_seek_frame_changed");
- blend_anim->connect("pressed", this, "_animation_blend");
- remove_anim->connect("pressed", this, "_animation_remove");
animation->connect("item_selected", this, "_animation_selected", Vector<Variant>(), true);
- resource_edit_anim->connect("pressed", this, "_animation_resource_edit");
+
file->connect("file_selected", this, "_dialog_action");
frame->connect("value_changed", this, "_seek_value_changed", Vector<Variant>(), true);
scale->connect("text_entered", this, "_scale_changed", Vector<Variant>(), true);
@@ -1777,18 +1781,17 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay
set_process_unhandled_key_input(true);
- key_editor = memnew(AnimationKeyEditor);
- add_child(key_editor);
- key_editor->set_v_size_flags(SIZE_EXPAND_FILL);
- key_editor->connect("timeline_changed", this, "_animation_key_editor_seek");
- key_editor->connect("animation_len_changed", this, "_animation_key_editor_anim_len_changed");
- key_editor->connect("animation_step_changed", this, "_animation_key_editor_anim_step_changed");
+ add_child(track_editor);
+ track_editor->set_v_size_flags(SIZE_EXPAND_FILL);
+ track_editor->connect("timeline_changed", this, "_animation_key_editor_seek");
+ track_editor->connect("animation_len_changed", this, "_animation_key_editor_anim_len_changed");
+ track_editor->connect("animation_step_changed", this, "_animation_key_editor_anim_step_changed");
_update_player();
// Onion skinning
- key_editor->connect("visibility_changed", this, "_editor_visibility_changed");
+ track_editor->connect("visibility_changed", this, "_editor_visibility_changed");
onion.enabled = false;
onion.past = true;
diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h
index a7b7c6c465..5ac7b99903 100644
--- a/editor/plugins/animation_player_editor_plugin.h
+++ b/editor/plugins/animation_player_editor_plugin.h
@@ -42,8 +42,9 @@
/**
@author Juan Linietsky <reduzio@gmail.com>
*/
-class AnimationKeyEditor;
+class AnimationTrackEditor;
class AnimationPlayerEditorPlugin;
+
class AnimationPlayerEditor : public VBoxContainer {
GDCLASS(AnimationPlayerEditor, VBoxContainer);
@@ -53,6 +54,14 @@ class AnimationPlayerEditor : public VBoxContainer {
AnimationPlayer *player;
enum {
+ TOOL_NEW_ANIM,
+ TOOL_LOAD_ANIM,
+ TOOL_SAVE_ANIM,
+ TOOL_SAVE_AS_ANIM,
+ TOOL_DUPLICATE_ANIM,
+ TOOL_RENAME_ANIM,
+ TOOL_EDIT_TRANSITIONS,
+ TOOL_REMOVE_ANIM,
TOOL_COPY_ANIM,
TOOL_PASTE_ANIM,
TOOL_EDIT_RESOURCE
@@ -72,6 +81,7 @@ class AnimationPlayerEditor : public VBoxContainer {
};
enum {
+ ANIM_OPEN,
ANIM_SAVE,
ANIM_SAVE_AS
};
@@ -89,16 +99,8 @@ class AnimationPlayerEditor : public VBoxContainer {
Button *play_bw_from;
//Button *pause;
- Button *add_anim;
Button *autoplay;
- Button *rename_anim;
- Button *duplicate_anim;
-
- Button *resource_edit_anim;
- Button *load_anim;
- MenuButton *save_anim;
- Button *blend_anim;
- Button *remove_anim;
+
MenuButton *tool_anim;
MenuButton *onion_skinning;
ToolButton *pin;
@@ -130,7 +132,7 @@ class AnimationPlayerEditor : public VBoxContainer {
bool updating;
bool updating_blends;
- AnimationKeyEditor *key_editor;
+ AnimationTrackEditor *track_editor;
// Onion skinning
struct {
@@ -207,8 +209,8 @@ class AnimationPlayerEditor : public VBoxContainer {
void _unhandled_key_input(const Ref<InputEvent> &p_ev);
void _animation_tool_menu(int p_option);
- void _animation_save_menu(int p_option);
void _onion_skinning_menu(int p_option);
+ void _animation_about_to_show_menu();
void _editor_visibility_changed();
bool _are_onion_layers_valid();
@@ -219,6 +221,8 @@ class AnimationPlayerEditor : public VBoxContainer {
void _start_onion_skinning();
void _stop_onion_skinning();
+ void _pin_pressed();
+
AnimationPlayerEditor();
~AnimationPlayerEditor();
@@ -232,7 +236,9 @@ public:
AnimationPlayer *get_player() const;
static AnimationPlayerEditor *singleton;
- AnimationKeyEditor *get_key_editor() { return key_editor; }
+ bool is_pinned() const { return pin->is_pressed(); }
+ void unpin() { pin->set_pressed(false); }
+ AnimationTrackEditor *get_track_editor() { return track_editor; }
Dictionary get_state() const;
void set_state(const Dictionary &p_state);
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index ad49ab86c9..13a9a84de1 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -30,7 +30,6 @@
#include "canvas_item_editor_plugin.h"
-#include "editor/animation_editor.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/plugins/animation_player_editor_plugin.h"
@@ -365,7 +364,7 @@ Object *CanvasItemEditor::_get_editor_data(Object *p_what) {
void CanvasItemEditor::_keying_changed() {
- if (AnimationPlayerEditor::singleton->get_key_editor()->is_visible_in_tree())
+ if (AnimationPlayerEditor::singleton->get_track_editor()->is_visible_in_tree())
animation_hb->show();
else
animation_hb->hide();
@@ -3080,7 +3079,7 @@ void CanvasItemEditor::_notification(int p_what) {
select_sb->set_default_margin(Margin(i), 4);
}
- AnimationPlayerEditor::singleton->get_key_editor()->connect("visibility_changed", this, "_keying_changed");
+ AnimationPlayerEditor::singleton->get_track_editor()->connect("visibility_changed", this, "_keying_changed");
_keying_changed();
get_tree()->connect("node_added", this, "_tree_changed", varray());
get_tree()->connect("node_removed", this, "_tree_changed", varray());
@@ -3692,11 +3691,11 @@ void CanvasItemEditor::_popup_callback(int p_op) {
Node2D *n2d = Object::cast_to<Node2D>(canvas_item);
if (key_pos)
- AnimationPlayerEditor::singleton->get_key_editor()->insert_node_value_key(n2d, "position", n2d->get_position(), existing);
+ AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(n2d, "position", n2d->get_position(), existing);
if (key_rot)
- AnimationPlayerEditor::singleton->get_key_editor()->insert_node_value_key(n2d, "rotation_degrees", Math::rad2deg(n2d->get_rotation()), existing);
+ AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(n2d, "rotation_degrees", Math::rad2deg(n2d->get_rotation()), existing);
if (key_scale)
- AnimationPlayerEditor::singleton->get_key_editor()->insert_node_value_key(n2d, "scale", n2d->get_scale(), existing);
+ AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(n2d, "scale", n2d->get_scale(), existing);
if (n2d->has_meta("_edit_bone_") && n2d->get_parent_item()) {
//look for an IK chain
@@ -3723,11 +3722,11 @@ void CanvasItemEditor::_popup_callback(int p_op) {
for (List<Node2D *>::Element *F = ik_chain.front(); F; F = F->next()) {
if (key_pos)
- AnimationPlayerEditor::singleton->get_key_editor()->insert_node_value_key(F->get(), "position", F->get()->get_position(), existing);
+ AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F->get(), "position", F->get()->get_position(), existing);
if (key_rot)
- AnimationPlayerEditor::singleton->get_key_editor()->insert_node_value_key(F->get(), "rotation_degrees", Math::rad2deg(F->get()->get_rotation()), existing);
+ AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F->get(), "rotation_degrees", Math::rad2deg(F->get()->get_rotation()), existing);
if (key_scale)
- AnimationPlayerEditor::singleton->get_key_editor()->insert_node_value_key(F->get(), "scale", F->get()->get_scale(), existing);
+ AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F->get(), "scale", F->get()->get_scale(), existing);
}
}
}
@@ -3737,11 +3736,11 @@ void CanvasItemEditor::_popup_callback(int p_op) {
Control *ctrl = Object::cast_to<Control>(canvas_item);
if (key_pos)
- AnimationPlayerEditor::singleton->get_key_editor()->insert_node_value_key(ctrl, "rect_position", ctrl->get_position(), existing);
+ AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl, "rect_position", ctrl->get_position(), existing);
if (key_rot)
- AnimationPlayerEditor::singleton->get_key_editor()->insert_node_value_key(ctrl, "rect_rotation", ctrl->get_rotation_degrees(), existing);
+ AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl, "rect_rotation", ctrl->get_rotation_degrees(), existing);
if (key_scale)
- AnimationPlayerEditor::singleton->get_key_editor()->insert_node_value_key(ctrl, "rect_size", ctrl->get_size(), existing);
+ AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl, "rect_size", ctrl->get_size(), existing);
}
}
@@ -3837,7 +3836,7 @@ void CanvasItemEditor::_popup_callback(int p_op) {
ctrl->set_position(Point2());
/*
if (key_scale)
- AnimationPlayerEditor::singleton->get_key_editor()->insert_node_value_key(ctrl,"rect/size",ctrl->get_size());
+ AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl,"rect/size",ctrl->get_size());
*/
}
}
diff --git a/editor/plugins/editor_preview_plugins.cpp b/editor/plugins/editor_preview_plugins.cpp
index d065a756b4..0d25b3685a 100644
--- a/editor/plugins/editor_preview_plugins.cpp
+++ b/editor/plugins/editor_preview_plugins.cpp
@@ -554,274 +554,92 @@ EditorScriptPreviewPlugin::EditorScriptPreviewPlugin() {
}
///////////////////////////////////////////////////////////////////
-// FIXME: Needs to be rewritten for AudioStream in Godot 3.0+
-#if 0
-bool EditorSamplePreviewPlugin::handles(const String& p_type) const {
+bool EditorAudioStreamPreviewPlugin::handles(const String &p_type) const {
- return ClassDB::is_parent_class(p_type,"Sample");
+ return ClassDB::is_parent_class(p_type, "AudioStream");
}
-Ref<Texture> EditorSamplePreviewPlugin::generate(const RES& p_from) {
-
- Ref<Sample> smp =p_from;
- ERR_FAIL_COND_V(smp.is_null(),Ref<Texture>());
+Ref<Texture> EditorAudioStreamPreviewPlugin::generate(const RES &p_from) {
+ Ref<AudioStream> stream = p_from;
+ ERR_FAIL_COND_V(stream.is_null(), Ref<Texture>());
int thumbnail_size = EditorSettings::get_singleton()->get("filesystem/file_dialog/thumbnail_size");
- thumbnail_size*=EDSCALE;
+ thumbnail_size *= EDSCALE;
PoolVector<uint8_t> img;
int w = thumbnail_size;
int h = thumbnail_size;
- img.resize(w*h*3);
+ img.resize(w * h * 3);
PoolVector<uint8_t>::Write imgdata = img.write();
- uint8_t * imgw = imgdata.ptr();
- PoolVector<uint8_t> data = smp->get_data();
- PoolVector<uint8_t>::Read sampledata = data.read();
- const uint8_t *sdata=sampledata.ptr();
+ uint8_t *imgw = imgdata.ptr();
- bool stereo = smp->is_stereo();
- bool _16=smp->get_format()==Sample::FORMAT_PCM16;
- int len = smp->get_length();
+ Ref<AudioStreamPlayback> playback = stream->instance_playback();
- if (len<1)
- return Ref<Texture>();
+ float len_s = stream->get_length();
+ if (len_s == 0) {
+ len_s = 60; //one minute audio if no length specified
+ }
+ int frame_length = AudioServer::get_singleton()->get_mix_rate() * len_s;
- if (smp->get_format()==Sample::FORMAT_IMA_ADPCM) {
-
- struct IMA_ADPCM_State {
-
- int16_t step_index;
- int32_t predictor;
- /* values at loop point */
- int16_t loop_step_index;
- int32_t loop_predictor;
- int32_t last_nibble;
- int32_t loop_pos;
- int32_t window_ofs;
- const uint8_t *ptr;
- } ima_adpcm;
-
- ima_adpcm.step_index=0;
- ima_adpcm.predictor=0;
- ima_adpcm.loop_step_index=0;
- ima_adpcm.loop_predictor=0;
- ima_adpcm.last_nibble=-1;
- ima_adpcm.loop_pos=0x7FFFFFFF;
- ima_adpcm.window_ofs=0;
- ima_adpcm.ptr=NULL;
-
-
- for(int i=0;i<w;i++) {
-
- float max[2]={-1e10,-1e10};
- float min[2]={1e10,1e10};
- int from = i*len/w;
- int to = (i+1)*len/w;
- if (to>=len)
- to=len-1;
-
- for(int j=from;j<to;j++) {
-
- while(j>ima_adpcm.last_nibble) {
-
- static const int16_t _ima_adpcm_step_table[89] = {
- 7, 8, 9, 10, 11, 12, 13, 14, 16, 17,
- 19, 21, 23, 25, 28, 31, 34, 37, 41, 45,
- 50, 55, 60, 66, 73, 80, 88, 97, 107, 118,
- 130, 143, 157, 173, 190, 209, 230, 253, 279, 307,
- 337, 371, 408, 449, 494, 544, 598, 658, 724, 796,
- 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066,
- 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358,
- 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
- 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
- };
-
- static const int8_t _ima_adpcm_index_table[16] = {
- -1, -1, -1, -1, 2, 4, 6, 8,
- -1, -1, -1, -1, 2, 4, 6, 8
- };
-
- int16_t nibble,diff,step;
-
- ima_adpcm.last_nibble++;
- const uint8_t *src_ptr=sdata;
-
- int ofs = ima_adpcm.last_nibble>>1;
-
- if (stereo)
- ofs*=2;
-
-
- nibble = (ima_adpcm.last_nibble&1)?
- (src_ptr[ofs]>>4):(src_ptr[ofs]&0xF);
- step=_ima_adpcm_step_table[ima_adpcm.step_index];
-
- ima_adpcm.step_index += _ima_adpcm_index_table[nibble];
- if (ima_adpcm.step_index<0)
- ima_adpcm.step_index=0;
- if (ima_adpcm.step_index>88)
- ima_adpcm.step_index=88;
-
- diff = step >> 3 ;
- if (nibble & 1)
- diff += step >> 2 ;
- if (nibble & 2)
- diff += step >> 1 ;
- if (nibble & 4)
- diff += step ;
- if (nibble & 8)
- diff = -diff ;
-
- ima_adpcm.predictor+=diff;
- if (ima_adpcm.predictor<-0x8000)
- ima_adpcm.predictor=-0x8000;
- else if (ima_adpcm.predictor>0x7FFF)
- ima_adpcm.predictor=0x7FFF;
-
-
- /* store loop if there */
- if (ima_adpcm.last_nibble==ima_adpcm.loop_pos) {
-
- ima_adpcm.loop_step_index = ima_adpcm.step_index;
- ima_adpcm.loop_predictor = ima_adpcm.predictor;
- }
+ Vector<AudioFrame> frames;
+ frames.resize(frame_length);
- }
+ playback->start();
+ playback->mix(frames.ptrw(), 1, frames.size());
+ playback->stop();
- float v=ima_adpcm.predictor/32767.0;
- if (v>max[0])
- max[0]=v;
- if (v<min[0])
- min[0]=v;
- }
- max[0]*=0.8;
- min[0]*=0.8;
-
- for(int j=0;j<h;j++) {
- float v = (j/(float)h) * 2.0 - 1.0;
- uint8_t* imgofs = &imgw[(uint64_t(j)*w+i)*3];
- if (v>min[0] && v<max[0]) {
- imgofs[0]=255;
- imgofs[1]=150;
- imgofs[2]=80;
- } else {
- imgofs[0]=0;
- imgofs[1]=0;
- imgofs[2]=0;
- }
- }
- }
- } else {
- for(int i=0;i<w;i++) {
- // i trust gcc will optimize this loop
- float max[2]={-1e10,-1e10};
- float min[2]={1e10,1e10};
- int c=stereo?2:1;
- int from = uint64_t(i)*len/w;
- int to = (uint64_t(i)+1)*len/w;
- if (to>=len)
- to=len-1;
-
- if (_16) {
- const int16_t*src =(const int16_t*)sdata;
-
- for(int j=0;j<c;j++) {
-
- for(int k=from;k<=to;k++) {
-
- float v = src[uint64_t(k)*c+j]/32768.0;
- if (v>max[j])
- max[j]=v;
- if (v<min[j])
- min[j]=v;
- }
+ for (int i = 0; i < w; i++) {
- }
- } else {
-
- const int8_t*src =(const int8_t*)sdata;
+ float max = -1000;
+ float min = 1000;
+ int from = uint64_t(i) * frame_length / w;
+ int to = uint64_t(i + 1) * frame_length / w;
+ to = MIN(to, frame_length);
+ from = MIN(from, frame_length - 1);
+ if (to == from) {
+ to = from + 1;
+ }
- for(int j=0;j<c;j++) {
+ for (int j = from; j < to; j++) {
- for(int k=from;k<=to;k++) {
+ max = MAX(max, frames[j].l);
+ max = MAX(max, frames[j].r);
- float v = src[uint64_t(k)*c+j]/128.0;
- if (v>max[j])
- max[j]=v;
- if (v<min[j])
- min[j]=v;
- }
+ min = MIN(min, frames[j].l);
+ min = MIN(min, frames[j].r);
+ }
- }
- }
+ int pfrom = CLAMP((min * 0.5 + 0.5) * h / 2, 0, h / 2) + h / 4;
+ int pto = CLAMP((max * 0.5 + 0.5) * h / 2, 0, h / 2) + h / 4;
- max[0]*=0.8;
- max[1]*=0.8;
- min[0]*=0.8;
- min[1]*=0.8;
-
- if (!stereo) {
- for(int j=0;j<h;j++) {
- float v = (j/(float)h) * 2.0 - 1.0;
- uint8_t* imgofs = &imgw[(j*w+i)*3];
- if (v>min[0] && v<max[0]) {
- imgofs[0]=255;
- imgofs[1]=150;
- imgofs[2]=80;
- } else {
- imgofs[0]=0;
- imgofs[1]=0;
- imgofs[2]=0;
- }
- }
+ for (int j = 0; j < h; j++) {
+ uint8_t *p = &imgw[(j * w + i) * 3];
+ if (j < pfrom || j > pto) {
+ p[0] = 100;
+ p[1] = 100;
+ p[2] = 100;
} else {
-
- for(int j=0;j<h;j++) {
-
- int half;
- float v;
- if (j<(h/2)) {
- half=0;
- v = (j/(float)(h/2)) * 2.0 - 1.0;
- } else {
- half=1;
- if( (float)(h/2) != 0 ) {
- v = ((j-(h/2))/(float)(h/2)) * 2.0 - 1.0;
- } else {
- v = ((j-(h/2))/(float)(1/2)) * 2.0 - 1.0;
- }
- }
-
- uint8_t* imgofs = &imgw[(j*w+i)*3];
- if (v>min[half] && v<max[half]) {
- imgofs[0]=255;
- imgofs[1]=150;
- imgofs[2]=80;
- } else {
- imgofs[0]=0;
- imgofs[1]=0;
- imgofs[2]=0;
- }
- }
-
+ p[0] = 180;
+ p[1] = 180;
+ p[2] = 180;
}
-
}
}
imgdata = PoolVector<uint8_t>::Write();
- post_process_preview(img);
+ //post_process_preview(img);
- Ref<ImageTexture> ptex = Ref<ImageTexture>( memnew( ImageTexture));
- ptex->create_from_image(Image(w,h,0,Image::FORMAT_RGB8,img),0);
+ Ref<ImageTexture> ptex = Ref<ImageTexture>(memnew(ImageTexture));
+ Ref<Image> image;
+ image.instance();
+ image->create(w, h, false, Image::FORMAT_RGB8, img);
+ ptex->create_from_image(image, 0);
return ptex;
-
}
-EditorSamplePreviewPlugin::EditorSamplePreviewPlugin() {
+EditorAudioStreamPreviewPlugin::EditorAudioStreamPreviewPlugin() {
}
-#endif
///////////////////////////////////////////////////////////////////////////
diff --git a/editor/plugins/editor_preview_plugins.h b/editor/plugins/editor_preview_plugins.h
index 332f991b49..140d9f849f 100644
--- a/editor/plugins/editor_preview_plugins.h
+++ b/editor/plugins/editor_preview_plugins.h
@@ -100,17 +100,13 @@ public:
EditorScriptPreviewPlugin();
};
-// FIXME: Needs to be rewritten for AudioStream in Godot 3.0+
-#if 0
-class EditorSamplePreviewPlugin : public EditorResourcePreviewGenerator {
+class EditorAudioStreamPreviewPlugin : public EditorResourcePreviewGenerator {
public:
+ virtual bool handles(const String &p_type) const;
+ virtual Ref<Texture> generate(const RES &p_from);
- virtual bool handles(const String& p_type) const;
- virtual Ref<Texture> generate(const RES& p_from);
-
- EditorSamplePreviewPlugin();
+ EditorAudioStreamPreviewPlugin();
};
-#endif
class EditorMeshPreviewPlugin : public EditorResourcePreviewGenerator {
diff --git a/editor/plugins/spatial_editor_plugin.cpp b/editor/plugins/spatial_editor_plugin.cpp
index 5b713ef3c4..f02efd9e4d 100644
--- a/editor/plugins/spatial_editor_plugin.cpp
+++ b/editor/plugins/spatial_editor_plugin.cpp
@@ -32,7 +32,7 @@
#include "camera_matrix.h"
#include "core/os/input.h"
-#include "editor/animation_editor.h"
+
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/plugins/animation_player_editor_plugin.h"
@@ -1829,7 +1829,7 @@ void SpatialEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
if (!get_selected_count() || _edit.mode != TRANSFORM_NONE)
return;
- if (!AnimationPlayerEditor::singleton->get_key_editor()->has_keying()) {
+ if (!AnimationPlayerEditor::singleton->get_track_editor()->has_keying()) {
set_message(TTR("Keying is disabled (no key inserted)."));
return;
}
diff --git a/editor/property_selector.cpp b/editor/property_selector.cpp
index 3e95064ead..d927e07976 100644
--- a/editor/property_selector.cpp
+++ b/editor/property_selector.cpp
@@ -120,33 +120,33 @@ void PropertySelector::_update_search() {
bool found = false;
Ref<Texture> type_icons[Variant::VARIANT_MAX] = {
- Control::get_icon("MiniVariant", "EditorIcons"),
- Control::get_icon("MiniBoolean", "EditorIcons"),
- Control::get_icon("MiniInteger", "EditorIcons"),
- Control::get_icon("MiniFloat", "EditorIcons"),
- Control::get_icon("MiniString", "EditorIcons"),
- Control::get_icon("MiniVector2", "EditorIcons"),
- Control::get_icon("MiniRect2", "EditorIcons"),
- Control::get_icon("MiniVector3", "EditorIcons"),
- Control::get_icon("MiniMatrix2", "EditorIcons"),
- Control::get_icon("MiniPlane", "EditorIcons"),
- Control::get_icon("MiniQuat", "EditorIcons"),
- Control::get_icon("MiniAabb", "EditorIcons"),
- Control::get_icon("MiniMatrix3", "EditorIcons"),
- Control::get_icon("MiniTransform", "EditorIcons"),
- Control::get_icon("MiniColor", "EditorIcons"),
- Control::get_icon("MiniPath", "EditorIcons"),
- Control::get_icon("MiniRid", "EditorIcons"),
- Control::get_icon("MiniObject", "EditorIcons"),
- Control::get_icon("MiniDictionary", "EditorIcons"),
- Control::get_icon("MiniArray", "EditorIcons"),
- Control::get_icon("MiniRawArray", "EditorIcons"),
- Control::get_icon("MiniIntArray", "EditorIcons"),
- Control::get_icon("MiniFloatArray", "EditorIcons"),
- Control::get_icon("MiniStringArray", "EditorIcons"),
- Control::get_icon("MiniVector2Array", "EditorIcons"),
- Control::get_icon("MiniVector3Array", "EditorIcons"),
- Control::get_icon("MiniColorArray", "EditorIcons")
+ Control::get_icon("Variant", "EditorIcons"),
+ Control::get_icon("bool", "EditorIcons"),
+ Control::get_icon("int", "EditorIcons"),
+ Control::get_icon("float", "EditorIcons"),
+ Control::get_icon("String", "EditorIcons"),
+ Control::get_icon("Vector2", "EditorIcons"),
+ Control::get_icon("Rect2", "EditorIcons"),
+ Control::get_icon("Vector3", "EditorIcons"),
+ Control::get_icon("Transform2D", "EditorIcons"),
+ Control::get_icon("Plane", "EditorIcons"),
+ Control::get_icon("Quat", "EditorIcons"),
+ Control::get_icon("AABB", "EditorIcons"),
+ Control::get_icon("Basis", "EditorIcons"),
+ Control::get_icon("Transform", "EditorIcons"),
+ Control::get_icon("Color", "EditorIcons"),
+ Control::get_icon("Path", "EditorIcons"),
+ Control::get_icon("RID", "EditorIcons"),
+ Control::get_icon("Object", "EditorIcons"),
+ Control::get_icon("Dictionary", "EditorIcons"),
+ Control::get_icon("Array", "EditorIcons"),
+ Control::get_icon("PoolByteArray", "EditorIcons"),
+ Control::get_icon("PoolIntArray", "EditorIcons"),
+ Control::get_icon("PoolRealArray", "EditorIcons"),
+ Control::get_icon("PoolStringArray", "EditorIcons"),
+ Control::get_icon("PoolVector2Array", "EditorIcons"),
+ Control::get_icon("PoolVector3Array", "EditorIcons"),
+ Control::get_icon("PoolColorArray", "EditorIcons")
};
for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
@@ -175,6 +175,10 @@ void PropertySelector::_update_search() {
if (search_box->get_text() != String() && E->get().name.find(search_box->get_text()) == -1)
continue;
+
+ if (type_filter.size() && type_filter.find(E->get().type) == -1)
+ continue;
+
TreeItem *item = search_options->create_item(category ? category : root);
item->set_text(0, E->get().name);
item->set_metadata(0, E->get().name);
@@ -534,6 +538,10 @@ void PropertySelector::select_property_from_instance(Object *p_instance, const S
_update_search();
}
+void PropertySelector::set_type_filter(const Vector<Variant::Type> &p_type_filter) {
+ type_filter = p_type_filter;
+}
+
void PropertySelector::_bind_methods() {
ClassDB::bind_method(D_METHOD("_text_changed"), &PropertySelector::_text_changed);
diff --git a/editor/property_selector.h b/editor/property_selector.h
index d9b1aee422..f5b34d210e 100644
--- a/editor/property_selector.h
+++ b/editor/property_selector.h
@@ -60,6 +60,8 @@ class PropertySelector : public ConfirmationDialog {
void _item_selected();
+ Vector<Variant::Type> type_filter;
+
protected:
void _notification(int p_what);
static void _bind_methods();
@@ -75,6 +77,8 @@ public:
void select_property_from_basic_type(Variant::Type p_type, const String &p_current = "");
void select_property_from_instance(Object *p_instance, const String &p_current = "");
+ void set_type_filter(const Vector<Variant::Type> &p_type_filter);
+
PropertySelector();
};
diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp
index 65e3cdedea..d6dd71e74b 100644
--- a/editor/scene_tree_dock.cpp
+++ b/editor/scene_tree_dock.cpp
@@ -33,7 +33,7 @@
#include "core/io/resource_saver.h"
#include "core/os/keyboard.h"
#include "core/project_settings.h"
-#include "editor/animation_editor.h"
+
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/multi_node_edit.h"
@@ -1262,8 +1262,8 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V
editor_data->get_undo_redo().add_do_method(this, "_set_owners", edited_scene, owners);
- if (AnimationPlayerEditor::singleton->get_key_editor()->get_root() == node)
- editor_data->get_undo_redo().add_do_method(AnimationPlayerEditor::singleton->get_key_editor(), "set_root", node);
+ if (AnimationPlayerEditor::singleton->get_track_editor()->get_root() == node)
+ editor_data->get_undo_redo().add_do_method(AnimationPlayerEditor::singleton->get_track_editor(), "set_root", node);
editor_data->get_undo_redo().add_undo_method(new_parent, "remove_child", node);
editor_data->get_undo_redo().add_undo_method(node, "set_name", former_names[ni]);
@@ -1290,8 +1290,8 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V
editor_data->get_undo_redo().add_undo_method(node->get_parent(), "add_child", node);
editor_data->get_undo_redo().add_undo_method(node->get_parent(), "move_child", node, child_pos);
editor_data->get_undo_redo().add_undo_method(this, "_set_owners", edited_scene, owners);
- if (AnimationPlayerEditor::singleton->get_key_editor()->get_root() == node)
- editor_data->get_undo_redo().add_undo_method(AnimationPlayerEditor::singleton->get_key_editor(), "set_root", node);
+ if (AnimationPlayerEditor::singleton->get_track_editor()->get_root() == node)
+ editor_data->get_undo_redo().add_undo_method(AnimationPlayerEditor::singleton->get_track_editor(), "set_root", node);
if (p_keep_global_xform) {
if (Object::cast_to<Node2D>(node))
@@ -1392,8 +1392,8 @@ void SceneTreeDock::_delete_confirm() {
editor_data->get_undo_redo().add_do_method(n->get_parent(), "remove_child", n);
editor_data->get_undo_redo().add_undo_method(n->get_parent(), "add_child", n);
editor_data->get_undo_redo().add_undo_method(n->get_parent(), "move_child", n, n->get_index());
- if (AnimationPlayerEditor::singleton->get_key_editor()->get_root() == n)
- editor_data->get_undo_redo().add_undo_method(AnimationPlayerEditor::singleton->get_key_editor(), "set_root", n);
+ if (AnimationPlayerEditor::singleton->get_track_editor()->get_root() == n)
+ editor_data->get_undo_redo().add_undo_method(AnimationPlayerEditor::singleton->get_track_editor(), "set_root", n);
editor_data->get_undo_redo().add_undo_method(this, "_set_owners", edited_scene, owners);
editor_data->get_undo_redo().add_undo_reference(n);
diff --git a/editor/scene_tree_editor.cpp b/editor/scene_tree_editor.cpp
index 6fe52e3337..7d2127d4f8 100644
--- a/editor/scene_tree_editor.cpp
+++ b/editor/scene_tree_editor.cpp
@@ -30,6 +30,7 @@
#include "scene_tree_editor.h"
+#include "editor/plugins/animation_player_editor_plugin.h"
#include "editor/plugins/canvas_item_editor_plugin.h"
#include "editor_node.h"
#include "message_queue.h"
@@ -90,6 +91,12 @@ void SceneTreeEditor::_cell_button_pressed(Object *p_item, int p_column, int p_i
_update_tree();
emit_signal("node_changed");
}
+ } else if (p_id == BUTTON_PIN) {
+
+ if (n->is_class("AnimationPlayer")) {
+ AnimationPlayerEditor::singleton->unpin();
+ _update_tree();
+ }
} else if (p_id == BUTTON_GROUP) {
if (n->is_class("CanvasItem")) {
@@ -284,6 +291,13 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent) {
p_node->connect("visibility_changed", this, "_node_visibility_changed", varray(p_node));
_update_visibility_color(p_node, item);
+ } else if (p_node->is_class("AnimationPlayer")) {
+
+ bool is_pinned = AnimationPlayerEditor::singleton->get_player() == p_node && AnimationPlayerEditor::singleton->is_pinned();
+
+ if (is_pinned) {
+ item->add_button(0, get_icon("Pin", "EditorIcons"), BUTTON_PIN, false, TTR("AnimationPlayer is pinned.\nClick to unpin."));
+ }
}
}
diff --git a/editor/scene_tree_editor.h b/editor/scene_tree_editor.h
index 896fd6c431..b173d7d215 100644
--- a/editor/scene_tree_editor.h
+++ b/editor/scene_tree_editor.h
@@ -55,6 +55,7 @@ class SceneTreeEditor : public Control {
BUTTON_WARNING = 5,
BUTTON_SIGNALS = 6,
BUTTON_GROUPS = 7,
+ BUTTON_PIN = 8,
};
Tree *tree;
diff --git a/platform/android/AndroidManifest.xml.template b/platform/android/AndroidManifest.xml.template
index 13d10b5026..6129968511 100644
--- a/platform/android/AndroidManifest.xml.template
+++ b/platform/android/AndroidManifest.xml.template
@@ -1,206 +1,12 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.godot.game"
- android:versionCode="1"
- android:versionName="1.0"
- android:installLocation="auto"
- >
-<supports-screens android:smallScreens="true"
- android:normalScreens="true"
- android:largeScreens="true"
- android:xlargeScreens="true"/>
-
- <application android:label="@string/godot_project_name_string" android:icon="@drawable/icon" android:allowBackup="false" $$ADD_APPATTRIBUTE_CHUNKS$$ >
- <activity android:name="org.godotengine.godot.Godot"
- android:label="@string/godot_project_name_string"
- android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
- android:launchMode="singleTask"
- android:screenOrientation="landscape"
- android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize"
- android:resizeableActivity="false">
-
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- <service android:name="org.godotengine.godot.GodotDownloaderService" />
-
-
-
-
-$$ADD_APPLICATION_CHUNKS$$
-
- </application>
- <uses-feature android:glEsVersion="0x00030000" android:required="true" />
-
-$$ADD_PERMISSION_CHUNKS$$
-<uses-permission android:name="godot.ACCESS_CHECKIN_PROPERTIES"/>
-<uses-permission android:name="godot.ACCESS_COARSE_LOCATION"/>
-<uses-permission android:name="godot.ACCESS_FINE_LOCATION"/>
-<uses-permission android:name="godot.ACCESS_LOCATION_EXTRA_COMMANDS"/>
-<uses-permission android:name="godot.ACCESS_MOCK_LOCATION"/>
-<uses-permission android:name="godot.ACCESS_NETWORK_STATE"/>
-<uses-permission android:name="godot.ACCESS_SURFACE_FLINGER"/>
-<uses-permission android:name="godot.ACCESS_WIFI_STATE"/>
-<uses-permission android:name="godot.ACCOUNT_MANAGER"/>
-<uses-permission android:name="godot.ADD_VOICEMAIL"/>
-<uses-permission android:name="godot.AUTHENTICATE_ACCOUNTS"/>
-<uses-permission android:name="godot.BATTERY_STATS"/>
-<uses-permission android:name="godot.BIND_ACCESSIBILITY_SERVICE"/>
-<uses-permission android:name="godot.BIND_APPWIDGET"/>
-<uses-permission android:name="godot.BIND_DEVICE_ADMIN"/>
-<uses-permission android:name="godot.BIND_INPUT_METHOD"/>
-<uses-permission android:name="godot.BIND_NFC_SERVICE"/>
-<uses-permission android:name="godot.BIND_NOTIFICATION_LISTENER_SERVICE"/>
-<uses-permission android:name="godot.BIND_PRINT_SERVICE"/>
-<uses-permission android:name="godot.BIND_REMOTEVIEWS"/>
-<uses-permission android:name="godot.BIND_TEXT_SERVICE"/>
-<uses-permission android:name="godot.BIND_VPN_SERVICE"/>
-<uses-permission android:name="godot.BIND_WALLPAPER"/>
-<uses-permission android:name="godot.BLUETOOTH"/>
-<uses-permission android:name="godot.BLUETOOTH_ADMIN"/>
-<uses-permission android:name="godot.BLUETOOTH_PRIVILEGED"/>
-<uses-permission android:name="godot.BRICK"/>
-<uses-permission android:name="godot.BROADCAST_PACKAGE_REMOVED"/>
-<uses-permission android:name="godot.BROADCAST_SMS"/>
-<uses-permission android:name="godot.BROADCAST_STICKY"/>
-<uses-permission android:name="godot.BROADCAST_WAP_PUSH"/>
-<uses-permission android:name="godot.CALL_PHONE"/>
-<uses-permission android:name="godot.CALL_PRIVILEGED"/>
-<uses-permission android:name="godot.CAMERA"/>
-<uses-permission android:name="godot.CAPTURE_AUDIO_OUTPUT"/>
-<uses-permission android:name="godot.CAPTURE_SECURE_VIDEO_OUTPUT"/>
-<uses-permission android:name="godot.CAPTURE_VIDEO_OUTPUT"/>
-<uses-permission android:name="godot.CHANGE_COMPONENT_ENABLED_STATE"/>
-<uses-permission android:name="godot.CHANGE_CONFIGURATION"/>
-<uses-permission android:name="godot.CHANGE_NETWORK_STATE"/>
-<uses-permission android:name="godot.CHANGE_WIFI_MULTICAST_STATE"/>
-<uses-permission android:name="godot.CHANGE_WIFI_STATE"/>
-<uses-permission android:name="godot.CLEAR_APP_CACHE"/>
-<uses-permission android:name="godot.CLEAR_APP_USER_DATA"/>
-<uses-permission android:name="godot.CONTROL_LOCATION_UPDATES"/>
-<uses-permission android:name="godot.DELETE_CACHE_FILES"/>
-<uses-permission android:name="godot.DELETE_PACKAGES"/>
-<uses-permission android:name="godot.DEVICE_POWER"/>
-<uses-permission android:name="godot.DIAGNOSTIC"/>
-<uses-permission android:name="godot.DISABLE_KEYGUARD"/>
-<uses-permission android:name="godot.DUMP"/>
-<uses-permission android:name="godot.EXPAND_STATUS_BAR"/>
-<uses-permission android:name="godot.FACTORY_TEST"/>
-<uses-permission android:name="godot.FLASHLIGHT"/>
-<uses-permission android:name="godot.FORCE_BACK"/>
-<uses-permission android:name="godot.GET_ACCOUNTS"/>
-<uses-permission android:name="godot.GET_PACKAGE_SIZE"/>
-<uses-permission android:name="godot.GET_TASKS"/>
-<uses-permission android:name="godot.GET_TOP_ACTIVITY_INFO"/>
-<uses-permission android:name="godot.GLOBAL_SEARCH"/>
-<uses-permission android:name="godot.HARDWARE_TEST"/>
-<uses-permission android:name="godot.INJECT_EVENTS"/>
-<uses-permission android:name="godot.INSTALL_LOCATION_PROVIDER"/>
-<uses-permission android:name="godot.INSTALL_PACKAGES"/>
-<uses-permission android:name="godot.INSTALL_SHORTCUT"/>
-<uses-permission android:name="godot.INTERNAL_SYSTEM_WINDOW"/>
-<uses-permission android:name="godot.INTERNET"/>
-<uses-permission android:name="godot.KILL_BACKGROUND_PROCESSES"/>
-<uses-permission android:name="godot.LOCATION_HARDWARE"/>
-<uses-permission android:name="godot.MANAGE_ACCOUNTS"/>
-<uses-permission android:name="godot.MANAGE_APP_TOKENS"/>
-<uses-permission android:name="godot.MANAGE_DOCUMENTS"/>
-<uses-permission android:name="godot.MASTER_CLEAR"/>
-<uses-permission android:name="godot.MEDIA_CONTENT_CONTROL"/>
-<uses-permission android:name="godot.MODIFY_AUDIO_SETTINGS"/>
-<uses-permission android:name="godot.MODIFY_PHONE_STATE"/>
-<uses-permission android:name="godot.MOUNT_FORMAT_FILESYSTEMS"/>
-<uses-permission android:name="godot.MOUNT_UNMOUNT_FILESYSTEMS"/>
-<uses-permission android:name="godot.NFC"/>
-<uses-permission android:name="godot.PERSISTENT_ACTIVITY"/>
-<uses-permission android:name="godot.PROCESS_OUTGOING_CALLS"/>
-<uses-permission android:name="godot.READ_CALENDAR"/>
-<uses-permission android:name="godot.READ_CALL_LOG"/>
-<uses-permission android:name="godot.READ_CONTACTS"/>
-<uses-permission android:name="godot.READ_EXTERNAL_STORAGE"/>
-<uses-permission android:name="godot.READ_FRAME_BUFFER"/>
-<uses-permission android:name="godot.READ_HISTORY_BOOKMARKS"/>
-<uses-permission android:name="godot.READ_INPUT_STATE"/>
-<uses-permission android:name="godot.READ_LOGS"/>
-<uses-permission android:name="godot.READ_PHONE_STATE"/>
-<uses-permission android:name="godot.READ_PROFILE"/>
-<uses-permission android:name="godot.READ_SMS"/>
-<uses-permission android:name="godot.READ_SOCIAL_STREAM"/>
-<uses-permission android:name="godot.READ_SYNC_SETTINGS"/>
-<uses-permission android:name="godot.READ_SYNC_STATS"/>
-<uses-permission android:name="godot.READ_USER_DICTIONARY"/>
-<uses-permission android:name="godot.REBOOT"/>
-<uses-permission android:name="godot.RECEIVE_BOOT_COMPLETED"/>
-<uses-permission android:name="godot.RECEIVE_MMS"/>
-<uses-permission android:name="godot.RECEIVE_SMS"/>
-<uses-permission android:name="godot.RECEIVE_WAP_PUSH"/>
-<uses-permission android:name="godot.RECORD_AUDIO"/>
-<uses-permission android:name="godot.REORDER_TASKS"/>
-<uses-permission android:name="godot.RESTART_PACKAGES"/>
-<uses-permission android:name="godot.SEND_RESPOND_VIA_MESSAGE"/>
-<uses-permission android:name="godot.SEND_SMS"/>
-<uses-permission android:name="godot.SET_ACTIVITY_WATCHER"/>
-<uses-permission android:name="godot.SET_ALARM"/>
-<uses-permission android:name="godot.SET_ALWAYS_FINISH"/>
-<uses-permission android:name="godot.SET_ANIMATION_SCALE"/>
-<uses-permission android:name="godot.SET_DEBUG_APP"/>
-<uses-permission android:name="godot.SET_ORIENTATION"/>
-<uses-permission android:name="godot.SET_POINTER_SPEED"/>
-<uses-permission android:name="godot.SET_PREFERRED_APPLICATIONS"/>
-<uses-permission android:name="godot.SET_PROCESS_LIMIT"/>
-<uses-permission android:name="godot.SET_TIME"/>
-<uses-permission android:name="godot.SET_TIME_ZONE"/>
-<uses-permission android:name="godot.SET_WALLPAPER"/>
-<uses-permission android:name="godot.SET_WALLPAPER_HINTS"/>
-<uses-permission android:name="godot.SIGNAL_PERSISTENT_PROCESSES"/>
-<uses-permission android:name="godot.STATUS_BAR"/>
-<uses-permission android:name="godot.SUBSCRIBED_FEEDS_READ"/>
-<uses-permission android:name="godot.SUBSCRIBED_FEEDS_WRITE"/>
-<uses-permission android:name="godot.SYSTEM_ALERT_WINDOW"/>
-<uses-permission android:name="godot.TRANSMIT_IR"/>
-<uses-permission android:name="godot.UNINSTALL_SHORTCUT"/>
-<uses-permission android:name="godot.UPDATE_DEVICE_STATS"/>
-<uses-permission android:name="godot.USE_CREDENTIALS"/>
-<uses-permission android:name="godot.USE_SIP"/>
-<uses-permission android:name="godot.VIBRATE"/>
-<uses-permission android:name="godot.WAKE_LOCK"/>
-<uses-permission android:name="godot.WRITE_APN_SETTINGS"/>
-<uses-permission android:name="godot.WRITE_CALENDAR"/>
-<uses-permission android:name="godot.WRITE_CALL_LOG"/>
-<uses-permission android:name="godot.WRITE_CONTACTS"/>
-<uses-permission android:name="godot.WRITE_EXTERNAL_STORAGE"/>
-<uses-permission android:name="godot.WRITE_GSERVICES"/>
-<uses-permission android:name="godot.WRITE_HISTORY_BOOKMARKS"/>
-<uses-permission android:name="godot.WRITE_PROFILE"/>
-<uses-permission android:name="godot.WRITE_SECURE_SETTINGS"/>
-<uses-permission android:name="godot.WRITE_SETTINGS"/>
-<uses-permission android:name="godot.WRITE_SMS"/>
-<uses-permission android:name="godot.WRITE_SOCIAL_STREAM"/>
-<uses-permission android:name="godot.WRITE_SYNC_SETTINGS"/>
-<uses-permission android:name="godot.WRITE_USER_DICTIONARY"/>
-<uses-permission android:name="godot.custom.0"/>
-<uses-permission android:name="godot.custom.1"/>
-<uses-permission android:name="godot.custom.2"/>
-<uses-permission android:name="godot.custom.3"/>
-<uses-permission android:name="godot.custom.4"/>
-<uses-permission android:name="godot.custom.5"/>
-<uses-permission android:name="godot.custom.6"/>
-<uses-permission android:name="godot.custom.7"/>
-<uses-permission android:name="godot.custom.8"/>
-<uses-permission android:name="godot.custom.9"/>
-<uses-permission android:name="godot.custom.10"/>
-<uses-permission android:name="godot.custom.11"/>
-<uses-permission android:name="godot.custom.12"/>
-<uses-permission android:name="godot.custom.13"/>
-<uses-permission android:name="godot.custom.14"/>
-<uses-permission android:name="godot.custom.15"/>
-<uses-permission android:name="godot.custom.16"/>
-<uses-permission android:name="godot.custom.17"/>
-<uses-permission android:name="godot.custom.18"/>
-<uses-permission android:name="godot.custom.19"/>
-
-<uses-sdk android:minSdkVersion="18" android:targetSdkVersion="27"/>
-
-</manifest>
+duplicate,
+delete
+options,
+unite equal tracks
+fix (harcode) seek
+biziers
+sound tracks
+play icons on key tracks
+
+Siempre quise se parte de un equipo que desarrolle un juego, lo publique en Steam y consiga un Overwhelmingly Positive.
+
+Posiblemente eso no me de nunca.. pero esto es muchísimo mejor! Gracias a todos por tenerle fé a este proyecto hermoso!
diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp
index a0e0137863..0aabc3b38c 100644
--- a/scene/animation/animation_player.cpp
+++ b/scene/animation/animation_player.cpp
@@ -33,7 +33,7 @@
#include "engine.h"
#include "message_queue.h"
#include "scene/scene_string_names.h"
-
+#include "servers/audio/audio_stream.h"
#ifdef TOOLS_ENABLED
void AnimatedValuesBackup::update_skeletons() {
@@ -325,10 +325,27 @@ void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim) {
p_anim->node_cache[i]->property_anim[a->track_get_path(i).get_concatenated_subnames()] = pa;
}
}
+
+ if (a->track_get_type(i) == Animation::TYPE_BEZIER && leftover_path.size()) {
+
+ if (!p_anim->node_cache[i]->bezier_anim.has(a->track_get_path(i).get_concatenated_subnames())) {
+
+ TrackNodeCache::BezierAnim ba;
+ String path = leftover_path[leftover_path.size() - 1];
+ Vector<String> index = path.split(".");
+ for (int j = 0; j < index.size(); j++) {
+ ba.bezier_property.push_back(index[j]);
+ }
+ ba.object = resource.is_valid() ? (Object *)resource.ptr() : (Object *)child;
+ ba.owner = p_anim->node_cache[i];
+
+ p_anim->node_cache[i]->bezier_anim[a->track_get_path(i).get_concatenated_subnames()] = ba;
+ }
+ }
}
}
-void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float p_time, float p_delta, float p_interp, bool p_allow_discrete) {
+void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float p_time, float p_delta, float p_interp, bool p_is_current, bool p_seeked, bool p_started) {
_ensure_node_caches(p_anim);
ERR_FAIL_COND(p_anim->node_cache.size() != p_anim->animation->get_track_count());
@@ -394,7 +411,39 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float
TrackNodeCache::PropertyAnim *pa = &E->get();
- if (a->value_track_get_update_mode(i) == Animation::UPDATE_CONTINUOUS || (p_delta == 0 && a->value_track_get_update_mode(i) == Animation::UPDATE_DISCRETE)) { //delta == 0 means seek
+ Animation::UpdateMode update_mode = a->value_track_get_update_mode(i);
+
+ if (update_mode == Animation::UPDATE_CAPTURE) {
+
+ if (p_started) {
+ pa->capture = pa->object->get_indexed(pa->subpath);
+ }
+
+ if (a->track_get_key_count(i) == 0)
+ continue; //eeh not worth it
+
+ float first_key_time = a->track_get_key_time(i, 0);
+
+ if (p_time < first_key_time) {
+ float c = p_time / first_key_time;
+ Variant first_value = a->track_get_key_value(i, 0);
+ Variant interp_value;
+ Variant::interpolate(pa->capture, first_value, c, interp_value);
+
+ if (pa->accum_pass != accum_pass) {
+ ERR_CONTINUE(cache_update_prop_size >= NODE_CACHE_UPDATE_MAX);
+ cache_update_prop[cache_update_prop_size++] = pa;
+ pa->value_accum = interp_value;
+ pa->accum_pass = accum_pass;
+ } else {
+ Variant::interpolate(pa->value_accum, interp_value, p_interp, pa->value_accum);
+ }
+
+ continue; //handled
+ }
+ }
+
+ if (update_mode == Animation::UPDATE_CONTINUOUS || update_mode == Animation::UPDATE_CAPTURE || (p_delta == 0 && update_mode == Animation::UPDATE_DISCRETE)) { //delta == 0 means seek
Variant value = a->value_track_interpolate(i, p_time);
@@ -415,7 +464,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float
Variant::interpolate(pa->value_accum, value, p_interp, pa->value_accum);
}
- } else if (p_allow_discrete && p_delta != 0) {
+ } else if (p_is_current && p_delta != 0) {
List<int> indices;
a->value_track_get_key_indices(i, p_time, p_delta, &indices);
@@ -470,9 +519,10 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float
if (!nc->node)
continue;
- if (p_delta == 0)
+ if (p_delta == 0) {
continue;
- if (!p_allow_discrete)
+ }
+ if (!p_is_current)
break;
List<int> indices;
@@ -500,11 +550,180 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float
}
} break;
+ case Animation::TYPE_BEZIER: {
+
+ if (!nc->node)
+ continue;
+
+ Map<StringName, TrackNodeCache::BezierAnim>::Element *E = nc->bezier_anim.find(a->track_get_path(i).get_concatenated_subnames());
+ ERR_CONTINUE(!E); //should it continue, or create a new one?
+
+ TrackNodeCache::BezierAnim *ba = &E->get();
+
+ float bezier = a->bezier_track_interpolate(i, p_time);
+ if (ba->accum_pass != accum_pass) {
+ ERR_CONTINUE(cache_update_bezier_size >= NODE_CACHE_UPDATE_MAX);
+ cache_update_bezier[cache_update_bezier_size++] = ba;
+ ba->bezier_accum = bezier;
+ ba->accum_pass = accum_pass;
+ } else {
+ ba->bezier_accum = Math::lerp(ba->bezier_accum, bezier, p_interp);
+ }
+
+ } break;
+ case Animation::TYPE_AUDIO: {
+
+ if (!nc->node)
+ continue;
+ if (p_delta == 0) {
+ continue;
+ }
+
+ if (p_seeked) {
+ //find whathever should be playing
+ int idx = a->track_find_key(i, p_time);
+ if (idx < 0)
+ continue;
+
+ Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx);
+ if (!stream.is_valid()) {
+ nc->node->call("stop");
+ nc->audio_playing = false;
+ playing_caches.erase(nc);
+ } else {
+ float start_ofs = a->audio_track_get_key_start_offset(i, idx);
+ start_ofs += p_time - a->track_get_key_time(i, idx);
+ float end_ofs = a->audio_track_get_key_end_offset(i, idx);
+ float len = stream->get_length();
+
+ if (start_ofs > len - end_ofs) {
+ nc->node->call("stop");
+ nc->audio_playing = false;
+ playing_caches.erase(nc);
+ continue;
+ }
+
+ nc->node->call("set_stream", stream);
+ nc->node->call("play", start_ofs);
+
+ nc->audio_playing = true;
+ playing_caches.insert(nc);
+ if (len && end_ofs > 0) { //force a end at a time
+ nc->audio_len = len - start_ofs - end_ofs;
+ } else {
+ nc->audio_len = 0;
+ }
+
+ nc->audio_start = p_time;
+ }
+
+ } else {
+ //find stuff to play
+ List<int> to_play;
+ a->track_get_key_indices_in_range(i, p_time, p_delta, &to_play);
+ if (to_play.size()) {
+ int idx = to_play.back()->get();
+
+ Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx);
+ if (!stream.is_valid()) {
+ nc->node->call("stop");
+ nc->audio_playing = false;
+ playing_caches.erase(nc);
+ } else {
+ float start_ofs = a->audio_track_get_key_start_offset(i, idx);
+ float end_ofs = a->audio_track_get_key_end_offset(i, idx);
+ float len = stream->get_length();
+
+ nc->node->call("set_stream", stream);
+ nc->node->call("play", start_ofs);
+
+ nc->audio_playing = true;
+ playing_caches.insert(nc);
+ if (len && end_ofs > 0) { //force a end at a time
+ nc->audio_len = len - start_ofs - end_ofs;
+ } else {
+ nc->audio_len = 0;
+ }
+
+ nc->audio_start = p_time;
+ }
+ } else if (nc->audio_playing) {
+ if (nc->audio_start > p_time || (nc->audio_len > 0 && p_time - nc->audio_start < nc->audio_len)) {
+ //time to stop
+ nc->node->call("stop");
+ nc->audio_playing = false;
+ playing_caches.erase(nc);
+ }
+ }
+ }
+
+ } break;
+ case Animation::TYPE_ANIMATION: {
+
+ AnimationPlayer *player = Object::cast_to<AnimationPlayer>(nc->node);
+ if (!player)
+ continue;
+
+ if (p_delta == 0 || p_seeked) {
+ //seek
+ int idx = a->track_find_key(i, p_time);
+ if (idx < 0)
+ continue;
+
+ float pos = a->track_get_key_time(i, idx);
+
+ StringName anim_name = a->animation_track_get_key_animation(i, idx);
+ if (String(anim_name) == "[stop]" || !player->has_animation(anim_name))
+ continue;
+
+ Ref<Animation> anim = player->get_animation(anim_name);
+
+ float at_anim_pos;
+
+ if (anim->has_loop()) {
+ at_anim_pos = Math::fposmod(p_time - pos, anim->get_length()); //seek to loop
+ } else {
+ at_anim_pos = MAX(anim->get_length(), p_time - pos); //seek to end
+ }
+
+ if (player->is_playing() || p_seeked) {
+ player->play(anim_name);
+ player->seek(at_anim_pos);
+ nc->animation_playing = true;
+ playing_caches.insert(nc);
+ } else {
+ player->set_assigned_animation(anim_name);
+ player->seek(at_anim_pos, true);
+ }
+ } else {
+ //find stuff to play
+ List<int> to_play;
+ a->track_get_key_indices_in_range(i, p_time, p_delta, &to_play);
+ if (to_play.size()) {
+ int idx = to_play.back()->get();
+
+ StringName anim_name = a->animation_track_get_key_animation(i, idx);
+ if (String(anim_name) == "[stop]" || !player->has_animation(anim_name)) {
+
+ if (playing_caches.has(nc)) {
+ playing_caches.erase(nc);
+ player->stop();
+ nc->animation_playing = false;
+ }
+ } else {
+ player->play(anim_name);
+ nc->animation_playing = true;
+ playing_caches.insert(nc);
+ }
+ }
+ }
+
+ } break;
}
}
}
-void AnimationPlayer::_animation_process_data(PlaybackData &cd, float p_delta, float p_blend) {
+void AnimationPlayer::_animation_process_data(PlaybackData &cd, float p_delta, float p_blend, bool p_seeked, bool p_started) {
float delta = p_delta * speed_scale * cd.speed_scale;
float next_pos = cd.pos + delta;
@@ -553,22 +772,25 @@ void AnimationPlayer::_animation_process_data(PlaybackData &cd, float p_delta, f
cd.pos = next_pos;
- _animation_process_animation(cd.from, cd.pos, delta, p_blend, &cd == &playback.current);
+ _animation_process_animation(cd.from, cd.pos, delta, p_blend, &cd == &playback.current, p_seeked, p_started);
}
-void AnimationPlayer::_animation_process2(float p_delta) {
+void AnimationPlayer::_animation_process2(float p_delta, bool p_started) {
Playback &c = playback;
accum_pass++;
- _animation_process_data(c.current, p_delta, 1.0f);
+ _animation_process_data(c.current, p_delta, 1.0f, c.seeked && p_delta != 0, p_started);
+ if (p_delta != 0) {
+ c.seeked = false;
+ }
List<Blend>::Element *prev = NULL;
for (List<Blend>::Element *E = c.blend.back(); E; E = prev) {
Blend &b = E->get();
float blend = b.blend_left / b.blend_time;
- _animation_process_data(b.data, p_delta, blend);
+ _animation_process_data(b.data, p_delta, blend, false, false);
b.blend_left -= Math::absf(speed_scale * p_delta);
@@ -652,6 +874,16 @@ void AnimationPlayer::_animation_update_transforms() {
}
cache_update_prop_size = 0;
+
+ for (int i = 0; i < cache_update_bezier_size; i++) {
+
+ TrackNodeCache::BezierAnim *ba = cache_update_bezier[i];
+
+ ERR_CONTINUE(ba->accum_pass != accum_pass);
+ ba->object->set_indexed(ba->bezier_property, ba->bezier_accum);
+ }
+
+ cache_update_bezier_size = 0;
}
void AnimationPlayer::_animation_process(float p_delta) {
@@ -660,7 +892,12 @@ void AnimationPlayer::_animation_process(float p_delta) {
end_reached = false;
end_notify = false;
- _animation_process2(p_delta);
+ _animation_process2(p_delta, playback.started);
+
+ if (playback.started) {
+ playback.started = false;
+ }
+
_animation_update_transforms();
if (end_reached) {
if (queued.size()) {
@@ -865,7 +1102,7 @@ void AnimationPlayer::queue(const StringName &p_name) {
void AnimationPlayer::clear_queue() {
queued.clear();
-};
+}
void AnimationPlayer::play_backwards(const StringName &p_name, float p_custom_blend) {
@@ -930,10 +1167,14 @@ void AnimationPlayer::play(const StringName &p_name, float p_custom_blend, float
}
}
+ _stop_playing_caches();
+
c.current.from = &animation_set[name];
c.current.pos = p_from_end ? c.current.from->animation->get_length() : 0;
c.current.speed_scale = p_custom_scale;
c.assigned = p_name;
+ c.seeked = false;
+ c.started = true;
if (!end_reached)
queued.clear();
@@ -1004,6 +1245,7 @@ String AnimationPlayer::get_assigned_animation() const {
void AnimationPlayer::stop(bool p_reset) {
+ _stop_playing_caches();
Playback &c = playback;
c.blend.clear();
if (p_reset) {
@@ -1042,6 +1284,7 @@ void AnimationPlayer::seek(float p_time, bool p_update) {
}
playback.current.pos = p_time;
+ playback.seeked = true;
if (p_update) {
_animation_process(0);
}
@@ -1086,6 +1329,24 @@ void AnimationPlayer::_animation_changed() {
clear_caches();
}
+void AnimationPlayer::_stop_playing_caches() {
+
+ for (Set<TrackNodeCache *>::Element *E = playing_caches.front(); E; E = E->next()) {
+
+ if (E->get()->node && E->get()->audio_playing) {
+ E->get()->node->call("stop");
+ }
+ if (E->get()->node && E->get()->animation_playing) {
+ AnimationPlayer *player = Object::cast_to<AnimationPlayer>(E->get()->node);
+ if (!player)
+ continue;
+ player->stop();
+ }
+ }
+
+ playing_caches.clear();
+}
+
void AnimationPlayer::_node_removed(Node *p_node) {
clear_caches(); // nodes contained here ar being removed, clear the caches
@@ -1093,6 +1354,8 @@ void AnimationPlayer::_node_removed(Node *p_node) {
void AnimationPlayer::clear_caches() {
+ _stop_playing_caches();
+
node_cache_map.clear();
for (Map<StringName, AnimationData>::Element *E = animation_set.front(); E; E = E->next()) {
@@ -1102,6 +1365,7 @@ void AnimationPlayer::clear_caches() {
cache_update_size = 0;
cache_update_prop_size = 0;
+ cache_update_bezier_size = 0;
}
void AnimationPlayer::set_active(bool p_active) {
@@ -1368,6 +1632,7 @@ AnimationPlayer::AnimationPlayer() {
accum_pass = 1;
cache_update_size = 0;
cache_update_prop_size = 0;
+ cache_update_bezier_size = 0;
speed_scale = 1;
end_reached = false;
end_notify = false;
@@ -1377,6 +1642,8 @@ AnimationPlayer::AnimationPlayer() {
root = SceneStringNames::get_singleton()->path_pp;
playing = false;
active = true;
+ playback.seeked = false;
+ playback.started = false;
}
AnimationPlayer::~AnimationPlayer() {
diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h
index af2022ddac..49c73e54ad 100644
--- a/scene/animation/animation_player.h
+++ b/scene/animation/animation_player.h
@@ -98,6 +98,12 @@ private:
Vector3 scale_accum;
uint64_t accum_pass;
+ bool audio_playing;
+ float audio_start;
+ float audio_len;
+
+ bool animation_playing;
+
struct PropertyAnim {
TrackNodeCache *owner;
@@ -106,6 +112,7 @@ private:
Object *object;
Variant value_accum;
uint64_t accum_pass;
+ Variant capture;
PropertyAnim() {
accum_pass = 0;
object = NULL;
@@ -114,6 +121,22 @@ private:
Map<StringName, PropertyAnim> property_anim;
+ struct BezierAnim {
+
+ Vector<StringName> bezier_property;
+ TrackNodeCache *owner;
+ float bezier_accum;
+ Object *object;
+ uint64_t accum_pass;
+ BezierAnim() {
+ accum_pass = 0;
+ bezier_accum = 0;
+ object = NULL;
+ }
+ };
+
+ Map<StringName, BezierAnim> bezier_anim;
+
TrackNodeCache() {
skeleton = NULL;
spatial = NULL;
@@ -121,6 +144,8 @@ private:
accum_pass = 0;
bone_idx = -1;
node_2d = NULL;
+ audio_playing = false;
+ animation_playing = false;
}
};
@@ -146,6 +171,10 @@ private:
int cache_update_size;
TrackNodeCache::PropertyAnim *cache_update_prop[NODE_CACHE_UPDATE_MAX];
int cache_update_prop_size;
+ TrackNodeCache::BezierAnim *cache_update_bezier[NODE_CACHE_UPDATE_MAX];
+ int cache_update_bezier_size;
+ Set<TrackNodeCache *> playing_caches;
+
Map<Ref<Animation>, int> used_anims;
uint64_t accum_pass;
@@ -202,6 +231,8 @@ private:
List<Blend> blend;
PlaybackData current;
StringName assigned;
+ bool seeked;
+ bool started;
} playback;
List<StringName> queued;
@@ -216,15 +247,16 @@ private:
NodePath root;
- void _animation_process_animation(AnimationData *p_anim, float p_time, float p_delta, float p_interp, bool p_allow_discrete = true);
+ void _animation_process_animation(AnimationData *p_anim, float p_time, float p_delta, float p_interp, bool p_is_current = true, bool p_seeked = false, bool p_started = false);
void _ensure_node_caches(AnimationData *p_anim);
- void _animation_process_data(PlaybackData &cd, float p_delta, float p_blend);
- void _animation_process2(float p_delta);
+ void _animation_process_data(PlaybackData &cd, float p_delta, float p_blend, bool p_seeked, bool p_started);
+ void _animation_process2(float p_delta, bool p_started);
void _animation_update_transforms();
void _animation_process(float p_delta);
void _node_removed(Node *p_node);
+ void _stop_playing_caches();
// bind helpers
PoolVector<String> _get_animation_list() const {
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index 93865cebde..18e609c798 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -440,6 +440,8 @@ void PopupMenu::_notification(int p_what) {
float h;
Size2 icon_size;
+ Color icon_color(1, 1, 1, items[i].disabled ? 0.5 : 1);
+
item_ofs.x += items[i].h_ofs;
if (!items[i].icon.is_null()) {
@@ -463,18 +465,18 @@ void PopupMenu::_notification(int p_what) {
if (items[i].checkable_type) {
Texture *icon = (items[i].checked ? check[items[i].checkable_type - 1] : uncheck[items[i].checkable_type - 1]).ptr();
- icon->draw(ci, item_ofs + Point2(0, Math::floor((h - icon->get_height()) / 2.0)));
+ icon->draw(ci, item_ofs + Point2(0, Math::floor((h - icon->get_height()) / 2.0)), icon_color);
item_ofs.x += icon->get_width() + hseparation;
}
if (!items[i].icon.is_null()) {
- items[i].icon->draw(ci, item_ofs + Point2(0, Math::floor((h - icon_size.height) / 2.0)));
+ items[i].icon->draw(ci, item_ofs + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color);
item_ofs.x += items[i].icon->get_width();
item_ofs.x += hseparation;
}
if (items[i].submenu != "") {
- submenu->draw(ci, Point2(size.width - style->get_margin(MARGIN_RIGHT) - submenu->get_width(), item_ofs.y + Math::floor(h - submenu->get_height()) / 2));
+ submenu->draw(ci, Point2(size.width - style->get_margin(MARGIN_RIGHT) - submenu->get_width(), item_ofs.y + Math::floor(h - submenu->get_height()) / 2), icon_color);
}
item_ofs.y += font->get_ascent();
@@ -913,6 +915,13 @@ void PopupMenu::set_item_multistate(int p_idx, int p_state) {
update();
}
+void PopupMenu::set_item_shortcut_disabled(int p_idx, bool p_disabled) {
+
+ ERR_FAIL_INDEX(p_idx, items.size());
+ items[p_idx].shortcut_is_disabled = p_disabled;
+ update();
+}
+
void PopupMenu::toggle_item_multistate(int p_idx) {
ERR_FAIL_INDEX(p_idx, items.size());
@@ -937,6 +946,12 @@ bool PopupMenu::is_item_radio_checkable(int p_idx) const {
return items[p_idx].checkable_type == Item::CHECKABLE_TYPE_RADIO_BUTTON;
}
+bool PopupMenu::is_item_shortcut_disabled(int p_idx) const {
+
+ ERR_FAIL_INDEX_V(p_idx, items.size(), false);
+ return items[p_idx].shortcut_is_disabled;
+}
+
int PopupMenu::get_item_count() const {
return items.size();
@@ -963,7 +978,7 @@ bool PopupMenu::activate_item_by_event(const Ref<InputEvent> &p_event, bool p_fo
int il = items.size();
for (int i = 0; i < il; i++) {
- if (is_item_disabled(i))
+ if (is_item_disabled(i) || items[i].shortcut_is_disabled)
continue;
if (items[i].shortcut.is_valid() && items[i].shortcut->is_shortcut(p_event) && (items[i].shortcut_is_global || !p_for_global_only)) {
@@ -1248,6 +1263,7 @@ void PopupMenu::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_item_tooltip", "idx", "tooltip"), &PopupMenu::set_item_tooltip);
ClassDB::bind_method(D_METHOD("set_item_shortcut", "idx", "shortcut", "global"), &PopupMenu::set_item_shortcut, DEFVAL(false));
ClassDB::bind_method(D_METHOD("set_item_multistate", "idx", "state"), &PopupMenu::set_item_multistate);
+ ClassDB::bind_method(D_METHOD("set_item_shortcut_disabled", "idx", "disabled"), &PopupMenu::set_item_shortcut_disabled);
ClassDB::bind_method(D_METHOD("toggle_item_checked", "idx"), &PopupMenu::toggle_item_checked);
ClassDB::bind_method(D_METHOD("toggle_item_multistate", "idx"), &PopupMenu::toggle_item_multistate);
@@ -1264,6 +1280,7 @@ void PopupMenu::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_item_separator", "idx"), &PopupMenu::is_item_separator);
ClassDB::bind_method(D_METHOD("is_item_checkable", "idx"), &PopupMenu::is_item_checkable);
ClassDB::bind_method(D_METHOD("is_item_radio_checkable", "idx"), &PopupMenu::is_item_radio_checkable);
+ ClassDB::bind_method(D_METHOD("is_item_shortcut_disabled", "idx"), &PopupMenu::is_item_shortcut_disabled);
ClassDB::bind_method(D_METHOD("get_item_tooltip", "idx"), &PopupMenu::get_item_tooltip);
ClassDB::bind_method(D_METHOD("get_item_shortcut", "idx"), &PopupMenu::get_item_shortcut);
diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h
index fde91bd845..d3ee9be1c0 100644
--- a/scene/gui/popup_menu.h
+++ b/scene/gui/popup_menu.h
@@ -64,6 +64,7 @@ class PopupMenu : public Popup {
int h_ofs;
Ref<ShortCut> shortcut;
bool shortcut_is_global;
+ bool shortcut_is_disabled;
Item() {
checked = false;
@@ -76,6 +77,7 @@ class PopupMenu : public Popup {
_ofs_cache = 0;
h_ofs = 0;
shortcut_is_global = false;
+ shortcut_is_disabled = false;
}
};
@@ -149,6 +151,7 @@ public:
void set_item_h_offset(int p_idx, int p_offset);
void set_item_multistate(int p_idx, int p_state);
void toggle_item_multistate(int p_idx);
+ void set_item_shortcut_disabled(int p_idx, bool p_disabled);
void toggle_item_checked(int p_idx);
@@ -165,6 +168,7 @@ public:
bool is_item_separator(int p_idx) const;
bool is_item_checkable(int p_idx) const;
bool is_item_radio_checkable(int p_idx) const;
+ bool is_item_shortcut_disabled(int p_idx) const;
String get_item_tooltip(int p_idx) const;
Ref<ShortCut> get_item_shortcut(int p_idx) const;
int get_item_state(int p_idx) const;
diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp
index 2dd5c64378..495d618930 100644
--- a/scene/gui/scroll_container.cpp
+++ b/scene/gui/scroll_container.cpp
@@ -241,10 +241,10 @@ void ScrollContainer::_notification(int p_what) {
size -= sb->get_minimum_size();
ofs += sb->get_offset();
- if (h_scroll->is_visible_in_tree())
+ if (h_scroll->is_visible_in_tree() && h_scroll->get_parent() == this) //scrolls may have been moved out for reasons
size.y -= h_scroll->get_minimum_size().y;
- if (v_scroll->is_visible_in_tree())
+ if (v_scroll->is_visible_in_tree() && v_scroll->get_parent() == this) //scrolls may have been moved out for reasons
size.x -= h_scroll->get_minimum_size().x;
for (int i = 0; i < get_child_count(); i++) {
@@ -482,6 +482,16 @@ String ScrollContainer::get_configuration_warning() const {
return "";
}
+HScrollBar *ScrollContainer::get_h_scrollbar() {
+
+ return h_scroll;
+}
+
+VScrollBar *ScrollContainer::get_v_scrollbar() {
+
+ return v_scroll;
+}
+
void ScrollContainer::_bind_methods() {
ClassDB::bind_method(D_METHOD("_scroll_moved"), &ScrollContainer::_scroll_moved);
@@ -498,6 +508,9 @@ void ScrollContainer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_deadzone", "deadzone"), &ScrollContainer::set_deadzone);
ClassDB::bind_method(D_METHOD("get_deadzone"), &ScrollContainer::get_deadzone);
+ ClassDB::bind_method(D_METHOD("get_h_scrollbar"), &ScrollContainer::get_h_scrollbar);
+ ClassDB::bind_method(D_METHOD("get_v_scrollbar"), &ScrollContainer::get_v_scrollbar);
+
ADD_SIGNAL(MethodInfo("scroll_started"));
ADD_SIGNAL(MethodInfo("scroll_ended"));
diff --git a/scene/gui/scroll_container.h b/scene/gui/scroll_container.h
index 3fe1ed447a..abef80294a 100644
--- a/scene/gui/scroll_container.h
+++ b/scene/gui/scroll_container.h
@@ -92,6 +92,9 @@ public:
int get_deadzone() const;
void set_deadzone(int p_deadzone);
+ HScrollBar *get_h_scrollbar();
+ VScrollBar *get_v_scrollbar();
+
virtual bool clips_input() const;
virtual String get_configuration_warning() const;
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index ca9be9823a..fa2f87a130 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -1308,13 +1308,37 @@ void Viewport::_gui_cancel_tooltip() {
}
}
+String Viewport::_gui_get_tooltip(Control *p_control, const Vector2 &p_pos) {
+
+ Vector2 pos = p_pos;
+ String tooltip;
+
+ while (p_control) {
+
+ tooltip = p_control->get_tooltip(pos);
+
+ if (tooltip != String())
+ break;
+ pos = p_control->get_transform().xform(pos);
+
+ if (p_control->data.mouse_filter == Control::MOUSE_FILTER_STOP)
+ break;
+ if (p_control->is_set_as_toplevel())
+ break;
+
+ p_control = p_control->get_parent_control();
+ }
+
+ return tooltip;
+}
+
void Viewport::_gui_show_tooltip() {
if (!gui.tooltip) {
return;
}
- String tooltip = gui.tooltip->get_tooltip(gui.tooltip->get_global_transform().xform_inv(gui.tooltip_pos));
+ String tooltip = _gui_get_tooltip(gui.tooltip, gui.tooltip->get_global_transform().xform_inv(gui.tooltip_pos));
if (tooltip.length() == 0)
return; // bye
@@ -1388,12 +1412,14 @@ void Viewport::_gui_call_input(Control *p_control, const Ref<InputEvent> &p_inpu
Control *control = Object::cast_to<Control>(ci);
if (control) {
- control->call_multilevel(SceneStringNames::get_singleton()->_gui_input, ev);
+
+ control->emit_signal(SceneStringNames::get_singleton()->gui_input, ev); //signal should be first, so it's possible to override an event (and then accept it)
if (gui.key_event_accepted)
break;
if (!control->is_inside_tree())
break;
- control->emit_signal(SceneStringNames::get_singleton()->gui_input, ev);
+ control->call_multilevel(SceneStringNames::get_singleton()->_gui_input, ev);
+
if (!control->is_inside_tree() || control->is_set_as_toplevel())
break;
if (gui.key_event_accepted)
@@ -1864,7 +1890,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
if (gui.tooltip_popup) {
if (can_tooltip) {
- String tooltip = over->get_tooltip(gui.tooltip->get_global_transform().xform_inv(mpos));
+ String tooltip = _gui_get_tooltip(over, gui.tooltip->get_global_transform().xform_inv(mpos));
if (tooltip.length() == 0)
_gui_cancel_tooltip();
@@ -1886,7 +1912,23 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
mm->set_position(pos);
- Control::CursorShape cursor_shape = over->get_cursor_shape(pos);
+ Control::CursorShape cursor_shape = Control::CURSOR_ARROW;
+ {
+ Control *c = over;
+ Vector2 cpos = pos;
+ while (c) {
+ cursor_shape = c->get_cursor_shape();
+ cpos = c->get_transform().xform(cpos);
+ if (cursor_shape != Control::CURSOR_ARROW)
+ break;
+ if (c->data.mouse_filter == Control::MOUSE_FILTER_STOP)
+ break;
+ if (c->is_set_as_toplevel())
+ break;
+ c = c->get_parent_control();
+ }
+ }
+
OS::get_singleton()->set_cursor_shape((OS::CursorShape)cursor_shape);
if (over->can_process()) {
diff --git a/scene/main/viewport.h b/scene/main/viewport.h
index c1ef58de69..3000398540 100644
--- a/scene/main/viewport.h
+++ b/scene/main/viewport.h
@@ -312,6 +312,7 @@ private:
void _gui_remove_root_control(List<Control *>::Element *RI);
void _gui_remove_subwindow_control(List<Control *>::Element *SI);
+ String _gui_get_tooltip(Control *p_control, const Vector2 &p_pos);
void _gui_cancel_tooltip();
void _gui_show_tooltip();
diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp
index 7a1fffaa26..9c95cba357 100644
--- a/scene/resources/animation.cpp
+++ b/scene/resources/animation.cpp
@@ -54,6 +54,15 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) {
} else if (type == "method") {
add_track(TYPE_METHOD);
+ } else if (type == "bezier") {
+
+ add_track(TYPE_BEZIER);
+ } else if (type == "audio") {
+
+ add_track(TYPE_AUDIO);
+ } else if (type == "animation") {
+
+ add_track(TYPE_ANIMATION);
} else {
return false;
@@ -163,7 +172,7 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) {
return true;
- } else {
+ } else if (track_get_type(track) == TYPE_METHOD) {
while (track_get_key_count(track))
track_remove_key(track, 0); //well shouldn't be set anyway
@@ -201,6 +210,114 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) {
}
}
}
+ } else if (track_get_type(track) == TYPE_BEZIER) {
+
+ BezierTrack *bt = static_cast<BezierTrack *>(tracks[track]);
+ Dictionary d = p_value;
+ ERR_FAIL_COND_V(!d.has("times"), false);
+ ERR_FAIL_COND_V(!d.has("points"), false);
+
+ PoolVector<float> times = d["times"];
+ PoolRealArray values = d["points"];
+
+ ERR_FAIL_COND_V(times.size() * 5 != values.size(), false);
+
+ if (times.size()) {
+
+ int valcount = times.size();
+
+ PoolVector<float>::Read rt = times.read();
+ PoolVector<float>::Read rv = values.read();
+
+ bt->values.resize(valcount);
+
+ for (int i = 0; i < valcount; i++) {
+
+ bt->values[i].time = rt[i];
+ bt->values[i].transition = 0; //unused in bezier
+ bt->values[i].value.value = rv[i * 5 + 0];
+ bt->values[i].value.in_handle.x = rv[i * 5 + 1];
+ bt->values[i].value.in_handle.y = rv[i * 5 + 2];
+ bt->values[i].value.out_handle.x = rv[i * 5 + 3];
+ bt->values[i].value.out_handle.y = rv[i * 5 + 4];
+ }
+ }
+
+ return true;
+ } else if (track_get_type(track) == TYPE_AUDIO) {
+
+ AudioTrack *ad = static_cast<AudioTrack *>(tracks[track]);
+ Dictionary d = p_value;
+ ERR_FAIL_COND_V(!d.has("times"), false);
+ ERR_FAIL_COND_V(!d.has("clips"), false);
+
+ PoolVector<float> times = d["times"];
+ Array clips = d["clips"];
+
+ ERR_FAIL_COND_V(clips.size() != times.size(), false);
+
+ if (times.size()) {
+
+ int valcount = times.size();
+
+ PoolVector<float>::Read rt = times.read();
+
+ ad->values.clear();
+
+ for (int i = 0; i < valcount; i++) {
+
+ Dictionary d = clips[i];
+ if (!d.has("start_offset"))
+ continue;
+ if (!d.has("end_offset"))
+ continue;
+ if (!d.has("stream"))
+ continue;
+
+ TKey<AudioKey> ak;
+ ak.time = rt[i];
+ ak.value.start_offset = d["start_offset"];
+ ak.value.end_offset = d["end_offset"];
+ ak.value.stream = d["stream"];
+
+ ad->values.push_back(ak);
+ }
+ }
+
+ return true;
+ } else if (track_get_type(track) == TYPE_ANIMATION) {
+
+ AnimationTrack *an = static_cast<AnimationTrack *>(tracks[track]);
+ Dictionary d = p_value;
+ ERR_FAIL_COND_V(!d.has("times"), false);
+ ERR_FAIL_COND_V(!d.has("clips"), false);
+
+ PoolVector<float> times = d["times"];
+ PoolVector<String> clips = d["clips"];
+
+ ERR_FAIL_COND_V(clips.size() != times.size(), false);
+
+ if (times.size()) {
+
+ int valcount = times.size();
+
+ PoolVector<float>::Read rt = times.read();
+ PoolVector<String>::Read rc = clips.read();
+
+ an->values.resize(valcount);
+
+ for (int i = 0; i < valcount; i++) {
+
+ TKey<StringName> ak;
+ ak.time = rt[i];
+ ak.value = rc[i];
+ an->values[i] = ak;
+ }
+ }
+
+ return true;
+ } else {
+ return false;
}
} else
return false;
@@ -232,6 +349,9 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const {
case TYPE_TRANSFORM: r_ret = "transform"; break;
case TYPE_VALUE: r_ret = "value"; break;
case TYPE_METHOD: r_ret = "method"; break;
+ case TYPE_BEZIER: r_ret = "bezier"; break;
+ case TYPE_AUDIO: r_ret = "audio"; break;
+ case TYPE_ANIMATION: r_ret = "animation"; break;
}
return true;
@@ -329,7 +449,7 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const {
return true;
- } else {
+ } else if (track_get_type(track) == TYPE_METHOD) {
Dictionary d;
@@ -368,6 +488,119 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const {
r_ret = d;
return true;
+ } else if (track_get_type(track) == TYPE_BEZIER) {
+
+ const BezierTrack *bt = static_cast<const BezierTrack *>(tracks[track]);
+
+ Dictionary d;
+
+ PoolVector<float> key_times;
+ PoolVector<float> key_points;
+
+ int kk = bt->values.size();
+
+ key_times.resize(kk);
+ key_points.resize(kk * 5);
+
+ PoolVector<float>::Write wti = key_times.write();
+ PoolVector<float>::Write wpo = key_points.write();
+
+ int idx = 0;
+
+ const TKey<BezierKey> *vls = bt->values.ptr();
+
+ for (int i = 0; i < kk; i++) {
+
+ wti[idx] = vls[i].time;
+ wpo[idx * 5 + 0] = vls[i].value.value;
+ wpo[idx * 5 + 1] = vls[i].value.in_handle.x;
+ wpo[idx * 5 + 2] = vls[i].value.in_handle.y;
+ wpo[idx * 5 + 3] = vls[i].value.out_handle.x;
+ wpo[idx * 5 + 4] = vls[i].value.out_handle.y;
+ idx++;
+ }
+
+ wti = PoolVector<float>::Write();
+ wpo = PoolVector<float>::Write();
+
+ d["times"] = key_times;
+ d["points"] = key_points;
+
+ r_ret = d;
+
+ return true;
+ } else if (track_get_type(track) == TYPE_AUDIO) {
+
+ const AudioTrack *ad = static_cast<const AudioTrack *>(tracks[track]);
+
+ Dictionary d;
+
+ PoolVector<float> key_times;
+ Array clips;
+
+ int kk = ad->values.size();
+
+ key_times.resize(kk);
+
+ PoolVector<float>::Write wti = key_times.write();
+
+ int idx = 0;
+
+ const TKey<AudioKey> *vls = ad->values.ptr();
+
+ for (int i = 0; i < kk; i++) {
+
+ wti[idx] = vls[i].time;
+ Dictionary clip;
+ clip["start_offset"] = vls[i].value.start_offset;
+ clip["end_offset"] = vls[i].value.end_offset;
+ clip["stream"] = vls[i].value.stream;
+ clips.push_back(clip);
+ idx++;
+ }
+
+ wti = PoolVector<float>::Write();
+
+ d["times"] = key_times;
+ d["clips"] = clips;
+
+ r_ret = d;
+
+ return true;
+ } else if (track_get_type(track) == TYPE_ANIMATION) {
+
+ const AnimationTrack *an = static_cast<const AnimationTrack *>(tracks[track]);
+
+ Dictionary d;
+
+ PoolVector<float> key_times;
+ PoolVector<String> clips;
+
+ int kk = an->values.size();
+
+ key_times.resize(kk);
+ clips.resize(kk);
+
+ PoolVector<float>::Write wti = key_times.write();
+ PoolVector<String>::Write wcl = clips.write();
+
+ const TKey<StringName> *vls = an->values.ptr();
+
+ for (int i = 0; i < kk; i++) {
+
+ wti[i] = vls[i].time;
+ wcl[i] = vls[i].value;
+ }
+
+ wti = PoolVector<float>::Write();
+ wcl = PoolVector<String>::Write();
+
+ d["times"] = key_times;
+ d["clips"] = clips;
+
+ r_ret = d;
+
+ return true;
}
} else
return false;
@@ -412,6 +645,21 @@ int Animation::add_track(TrackType p_type, int p_at_pos) {
tracks.insert(p_at_pos, memnew(MethodTrack));
} break;
+ case TYPE_BEZIER: {
+
+ tracks.insert(p_at_pos, memnew(BezierTrack));
+
+ } break;
+ case TYPE_AUDIO: {
+
+ tracks.insert(p_at_pos, memnew(AudioTrack));
+
+ } break;
+ case TYPE_ANIMATION: {
+
+ tracks.insert(p_at_pos, memnew(AnimationTrack));
+
+ } break;
default: {
ERR_PRINT("Unknown track type");
@@ -446,6 +694,24 @@ void Animation::remove_track(int p_track) {
_clear(mt->methods);
} break;
+ case TYPE_BEZIER: {
+
+ BezierTrack *bz = static_cast<BezierTrack *>(t);
+ _clear(bz->values);
+
+ } break;
+ case TYPE_AUDIO: {
+
+ AudioTrack *ad = static_cast<AudioTrack *>(t);
+ _clear(ad->values);
+
+ } break;
+ case TYPE_ANIMATION: {
+
+ AnimationTrack *an = static_cast<AnimationTrack *>(t);
+ _clear(an->values);
+
+ } break;
}
memdelete(t);
@@ -642,6 +908,27 @@ void Animation::track_remove_key(int p_track, int p_idx) {
mt->methods.remove(p_idx);
} break;
+ case TYPE_BEZIER: {
+
+ BezierTrack *bz = static_cast<BezierTrack *>(t);
+ ERR_FAIL_INDEX(p_idx, bz->values.size());
+ bz->values.remove(p_idx);
+
+ } break;
+ case TYPE_AUDIO: {
+
+ AudioTrack *ad = static_cast<AudioTrack *>(t);
+ ERR_FAIL_INDEX(p_idx, ad->values.size());
+ ad->values.remove(p_idx);
+
+ } break;
+ case TYPE_ANIMATION: {
+
+ AnimationTrack *an = static_cast<AnimationTrack *>(t);
+ ERR_FAIL_INDEX(p_idx, an->values.size());
+ an->values.remove(p_idx);
+
+ } break;
}
emit_changed();
@@ -686,6 +973,39 @@ int Animation::track_find_key(int p_track, float p_time, bool p_exact) const {
return k;
} break;
+ case TYPE_BEZIER: {
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+ int k = _find(bt->values, p_time);
+ if (k < 0 || k >= bt->values.size())
+ return -1;
+ if (bt->values[k].time != p_time && p_exact)
+ return -1;
+ return k;
+
+ } break;
+ case TYPE_AUDIO: {
+
+ AudioTrack *at = static_cast<AudioTrack *>(t);
+ int k = _find(at->values, p_time);
+ if (k < 0 || k >= at->values.size())
+ return -1;
+ if (at->values[k].time != p_time && p_exact)
+ return -1;
+ return k;
+
+ } break;
+ case TYPE_ANIMATION: {
+
+ AnimationTrack *at = static_cast<AnimationTrack *>(t);
+ int k = _find(at->values, p_time);
+ if (k < 0 || k >= at->values.size())
+ return -1;
+ if (at->values[k].time != p_time && p_exact)
+ return -1;
+ return k;
+
+ } break;
}
return -1;
@@ -748,6 +1068,51 @@ void Animation::track_insert_key(int p_track, float p_time, const Variant &p_key
_insert(p_time, mt->methods, k);
} break;
+ case TYPE_BEZIER: {
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+
+ Array arr = p_key;
+ ERR_FAIL_COND(arr.size() != 5);
+
+ TKey<BezierKey> k;
+ k.time = p_time;
+ k.value.value = arr[0];
+ k.value.in_handle.x = arr[1];
+ k.value.in_handle.y = arr[2];
+ k.value.out_handle.x = arr[3];
+ k.value.out_handle.y = arr[4];
+ _insert(p_time, bt->values, k);
+
+ } break;
+ case TYPE_AUDIO: {
+
+ AudioTrack *at = static_cast<AudioTrack *>(t);
+
+ Dictionary k = p_key;
+ ERR_FAIL_COND(!k.has("start_offset"));
+ ERR_FAIL_COND(!k.has("end_offset"));
+ ERR_FAIL_COND(!k.has("stream"));
+
+ TKey<AudioKey> ak;
+ ak.time = p_time;
+ ak.value.start_offset = k["start_offset"];
+ ak.value.end_offset = k["end_offset"];
+ ak.value.stream = k["stream"];
+ _insert(p_time, at->values, ak);
+
+ } break;
+ case TYPE_ANIMATION: {
+
+ AnimationTrack *at = static_cast<AnimationTrack *>(t);
+
+ TKey<StringName> ak;
+ ak.time = p_time;
+ ak.value = p_key;
+
+ _insert(p_time, at->values, ak);
+
+ } break;
}
emit_changed();
@@ -776,6 +1141,21 @@ int Animation::track_get_key_count(int p_track) const {
MethodTrack *mt = static_cast<MethodTrack *>(t);
return mt->methods.size();
} break;
+ case TYPE_BEZIER: {
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+ return bt->values.size();
+ } break;
+ case TYPE_AUDIO: {
+
+ AudioTrack *at = static_cast<AudioTrack *>(t);
+ return at->values.size();
+ } break;
+ case TYPE_ANIMATION: {
+
+ AnimationTrack *at = static_cast<AnimationTrack *>(t);
+ return at->values.size();
+ } break;
}
ERR_FAIL_V(-1);
@@ -817,6 +1197,41 @@ Variant Animation::track_get_key_value(int p_track, int p_key_idx) const {
return d;
} break;
+ case TYPE_BEZIER: {
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+ ERR_FAIL_INDEX_V(p_key_idx, bt->values.size(), Variant());
+
+ Array arr;
+ arr.resize(5);
+ arr[0] = bt->values[p_key_idx].value.value;
+ arr[1] = bt->values[p_key_idx].value.in_handle.x;
+ arr[2] = bt->values[p_key_idx].value.in_handle.y;
+ arr[3] = bt->values[p_key_idx].value.out_handle.x;
+ arr[4] = bt->values[p_key_idx].value.out_handle.y;
+ return arr;
+
+ } break;
+ case TYPE_AUDIO: {
+
+ AudioTrack *at = static_cast<AudioTrack *>(t);
+ ERR_FAIL_INDEX_V(p_key_idx, at->values.size(), Variant());
+
+ Dictionary k;
+ k["start_offset"] = at->values[p_key_idx].value.start_offset;
+ k["end_offset"] = at->values[p_key_idx].value.end_offset;
+ k["stream"] = at->values[p_key_idx].value.stream;
+ return k;
+
+ } break;
+ case TYPE_ANIMATION: {
+
+ AnimationTrack *at = static_cast<AnimationTrack *>(t);
+ ERR_FAIL_INDEX_V(p_key_idx, at->values.size(), Variant());
+
+ return at->values[p_key_idx].value;
+
+ } break;
}
ERR_FAIL_V(Variant());
@@ -849,6 +1264,27 @@ float Animation::track_get_key_time(int p_track, int p_key_idx) const {
return mt->methods[p_key_idx].time;
} break;
+ case TYPE_BEZIER: {
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+ ERR_FAIL_INDEX_V(p_key_idx, bt->values.size(), -1);
+ return bt->values[p_key_idx].time;
+
+ } break;
+ case TYPE_AUDIO: {
+
+ AudioTrack *at = static_cast<AudioTrack *>(t);
+ ERR_FAIL_INDEX_V(p_key_idx, at->values.size(), -1);
+ return at->values[p_key_idx].time;
+
+ } break;
+ case TYPE_ANIMATION: {
+
+ AnimationTrack *at = static_cast<AnimationTrack *>(t);
+ ERR_FAIL_INDEX_V(p_key_idx, at->values.size(), -1);
+ return at->values[p_key_idx].time;
+
+ } break;
}
ERR_FAIL_V(-1);
@@ -881,6 +1317,18 @@ float Animation::track_get_key_transition(int p_track, int p_key_idx) const {
return mt->methods[p_key_idx].transition;
} break;
+ case TYPE_BEZIER: {
+
+ return 1; //bezier does not really use transitions
+ } break;
+ case TYPE_AUDIO: {
+
+ return 1; //audio does not really use transitions
+ } break;
+ case TYPE_ANIMATION: {
+
+ return 1; //animation does not really use transitions
+ } break;
}
ERR_FAIL_V(0);
@@ -923,6 +1371,42 @@ void Animation::track_set_key_value(int p_track, int p_key_idx, const Variant &p
if (d.has("args"))
mt->methods[p_key_idx].params = d["args"];
} break;
+ case TYPE_BEZIER: {
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+ ERR_FAIL_INDEX(p_key_idx, bt->values.size());
+
+ Array arr = p_value;
+ ERR_FAIL_COND(arr.size() != 5);
+
+ bt->values[p_key_idx].value.value = arr[0];
+ bt->values[p_key_idx].value.in_handle.x = arr[1];
+ bt->values[p_key_idx].value.in_handle.y = arr[2];
+ bt->values[p_key_idx].value.out_handle.x = arr[3];
+ bt->values[p_key_idx].value.out_handle.y = arr[4];
+
+ } break;
+ case TYPE_AUDIO: {
+
+ AudioTrack *at = static_cast<AudioTrack *>(t);
+
+ Dictionary k = p_value;
+ ERR_FAIL_COND(!k.has("start_offset"));
+ ERR_FAIL_COND(!k.has("end_offset"));
+ ERR_FAIL_COND(!k.has("stream"));
+
+ at->values[p_key_idx].value.start_offset = k["start_offset"];
+ at->values[p_key_idx].value.end_offset = k["end_offset"];
+ at->values[p_key_idx].value.stream = k["stream"];
+
+ } break;
+ case TYPE_ANIMATION: {
+
+ AnimationTrack *at = static_cast<AnimationTrack *>(t);
+
+ at->values[p_key_idx].value = p_value;
+
+ } break;
}
}
@@ -953,6 +1437,11 @@ void Animation::track_set_key_transition(int p_track, int p_key_idx, float p_tra
mt->methods[p_key_idx].transition = p_transition;
} break;
+ case TYPE_BEZIER:
+ case TYPE_AUDIO:
+ case TYPE_ANIMATION: {
+ // they dont use transition
+ } break;
}
}
@@ -1410,7 +1899,7 @@ void Animation::value_track_set_update_mode(int p_track, UpdateMode p_mode) {
ERR_FAIL_INDEX(p_track, tracks.size());
Track *t = tracks[p_track];
ERR_FAIL_COND(t->type != TYPE_VALUE);
- ERR_FAIL_INDEX(p_mode, 3);
+ ERR_FAIL_INDEX(p_mode, 4);
ValueTrack *vt = static_cast<ValueTrack *>(t);
vt->update_mode = p_mode;
@@ -1426,6 +1915,161 @@ Animation::UpdateMode Animation::value_track_get_update_mode(int p_track) const
return vt->update_mode;
}
+template <class T>
+void Animation::_track_get_key_indices_in_range(const Vector<T> &p_array, float from_time, float to_time, List<int> *p_indices) const {
+
+ if (from_time != length && to_time == length)
+ to_time = length * 1.01; //include a little more if at the end
+
+ int to = _find(p_array, to_time);
+
+ // can't really send the events == time, will be sent in the next frame.
+ // if event>=len then it will probably never be requested by the anim player.
+
+ if (to >= 0 && p_array[to].time >= to_time)
+ to--;
+
+ if (to < 0)
+ return; // not bother
+
+ int from = _find(p_array, from_time);
+
+ // position in the right first event.+
+ if (from < 0 || p_array[from].time < from_time)
+ from++;
+
+ int max = p_array.size();
+
+ for (int i = from; i <= to; i++) {
+
+ ERR_CONTINUE(i < 0 || i >= max); // shouldn't happen
+ p_indices->push_back(i);
+ }
+}
+
+void Animation::track_get_key_indices_in_range(int p_track, float p_time, float p_delta, List<int> *p_indices) const {
+
+ ERR_FAIL_INDEX(p_track, tracks.size());
+ const Track *t = tracks[p_track];
+
+ float from_time = p_time - p_delta;
+ float to_time = p_time;
+
+ if (from_time > to_time)
+ SWAP(from_time, to_time);
+
+ if (loop) {
+
+ if (from_time > length || from_time < 0)
+ from_time = Math::fposmod(from_time, length);
+
+ if (to_time > length || to_time < 0)
+ to_time = Math::fposmod(to_time, length);
+
+ if (from_time > to_time) {
+ // handle loop by splitting
+
+ switch (t->type) {
+
+ case TYPE_TRANSFORM: {
+
+ const TransformTrack *tt = static_cast<const TransformTrack *>(t);
+ _track_get_key_indices_in_range(tt->transforms, from_time, length, p_indices);
+ _track_get_key_indices_in_range(tt->transforms, 0, to_time, p_indices);
+
+ } break;
+ case TYPE_VALUE: {
+
+ const ValueTrack *vt = static_cast<const ValueTrack *>(t);
+ _track_get_key_indices_in_range(vt->values, from_time, length, p_indices);
+ _track_get_key_indices_in_range(vt->values, 0, to_time, p_indices);
+
+ } break;
+ case TYPE_METHOD: {
+
+ const MethodTrack *mt = static_cast<const MethodTrack *>(t);
+ _track_get_key_indices_in_range(mt->methods, from_time, length, p_indices);
+ _track_get_key_indices_in_range(mt->methods, 0, to_time, p_indices);
+
+ } break;
+ case TYPE_BEZIER: {
+
+ const BezierTrack *bz = static_cast<const BezierTrack *>(t);
+ _track_get_key_indices_in_range(bz->values, from_time, length, p_indices);
+ _track_get_key_indices_in_range(bz->values, 0, to_time, p_indices);
+
+ } break;
+ case TYPE_AUDIO: {
+
+ const AudioTrack *ad = static_cast<const AudioTrack *>(t);
+ _track_get_key_indices_in_range(ad->values, from_time, length, p_indices);
+ _track_get_key_indices_in_range(ad->values, 0, to_time, p_indices);
+
+ } break;
+ case TYPE_ANIMATION: {
+
+ const AnimationTrack *an = static_cast<const AnimationTrack *>(t);
+ _track_get_key_indices_in_range(an->values, from_time, length, p_indices);
+ _track_get_key_indices_in_range(an->values, 0, to_time, p_indices);
+
+ } break;
+ }
+ return;
+ }
+ } else {
+
+ if (from_time < 0)
+ from_time = 0;
+ if (from_time > length)
+ from_time = length;
+
+ if (to_time < 0)
+ to_time = 0;
+ if (to_time > length)
+ to_time = length;
+ }
+
+ switch (t->type) {
+
+ case TYPE_TRANSFORM: {
+
+ const TransformTrack *tt = static_cast<const TransformTrack *>(t);
+ _track_get_key_indices_in_range(tt->transforms, from_time, to_time, p_indices);
+
+ } break;
+ case TYPE_VALUE: {
+
+ const ValueTrack *vt = static_cast<const ValueTrack *>(t);
+ _track_get_key_indices_in_range(vt->values, from_time, to_time, p_indices);
+
+ } break;
+ case TYPE_METHOD: {
+
+ const MethodTrack *mt = static_cast<const MethodTrack *>(t);
+ _track_get_key_indices_in_range(mt->methods, from_time, to_time, p_indices);
+
+ } break;
+ case TYPE_BEZIER: {
+
+ const BezierTrack *bz = static_cast<const BezierTrack *>(t);
+ _track_get_key_indices_in_range(bz->values, from_time, to_time, p_indices);
+
+ } break;
+ case TYPE_AUDIO: {
+
+ const AudioTrack *ad = static_cast<const AudioTrack *>(t);
+ _track_get_key_indices_in_range(ad->values, from_time, to_time, p_indices);
+
+ } break;
+ case TYPE_ANIMATION: {
+
+ const AnimationTrack *an = static_cast<const AnimationTrack *>(t);
+ _track_get_key_indices_in_range(an->values, from_time, to_time, p_indices);
+
+ } break;
+ }
+}
+
void Animation::_method_track_get_key_indices_in_range(const MethodTrack *mt, float from_time, float to_time, List<int> *p_indices) const {
if (from_time != length && to_time == length)
@@ -1527,6 +2171,357 @@ StringName Animation::method_track_get_name(int p_track, int p_key_idx) const {
return pm->methods[p_key_idx].method;
}
+int Animation::bezier_track_insert_key(int p_track, float p_time, float p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle) {
+
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), -1);
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_BEZIER, -1);
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+
+ TKey<BezierKey> k;
+ k.time = p_time;
+ k.value.value = p_value;
+ k.value.in_handle = p_in_handle;
+ if (k.value.in_handle.x > 0) {
+ k.value.in_handle.x = 0;
+ }
+ k.value.out_handle = p_out_handle;
+ if (k.value.out_handle.x < 0) {
+ k.value.out_handle.x = 0;
+ }
+
+ int key = _insert(p_time, bt->values, k);
+
+ emit_changed();
+
+ return key;
+}
+
+void Animation::bezier_track_set_key_value(int p_track, int p_index, float p_value) {
+
+ ERR_FAIL_INDEX(p_track, tracks.size());
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND(t->type != TYPE_BEZIER);
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+
+ ERR_FAIL_INDEX(p_index, bt->values.size());
+
+ bt->values[p_index].value.value = p_value;
+ emit_changed();
+}
+
+void Animation::bezier_track_set_key_in_handle(int p_track, int p_index, const Vector2 &p_handle) {
+
+ ERR_FAIL_INDEX(p_track, tracks.size());
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND(t->type != TYPE_BEZIER);
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+
+ ERR_FAIL_INDEX(p_index, bt->values.size());
+
+ bt->values[p_index].value.in_handle = p_handle;
+ if (bt->values[p_index].value.in_handle.x > 0) {
+ bt->values[p_index].value.in_handle.x = 0;
+ }
+ emit_changed();
+}
+void Animation::bezier_track_set_key_out_handle(int p_track, int p_index, const Vector2 &p_handle) {
+
+ ERR_FAIL_INDEX(p_track, tracks.size());
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND(t->type != TYPE_BEZIER);
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+
+ ERR_FAIL_INDEX(p_index, bt->values.size());
+
+ bt->values[p_index].value.out_handle = p_handle;
+ if (bt->values[p_index].value.out_handle.x < 0) {
+ bt->values[p_index].value.out_handle.x = 0;
+ }
+ emit_changed();
+}
+float Animation::bezier_track_get_key_value(int p_track, int p_index) const {
+
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), 0);
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_BEZIER, 0);
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+
+ ERR_FAIL_INDEX_V(p_index, bt->values.size(), 0);
+
+ return bt->values[p_index].value.value;
+}
+Vector2 Animation::bezier_track_get_key_in_handle(int p_track, int p_index) const {
+
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), Vector2());
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_BEZIER, Vector2());
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+
+ ERR_FAIL_INDEX_V(p_index, bt->values.size(), Vector2());
+
+ return bt->values[p_index].value.in_handle;
+}
+Vector2 Animation::bezier_track_get_key_out_handle(int p_track, int p_index) const {
+
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), Vector2());
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_BEZIER, Vector2());
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+
+ ERR_FAIL_INDEX_V(p_index, bt->values.size(), Vector2());
+
+ return bt->values[p_index].value.out_handle;
+}
+
+static _FORCE_INLINE_ Vector2 _bezier_interp(real_t t, const Vector2 &start, const Vector2 &control_1, const Vector2 &control_2, const Vector2 &end) {
+ /* Formula from Wikipedia article on Bezier curves. */
+ real_t omt = (1.0 - t);
+ real_t omt2 = omt * omt;
+ real_t omt3 = omt2 * omt;
+ real_t t2 = t * t;
+ real_t t3 = t2 * t;
+
+ return start * omt3 + control_1 * omt2 * t * 3.0 + control_2 * omt * t2 * 3.0 + end * t3;
+}
+
+float Animation::bezier_track_interpolate(int p_track, float p_time) const {
+ //this uses a different interpolation scheme
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), 0);
+ Track *track = tracks[p_track];
+ ERR_FAIL_COND_V(track->type != TYPE_BEZIER, 0);
+
+ BezierTrack *bt = static_cast<BezierTrack *>(track);
+
+ int len = _find(bt->values, length) + 1; // try to find last key (there may be more past the end)
+
+ if (len <= 0) {
+ // (-1 or -2 returned originally) (plus one above)
+ return 0;
+ } else if (len == 1) { // one key found (0+1), return it
+ return bt->values[0].value.value;
+ }
+
+ int idx = _find(bt->values, p_time);
+
+ ERR_FAIL_COND_V(idx == -2, 0);
+
+ //there really is no looping interpolation on bezier
+
+ if (idx < 0) {
+ return bt->values[0].value.value;
+ }
+
+ if (idx >= bt->values.size() - 1) {
+ return bt->values[bt->values.size() - 1].value.value;
+ }
+
+ float t = p_time - bt->values[idx].time;
+
+ int iterations = 10;
+
+ float low = 0;
+ float high = bt->values[idx + 1].time - bt->values[idx].time;
+ float middle = 0;
+
+ Vector2 start(0, bt->values[idx].value.value);
+ Vector2 start_out = start + bt->values[idx].value.out_handle;
+ Vector2 end(high, bt->values[idx + 1].value.value);
+ Vector2 end_in = end + bt->values[idx + 1].value.in_handle;
+
+ //narrow high and low as much as possible
+ for (int i = 0; i < iterations; i++) {
+
+ middle = (low + high) / 2;
+
+ Vector2 interp = _bezier_interp(middle, start, start_out, end_in, end);
+
+ if (interp.x < t) {
+ low = middle;
+ } else {
+ high = middle;
+ }
+ }
+
+ //interpolate the result:
+ Vector2 low_pos = _bezier_interp(low, start, start_out, end_in, end);
+ Vector2 high_pos = _bezier_interp(high, start, start_out, end_in, end);
+
+ float c = (t - low_pos.x) / (high_pos.x - low_pos.x);
+
+ return low_pos.linear_interpolate(high_pos, c).y;
+}
+
+int Animation::audio_track_insert_key(int p_track, float p_time, const RES &p_stream, float p_start_offset, float p_end_offset) {
+
+ print_line("really insert key? ");
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), -1);
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_AUDIO, -1);
+
+ AudioTrack *at = static_cast<AudioTrack *>(t);
+
+ TKey<AudioKey> k;
+ k.time = p_time;
+ k.value.stream = p_stream;
+ k.value.start_offset = p_start_offset;
+ if (k.value.start_offset < 0)
+ k.value.start_offset = 0;
+ k.value.end_offset = p_end_offset;
+ if (k.value.end_offset < 0)
+ k.value.end_offset = 0;
+
+ int key = _insert(p_time, at->values, k);
+
+ emit_changed();
+
+ return key;
+}
+
+void Animation::audio_track_set_key_stream(int p_track, int p_key, const RES &p_stream) {
+
+ ERR_FAIL_INDEX(p_track, tracks.size());
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND(t->type != TYPE_AUDIO);
+
+ AudioTrack *at = static_cast<AudioTrack *>(t);
+
+ ERR_FAIL_INDEX(p_key, at->values.size());
+
+ at->values[p_key].value.stream = p_stream;
+
+ emit_changed();
+}
+
+void Animation::audio_track_set_key_start_offset(int p_track, int p_key, float p_offset) {
+
+ ERR_FAIL_INDEX(p_track, tracks.size());
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND(t->type != TYPE_AUDIO);
+
+ AudioTrack *at = static_cast<AudioTrack *>(t);
+
+ ERR_FAIL_INDEX(p_key, at->values.size());
+
+ if (p_offset < 0)
+ p_offset = 0;
+
+ at->values[p_key].value.start_offset = p_offset;
+
+ emit_changed();
+}
+
+void Animation::audio_track_set_key_end_offset(int p_track, int p_key, float p_offset) {
+
+ ERR_FAIL_INDEX(p_track, tracks.size());
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND(t->type != TYPE_AUDIO);
+
+ AudioTrack *at = static_cast<AudioTrack *>(t);
+
+ ERR_FAIL_INDEX(p_key, at->values.size());
+
+ if (p_offset < 0)
+ p_offset = 0;
+
+ at->values[p_key].value.end_offset = p_offset;
+
+ emit_changed();
+}
+
+RES Animation::audio_track_get_key_stream(int p_track, int p_key) const {
+
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), RES());
+ const Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_AUDIO, RES());
+
+ const AudioTrack *at = static_cast<const AudioTrack *>(t);
+
+ ERR_FAIL_INDEX_V(p_key, at->values.size(), RES());
+
+ return at->values[p_key].value.stream;
+}
+float Animation::audio_track_get_key_start_offset(int p_track, int p_key) const {
+
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), 0);
+ const Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_AUDIO, 0);
+
+ const AudioTrack *at = static_cast<const AudioTrack *>(t);
+
+ ERR_FAIL_INDEX_V(p_key, at->values.size(), 0);
+
+ return at->values[p_key].value.start_offset;
+}
+float Animation::audio_track_get_key_end_offset(int p_track, int p_key) const {
+
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), 0);
+ const Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_AUDIO, 0);
+
+ const AudioTrack *at = static_cast<const AudioTrack *>(t);
+
+ ERR_FAIL_INDEX_V(p_key, at->values.size(), 0);
+
+ return at->values[p_key].value.end_offset;
+}
+
+//
+
+int Animation::animation_track_insert_key(int p_track, float p_time, const StringName &p_animation) {
+
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), -1);
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_ANIMATION, -1);
+
+ AnimationTrack *at = static_cast<AnimationTrack *>(t);
+
+ TKey<StringName> k;
+ k.time = p_time;
+ k.value = p_animation;
+
+ int key = _insert(p_time, at->values, k);
+
+ emit_changed();
+
+ return key;
+}
+
+void Animation::animation_track_set_key_animation(int p_track, int p_key, const StringName &p_animation) {
+
+ ERR_FAIL_INDEX(p_track, tracks.size());
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND(t->type != TYPE_ANIMATION);
+
+ AnimationTrack *at = static_cast<AnimationTrack *>(t);
+
+ ERR_FAIL_INDEX(p_key, at->values.size());
+
+ at->values[p_key].value = p_animation;
+
+ emit_changed();
+}
+
+StringName Animation::animation_track_get_key_animation(int p_track, int p_key) const {
+
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), StringName());
+ const Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_ANIMATION, StringName());
+
+ const AnimationTrack *at = static_cast<const AnimationTrack *>(t);
+
+ ERR_FAIL_INDEX_V(p_key, at->values.size(), StringName());
+
+ return at->values[p_key].value;
+}
+
void Animation::set_length(float p_length) {
ERR_FAIL_COND(length < 0);
@@ -1592,6 +2587,16 @@ void Animation::track_move_down(int p_track) {
emit_changed();
}
+void Animation::track_swap(int p_track, int p_with_track) {
+
+ ERR_FAIL_INDEX(p_track, tracks.size());
+ ERR_FAIL_INDEX(p_with_track, tracks.size());
+ if (p_track == p_with_track)
+ return;
+ SWAP(tracks[p_track], tracks[p_with_track]);
+ emit_changed();
+}
+
void Animation::set_step(float p_step) {
step = p_step;
@@ -1631,6 +2636,7 @@ void Animation::_bind_methods() {
ClassDB::bind_method(D_METHOD("track_move_up", "idx"), &Animation::track_move_up);
ClassDB::bind_method(D_METHOD("track_move_down", "idx"), &Animation::track_move_down);
+ ClassDB::bind_method(D_METHOD("track_swap", "idx", "with_idx"), &Animation::track_swap);
ClassDB::bind_method(D_METHOD("track_set_imported", "idx", "imported"), &Animation::track_set_imported);
ClassDB::bind_method(D_METHOD("track_is_imported", "idx"), &Animation::track_is_imported);
@@ -1667,6 +2673,30 @@ void Animation::_bind_methods() {
ClassDB::bind_method(D_METHOD("method_track_get_name", "idx", "key_idx"), &Animation::method_track_get_name);
ClassDB::bind_method(D_METHOD("method_track_get_params", "idx", "key_idx"), &Animation::method_track_get_params);
+ ClassDB::bind_method(D_METHOD("bezier_track_insert_key", "track", "time", "value", "in_handle", "out_handle"), &Animation::bezier_track_insert_key, DEFVAL(Vector2()), DEFVAL(Vector2()));
+
+ ClassDB::bind_method(D_METHOD("bezier_track_set_key_value", "idx", "key_idx", "value"), &Animation::bezier_track_set_key_value);
+ ClassDB::bind_method(D_METHOD("bezier_track_set_key_in_handle", "idx", "key_idx", "in_handle"), &Animation::bezier_track_set_key_in_handle);
+ ClassDB::bind_method(D_METHOD("bezier_track_set_key_out_handle", "idx", "key_idx", "out_handle"), &Animation::bezier_track_set_key_out_handle);
+
+ ClassDB::bind_method(D_METHOD("bezier_track_get_key_value", "idx", "key_idx"), &Animation::bezier_track_get_key_value);
+ ClassDB::bind_method(D_METHOD("bezier_track_get_key_in_handle", "idx", "key_idx"), &Animation::bezier_track_get_key_in_handle);
+ ClassDB::bind_method(D_METHOD("bezier_track_get_key_out_handle", "idx", "key_idx"), &Animation::bezier_track_get_key_out_handle);
+
+ ClassDB::bind_method(D_METHOD("bezier_track_interpolate", "track", "time"), &Animation::bezier_track_interpolate);
+
+ ClassDB::bind_method(D_METHOD("audio_track_insert_key", "track", "time", "stream", "start_offset", "end_offset"), &Animation::audio_track_insert_key, DEFVAL(0), DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("audio_track_set_key_stream", "idx", "key_idx", "stream"), &Animation::audio_track_set_key_stream);
+ ClassDB::bind_method(D_METHOD("audio_track_set_key_start_offset", "idx", "key_idx", "offset"), &Animation::audio_track_set_key_start_offset);
+ ClassDB::bind_method(D_METHOD("audio_track_set_key_end_offset", "idx", "key_idx", "offset"), &Animation::audio_track_set_key_end_offset);
+ ClassDB::bind_method(D_METHOD("audio_track_get_key_stream", "idx", "key_idx"), &Animation::audio_track_get_key_stream);
+ ClassDB::bind_method(D_METHOD("audio_track_get_key_start_offset", "idx", "key_idx"), &Animation::audio_track_get_key_start_offset);
+ ClassDB::bind_method(D_METHOD("audio_track_get_key_end_offset", "idx", "key_idx"), &Animation::audio_track_get_key_end_offset);
+
+ ClassDB::bind_method(D_METHOD("animation_track_insert_key", "track", "time", "animation"), &Animation::animation_track_insert_key);
+ ClassDB::bind_method(D_METHOD("animation_track_set_key_animation", "idx", "key_idx", "animation"), &Animation::animation_track_set_key_animation);
+ ClassDB::bind_method(D_METHOD("animation_track_get_key_animation", "idx", "key_idx"), &Animation::animation_track_get_key_animation);
+
ClassDB::bind_method(D_METHOD("set_length", "time_sec"), &Animation::set_length);
ClassDB::bind_method(D_METHOD("get_length"), &Animation::get_length);
@@ -1686,6 +2716,9 @@ void Animation::_bind_methods() {
BIND_ENUM_CONSTANT(TYPE_VALUE);
BIND_ENUM_CONSTANT(TYPE_TRANSFORM);
BIND_ENUM_CONSTANT(TYPE_METHOD);
+ BIND_ENUM_CONSTANT(TYPE_BEZIER);
+ BIND_ENUM_CONSTANT(TYPE_AUDIO);
+ BIND_ENUM_CONSTANT(TYPE_ANIMATION);
BIND_ENUM_CONSTANT(INTERPOLATION_NEAREST);
BIND_ENUM_CONSTANT(INTERPOLATION_LINEAR);
@@ -1694,6 +2727,7 @@ void Animation::_bind_methods() {
BIND_ENUM_CONSTANT(UPDATE_CONTINUOUS);
BIND_ENUM_CONSTANT(UPDATE_DISCRETE);
BIND_ENUM_CONSTANT(UPDATE_TRIGGER);
+ BIND_ENUM_CONSTANT(UPDATE_CAPTURE);
}
void Animation::clear() {
diff --git a/scene/resources/animation.h b/scene/resources/animation.h
index 73691a69f2..a41e6ea5d7 100644
--- a/scene/resources/animation.h
+++ b/scene/resources/animation.h
@@ -45,6 +45,9 @@ public:
TYPE_VALUE, ///< Set a value in a property, can be interpolated.
TYPE_TRANSFORM, ///< Transform a node or a bone.
TYPE_METHOD, ///< Call any method on a specific node.
+ TYPE_BEZIER, ///< Bezier curve
+ TYPE_AUDIO,
+ TYPE_ANIMATION,
};
enum InterpolationType {
@@ -57,6 +60,7 @@ public:
UPDATE_CONTINUOUS,
UPDATE_DISCRETE,
UPDATE_TRIGGER,
+ UPDATE_CAPTURE,
};
@@ -137,6 +141,55 @@ private:
MethodTrack() { type = TYPE_METHOD; }
};
+ /* BEZIER TRACK */
+
+ struct BezierKey {
+ Vector2 in_handle; //relative (x always <0)
+ Vector2 out_handle; //relative (x always >0)
+ float value;
+ };
+
+ struct BezierTrack : public Track {
+
+ Vector<TKey<BezierKey> > values;
+
+ BezierTrack() {
+ type = TYPE_BEZIER;
+ }
+ };
+
+ /* AUDIO TRACK */
+
+ struct AudioKey {
+ RES stream;
+ float start_offset; //offset from start
+ float end_offset; //offset from end, if 0 then full length or infinite
+ AudioKey() {
+ start_offset = 0;
+ end_offset = 0;
+ }
+ };
+
+ struct AudioTrack : public Track {
+
+ Vector<TKey<AudioKey> > values;
+
+ AudioTrack() {
+ type = TYPE_AUDIO;
+ }
+ };
+
+ /* AUDIO TRACK */
+
+ struct AnimationTrack : public Track {
+
+ Vector<TKey<StringName> > values;
+
+ AnimationTrack() {
+ type = TYPE_ANIMATION;
+ }
+ };
+
Vector<Track *> tracks;
/*
@@ -168,6 +221,9 @@ private:
template <class T>
_FORCE_INLINE_ T _interpolate(const Vector<TKey<T> > &p_keys, float p_time, InterpolationType p_interp, bool p_loop_wrap, bool *p_ok) const;
+ template <class T>
+ _FORCE_INLINE_ void _track_get_key_indices_in_range(const Vector<T> &p_array, float from_time, float to_time, List<int> *p_indices) const;
+
_FORCE_INLINE_ void _value_track_get_key_indices_in_range(const ValueTrack *vt, float from_time, float to_time, List<int> *p_indices) const;
_FORCE_INLINE_ void _method_track_get_key_indices_in_range(const MethodTrack *mt, float from_time, float to_time, List<int> *p_indices) const;
@@ -238,6 +294,7 @@ public:
void track_move_up(int p_track);
void track_move_down(int p_track);
+ void track_swap(int p_track, int p_with_track);
void track_set_imported(int p_track, bool p_imported);
bool track_is_imported(int p_track) const;
@@ -245,7 +302,6 @@ public:
void track_set_enabled(int p_track, bool p_enabled);
bool track_is_enabled(int p_track) const;
- int transform_track_insert_key(int p_track, float p_time, const Vector3 p_loc, const Quat &p_rot = Quat(), const Vector3 &p_scale = Vector3());
void track_insert_key(int p_track, float p_time, const Variant &p_key, float p_transition = 1);
void track_set_key_transition(int p_track, int p_key_idx, float p_transition);
void track_set_key_value(int p_track, int p_key_idx, const Variant &p_value);
@@ -257,10 +313,33 @@ public:
float track_get_key_time(int p_track, int p_key_idx) const;
float track_get_key_transition(int p_track, int p_key_idx) const;
+ int transform_track_insert_key(int p_track, float p_time, const Vector3 p_loc, const Quat &p_rot = Quat(), const Vector3 &p_scale = Vector3());
Error transform_track_get_key(int p_track, int p_key, Vector3 *r_loc, Quat *r_rot, Vector3 *r_scale) const;
void track_set_interpolation_type(int p_track, InterpolationType p_interp);
InterpolationType track_get_interpolation_type(int p_track) const;
+ int bezier_track_insert_key(int p_track, float p_time, float p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle);
+ void bezier_track_set_key_value(int p_track, int p_index, float p_value);
+ void bezier_track_set_key_in_handle(int p_track, int p_index, const Vector2 &p_handle);
+ void bezier_track_set_key_out_handle(int p_track, int p_index, const Vector2 &p_handle);
+ float bezier_track_get_key_value(int p_track, int p_index) const;
+ Vector2 bezier_track_get_key_in_handle(int p_track, int p_index) const;
+ Vector2 bezier_track_get_key_out_handle(int p_track, int p_index) const;
+
+ float bezier_track_interpolate(int p_track, float p_time) const;
+
+ int audio_track_insert_key(int p_track, float p_time, const RES &p_stream, float p_start_offset = 0, float p_end_offset = 0);
+ void audio_track_set_key_stream(int p_track, int p_key, const RES &p_stream);
+ void audio_track_set_key_start_offset(int p_track, int p_key, float p_offset);
+ void audio_track_set_key_end_offset(int p_track, int p_key, float p_offset);
+ RES audio_track_get_key_stream(int p_track, int p_key) const;
+ float audio_track_get_key_start_offset(int p_track, int p_key) const;
+ float audio_track_get_key_end_offset(int p_track, int p_key) const;
+
+ int animation_track_insert_key(int p_track, float p_time, const StringName &p_animation);
+ void animation_track_set_key_animation(int p_track, int p_key, const StringName &p_animation);
+ StringName animation_track_get_key_animation(int p_track, int p_key) const;
+
void track_set_interpolation_loop_wrap(int p_track, bool p_enable);
bool track_get_interpolation_loop_wrap(int p_track) const;
@@ -277,6 +356,8 @@ public:
void copy_track(int p_track, Ref<Animation> p_to_animation);
+ void track_get_key_indices_in_range(int p_track, float p_time, float p_delta, List<int> *p_indices) const;
+
void set_length(float p_length);
float get_length() const;
diff --git a/servers/audio/audio_stream.cpp b/servers/audio/audio_stream.cpp
index 0ad30987e7..113f23f8f2 100644
--- a/servers/audio/audio_stream.cpp
+++ b/servers/audio/audio_stream.cpp
@@ -89,6 +89,7 @@ void AudioStreamPlaybackResampled::mix(AudioFrame *p_buffer, float p_rate_scale,
}
}
}
+
////////////////////////////////
void AudioStream::_bind_methods() {
diff --git a/servers/audio/audio_stream.h b/servers/audio/audio_stream.h
index fda4fc2ccc..3312ce1ff6 100644
--- a/servers/audio/audio_stream.h
+++ b/servers/audio/audio_stream.h
@@ -31,6 +31,7 @@
#ifndef AUDIO_STREAM_H
#define AUDIO_STREAM_H
+#include "image.h"
#include "resource.h"
#include "servers/audio/audio_filter_sw.h"
#include "servers/audio_server.h"