Browse Source

Entirely new (and much improved) animation editor.

Juan Linietsky 7 years ago
parent
commit
b659fd6d74
60 changed files with 11711 additions and 5182 deletions
  1. 1183 0
      editor/animation_bezier_editor.cpp
  2. 141 0
      editor/animation_bezier_editor.h
  3. 0 4146
      editor/animation_editor.cpp
  4. 0 348
      editor/animation_editor.h
  5. 5001 0
      editor/animation_track_editor.cpp
  6. 483 0
      editor/animation_track_editor.h
  7. 1289 0
      editor/animation_track_editor_plugins.cpp
  8. 139 0
      editor/animation_track_editor_plugins.h
  9. 211 0
      editor/audio_stream_preview.cpp
  10. 56 0
      editor/audio_stream_preview.h
  11. 2 1
      editor/editor_inspector.cpp
  12. 4 3
      editor/editor_node.cpp
  13. 3 0
      editor/editor_node.h
  14. 18 0
      editor/editor_spin_slider.cpp
  15. 5 0
      editor/editor_spin_slider.h
  16. 63 0
      editor/icons/icon_animation_filter.svg
  17. 63 0
      editor/icons/icon_animation_track_group.svg
  18. 60 0
      editor/icons/icon_animation_track_list.svg
  19. 98 0
      editor/icons/icon_bezier_handles_balanced.svg
  20. 98 0
      editor/icons/icon_bezier_handles_free.svg
  21. 98 0
      editor/icons/icon_bezier_handles_mirror.svg
  22. 115 0
      editor/icons/icon_color_track_vu.svg
  23. 73 0
      editor/icons/icon_edit_bezier.svg
  24. 65 0
      editor/icons/icon_key_animation.svg
  25. 65 0
      editor/icons/icon_key_audio.svg
  26. 65 0
      editor/icons/icon_key_bezier.svg
  27. 60 0
      editor/icons/icon_key_bezier_handle.svg
  28. 64 0
      editor/icons/icon_key_bezier_point.svg
  29. 64 0
      editor/icons/icon_key_bezier_selected.svg
  30. 63 4
      editor/icons/icon_key_call.svg
  31. 75 4
      editor/icons/icon_key_selected.svg
  32. 63 4
      editor/icons/icon_key_xform.svg
  33. 74 0
      editor/icons/icon_time.svg
  34. 61 0
      editor/icons/icon_track_capture.svg
  35. 7 3
      editor/inspector_dock.cpp
  36. 2 1
      editor/inspector_dock.h
  37. 128 125
      editor/plugins/animation_player_editor_plugin.cpp
  38. 19 13
      editor/plugins/animation_player_editor_plugin.h
  39. 12 13
      editor/plugins/canvas_item_editor_plugin.cpp
  40. 53 235
      editor/plugins/editor_preview_plugins.cpp
  41. 4 8
      editor/plugins/editor_preview_plugins.h
  42. 2 2
      editor/plugins/spatial_editor_plugin.cpp
  43. 35 27
      editor/property_selector.cpp
  44. 4 0
      editor/property_selector.h
  45. 7 7
      editor/scene_tree_dock.cpp
  46. 14 0
      editor/scene_tree_editor.cpp
  47. 1 0
      editor/scene_tree_editor.h
  48. 12 206
      platform/android/AndroidManifest.xml.template
  49. 280 13
      scene/animation/animation_player.cpp
  50. 35 3
      scene/animation/animation_player.h
  51. 21 4
      scene/gui/popup_menu.cpp
  52. 4 0
      scene/gui/popup_menu.h
  53. 15 2
      scene/gui/scroll_container.cpp
  54. 3 0
      scene/gui/scroll_container.h
  55. 47 5
      scene/main/viewport.cpp
  56. 1 0
      scene/main/viewport.h
  57. 999 4
      scene/resources/animation.cpp
  58. 82 1
      scene/resources/animation.h
  59. 1 0
      servers/audio/audio_stream.cpp
  60. 1 0
      servers/audio/audio_stream.h

+ 1183 - 0
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
+}

+ 141 - 0
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

+ 0 - 4146
editor/animation_editor.cpp

@@ -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);
-}

+ 0 - 348
editor/animation_editor.h

@@ -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

+ 5001 - 0
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);
+	}
+}

+ 483 - 0
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

+ 1289 - 0
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;
+}

+ 139 - 0
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

+ 211 - 0
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);
+}

+ 56 - 0
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

+ 2 - 1
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 != "") {

+ 4 - 3
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)));

+ 3 - 0
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 <[email protected]>
 */
@@ -298,6 +300,7 @@ private:
 	Vector<ToolButton *> main_editor_buttons;
 	Vector<EditorPlugin *> editor_table;
 
+	AudioStreamPreviewGenerator *preview_gen;
 	ProgressDialog *progress_dialog;
 	BackgroundProgress *progress_hb;
 

+ 18 - 0
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;
 }

+ 5 - 0
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();
 };

+ 63 - 0
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>

+ 63 - 0
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>

+ 60 - 0
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>

+ 98 - 0
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>

+ 98 - 0
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>

+ 98 - 0
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>

+ 115 - 0
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>

+ 73 - 0
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>

+ 65 - 0
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>

+ 65 - 0
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>

+ 65 - 0
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>

+ 60 - 0
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>

+ 64 - 0
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>

+ 64 - 0
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>

+ 63 - 4
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>

+ 75 - 4
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>

+ 63 - 4
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>

+ 74 - 0
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>

+ 61 - 0
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>

+ 7 - 3
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) {

+ 2 - 1
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);

+ 128 - 125
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;

+ 19 - 13
editor/plugins/animation_player_editor_plugin.h

@@ -42,8 +42,9 @@
 /**
 	@author Juan Linietsky <[email protected]>
 */
-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);
 

+ 12 - 13
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());
                                    */
 				}
 			}

+ 53 - 235
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
 
 ///////////////////////////////////////////////////////////////////////////
 

+ 4 - 8
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 {
 

+ 2 - 2
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;
 			}

+ 35 - 27
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);

+ 4 - 0
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();
 };
 

+ 7 - 7
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);
 

+ 14 - 0
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."));
+			}
 		}
 	}
 

+ 1 - 0
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;

+ 12 - 206
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!

+ 280 - 13
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() {

+ 35 - 3
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 {

+ 21 - 4
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);
 

+ 4 - 0
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;

+ 15 - 2
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"));
 

+ 3 - 0
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;

+ 47 - 5
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()) {

+ 1 - 0
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();
 

File diff suppressed because it is too large
+ 999 - 4
scene/resources/animation.cpp


+ 82 - 1
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;
 

+ 1 - 0
servers/audio/audio_stream.cpp

@@ -89,6 +89,7 @@ void AudioStreamPlaybackResampled::mix(AudioFrame *p_buffer, float p_rate_scale,
 		}
 	}
 }
+
 ////////////////////////////////
 
 void AudioStream::_bind_methods() {

+ 1 - 0
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"

Some files were not shown because too many files changed in this diff