Ver código fonte

State machine animation node

Juan Linietsky 7 anos atrás
pai
commit
4f5a7ebaec
30 arquivos alterados com 3660 adições e 13 exclusões
  1. 45 0
      core/method_ptrcall.h
  2. 1 0
      core/type_info.h
  3. 24 0
      core/variant.cpp
  4. 2 0
      core/variant.h
  5. 2 0
      editor/editor_node.cpp
  6. 68 0
      editor/icons/icon_auto_end.svg
  7. 85 0
      editor/icons/icon_play_travel.svg
  8. 80 0
      editor/icons/icon_tool_add_node.svg
  9. 72 0
      editor/icons/icon_tool_connect.svg
  10. 72 0
      editor/icons/icon_transition_end.svg
  11. 74 0
      editor/icons/icon_transition_end_auto.svg
  12. 74 0
      editor/icons/icon_transition_end_auto_big.svg
  13. 74 0
      editor/icons/icon_transition_end_big.svg
  14. 64 0
      editor/icons/icon_transition_immediate.svg
  15. 64 0
      editor/icons/icon_transition_immediate_auto.svg
  16. 66 0
      editor/icons/icon_transition_immediate_auto_big.svg
  17. 66 0
      editor/icons/icon_transition_immediate_big.svg
  18. 72 0
      editor/icons/icon_transition_sync.svg
  19. 74 0
      editor/icons/icon_transition_sync_auto.svg
  20. 74 0
      editor/icons/icon_transition_sync_auto_big.svg
  21. 74 0
      editor/icons/icon_transition_sync_big.svg
  22. 4 4
      editor/plugins/animation_blend_space_editor.cpp
  23. 1 0
      editor/plugins/animation_blend_tree_editor_plugin.cpp
  24. 1313 0
      editor/plugins/animation_state_machine_editor.cpp
  25. 167 0
      editor/plugins/animation_state_machine_editor.h
  26. 5 5
      scene/animation/animation_blend_space.cpp
  27. 4 4
      scene/animation/animation_blend_space.h
  28. 794 0
      scene/animation/animation_node_state_machine.cpp
  29. 142 0
      scene/animation/animation_node_state_machine.h
  30. 3 0
      scene/register_scene_types.cpp

+ 45 - 0
core/method_ptrcall.h

@@ -214,6 +214,50 @@ struct PtrToArg<const T *> {
 		}                                                                                        \
 	}
 
+#define MAKE_VECARG_ALT(m_type, m_type_alt)                                                      \
+	template <>                                                                                  \
+	struct PtrToArg<Vector<m_type_alt> > {                                                       \
+		_FORCE_INLINE_ static Vector<m_type_alt> convert(const void *p_ptr) {                    \
+			const PoolVector<m_type> *dvs = reinterpret_cast<const PoolVector<m_type> *>(p_ptr); \
+			Vector<m_type_alt> ret;                                                              \
+			int len = dvs->size();                                                               \
+			ret.resize(len);                                                                     \
+			{                                                                                    \
+				PoolVector<m_type>::Read r = dvs->read();                                        \
+				for (int i = 0; i < len; i++) {                                                  \
+					ret[i] = r[i];                                                               \
+				}                                                                                \
+			}                                                                                    \
+			return ret;                                                                          \
+		}                                                                                        \
+		_FORCE_INLINE_ static void encode(Vector<m_type_alt> p_vec, void *p_ptr) {               \
+			PoolVector<m_type> *dv = reinterpret_cast<PoolVector<m_type> *>(p_ptr);              \
+			int len = p_vec.size();                                                              \
+			dv->resize(len);                                                                     \
+			{                                                                                    \
+				PoolVector<m_type>::Write w = dv->write();                                       \
+				for (int i = 0; i < len; i++) {                                                  \
+					w[i] = p_vec[i];                                                             \
+				}                                                                                \
+			}                                                                                    \
+		}                                                                                        \
+	};                                                                                           \
+	template <>                                                                                  \
+	struct PtrToArg<const Vector<m_type_alt> &> {                                                \
+		_FORCE_INLINE_ static Vector<m_type_alt> convert(const void *p_ptr) {                    \
+			const PoolVector<m_type> *dvs = reinterpret_cast<const PoolVector<m_type> *>(p_ptr); \
+			Vector<m_type_alt> ret;                                                              \
+			int len = dvs->size();                                                               \
+			ret.resize(len);                                                                     \
+			{                                                                                    \
+				PoolVector<m_type>::Read r = dvs->read();                                        \
+				for (int i = 0; i < len; i++) {                                                  \
+					ret[i] = r[i];                                                               \
+				}                                                                                \
+			}                                                                                    \
+			return ret;                                                                          \
+		}                                                                                        \
+	}
 MAKE_VECARG(String);
 MAKE_VECARG(uint8_t);
 MAKE_VECARG(int);
@@ -221,6 +265,7 @@ MAKE_VECARG(float);
 MAKE_VECARG(Vector2);
 MAKE_VECARG(Vector3);
 MAKE_VECARG(Color);
+MAKE_VECARG_ALT(String, StringName);
 
 //for stuff that gets converted to Array vectors
 #define MAKE_VECARR(m_type)                                                    \

+ 1 - 0
core/type_info.h

@@ -194,6 +194,7 @@ MAKE_TEMPLATE_TYPE_INFO(Vector, Color, Variant::POOL_COLOR_ARRAY)
 MAKE_TEMPLATE_TYPE_INFO(Vector, Variant, Variant::ARRAY)
 MAKE_TEMPLATE_TYPE_INFO(Vector, RID, Variant::ARRAY)
 MAKE_TEMPLATE_TYPE_INFO(Vector, Plane, Variant::ARRAY)
+MAKE_TEMPLATE_TYPE_INFO(Vector, StringName, Variant::POOL_STRING_ARRAY)
 
 MAKE_TEMPLATE_TYPE_INFO(PoolVector, Plane, Variant::ARRAY)
 MAKE_TEMPLATE_TYPE_INFO(PoolVector, Face3, Variant::POOL_VECTOR3_ARRAY)

+ 24 - 0
core/variant.cpp

@@ -2012,6 +2012,19 @@ Variant::operator Vector<String>() const {
 	}
 	return to;
 }
+Variant::operator Vector<StringName>() const {
+
+	PoolVector<String> from = operator PoolVector<String>();
+	Vector<StringName> to;
+	int len = from.size();
+	to.resize(len);
+	for (int i = 0; i < len; i++) {
+
+		to[i] = from[i];
+	}
+	return to;
+}
+
 Variant::operator Vector<Vector3>() const {
 
 	PoolVector<Vector3> from = operator PoolVector<Vector3>();
@@ -2444,6 +2457,17 @@ Variant::Variant(const Vector<String> &p_array) {
 	*this = v;
 }
 
+Variant::Variant(const Vector<StringName> &p_array) {
+
+	type = NIL;
+	PoolVector<String> v;
+	int len = p_array.size();
+	v.resize(len);
+	for (int i = 0; i < len; i++)
+		v.set(i, p_array[i]);
+	*this = v;
+}
+
 Variant::Variant(const Vector<Vector3> &p_array) {
 
 	type = NIL;

+ 2 - 0
core/variant.h

@@ -216,6 +216,7 @@ public:
 	operator Vector<int>() const;
 	operator Vector<real_t>() const;
 	operator Vector<String>() const;
+	operator Vector<StringName>() const;
 	operator Vector<Vector3>() const;
 	operator Vector<Color>() const;
 	operator Vector<RID>() const;
@@ -280,6 +281,7 @@ public:
 	Variant(const Vector<int> &p_int_array);
 	Variant(const Vector<real_t> &p_real_array);
 	Variant(const Vector<String> &p_string_array);
+	Variant(const Vector<StringName> &p_string_array);
 	Variant(const Vector<Vector3> &p_vector3_array);
 	Variant(const Vector<Color> &p_color_array);
 	Variant(const Vector<Plane> &p_array); // helper

+ 2 - 0
editor/editor_node.cpp

@@ -69,6 +69,7 @@
 #include "editor/plugins/animation_blend_space_editor.h"
 #include "editor/plugins/animation_blend_tree_editor_plugin.h"
 #include "editor/plugins/animation_player_editor_plugin.h"
+#include "editor/plugins/animation_state_machine_editor.h"
 #include "editor/plugins/animation_tree_editor_plugin.h"
 #include "editor/plugins/asset_library_editor_plugin.h"
 #include "editor/plugins/baked_lightmap_editor_plugin.h"
@@ -5401,6 +5402,7 @@ EditorNode::EditorNode() {
 	//add_editor_plugin(memnew(ShaderGraphEditorPlugin(this)));
 	add_editor_plugin(memnew(AnimationNodeBlendTreeEditorPlugin(this)));
 	add_editor_plugin(memnew(AnimationNodeBlendSpaceEditorPlugin(this)));
+	add_editor_plugin(memnew(AnimationNodeStateMachineEditorPlugin(this)));
 
 	add_editor_plugin(memnew(CameraEditorPlugin(this)));
 	add_editor_plugin(memnew(ThemeEditorPlugin(this)));

+ 68 - 0
editor/icons/icon_auto_end.svg

@@ -0,0 +1,68 @@
+<?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_auto_end.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="1273"
+     inkscape:window-height="766"
+     id="namedview8"
+     showgrid="false"
+     inkscape:zoom="41.7193"
+     inkscape:cx="12.08616"
+     inkscape:cy="6.9898672"
+     inkscape:window-x="539"
+     inkscape:window-y="208"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg6" />
+  <path
+     inkscape:connector-curvature="0"
+     id="path2"
+     style="color:#000000;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;white-space:normal;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#e0e0e0;fill-rule:evenodd;color-rendering:auto;image-rendering:auto;shape-rendering:auto"
+     d="m 13.999798,14 c 0.552262,-5.5e-5 0.999945,-0.447738 1,-1 V 3 c -5.5e-5,-0.5522619 -0.447738,-0.9999448 -1,-1 H 5.9997976 C 5.6959349,1.9998247 5.4084731,2.1378063 5.2185476,2.375 l -4,5 c -0.29139692,0.3649711 -0.29139692,0.8830289 0,1.248 l 4,5 c 0.189538,0.237924 0.4770584,0.376652 0.78125,0.37695 h 8.0000004 z m -1,-2 H 6.4802976 l -3.1992,-4 3.1992,-4 H 12.999798 Z M 6.9997976,10 V 6 l -2,2 z"
+     sodipodi:nodetypes="cccccccccccccccccccccc" />
+  <g
+     aria-label="E"
+     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:40px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';letter-spacing:0px;word-spacing:0px;fill:#e0e0e0;fill-opacity:1;stroke:none"
+     id="text829"
+     transform="matrix(0.20475474,0,0,0.20475474,4.7903856,12.365563)">
+    <path
+       d="M 15.129502,-36.414393 H 35.422471 V -30.7308 H 22.649034 v 5.429688 h 12.011718 v 5.683594 H 22.649034 v 6.679687 h 13.203125 v 5.6835938 H 15.129502 Z"
+       style="fill:#e0e0e0;fill-opacity:1"
+       id="path831"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>

+ 85 - 0
editor/icons/icon_play_travel.svg

@@ -0,0 +1,85 @@
+<?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="svg8"
+   sodipodi:docname="icon_play_travel.svg"
+   inkscape:version="0.92.3 (2405546, 2018-03-11)">
+  <metadata
+     id="metadata14">
+    <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="defs12" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1446"
+     inkscape:window-height="646"
+     id="namedview10"
+     showgrid="false"
+     inkscape:zoom="14.75"
+     inkscape:cx="8.2818541"
+     inkscape:cy="5.7694884"
+     inkscape:window-x="67"
+     inkscape:window-y="27"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg8" />
+  <g
+     transform="matrix(0.59321602,0,0,0.59321602,-1.2203136,-611.14809)"
+     id="g6">
+    <g
+       id="g4">
+      <path
+         d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z"
+         dominant-baseline="auto"
+         style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#e0e0e0;fill-rule:evenodd;color-rendering:auto;image-rendering:auto;shape-rendering:auto"
+         id="path2"
+         inkscape:connector-curvature="0" />
+    </g>
+  </g>
+  <g
+     transform="matrix(0.59321602,0,0,0.59321602,7.5254716,-610.94451)"
+     id="g6-3">
+    <g
+       id="g4-6">
+      <path
+         d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z"
+         dominant-baseline="auto"
+         style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#e0e0e0;fill-rule:evenodd;color-rendering:auto;image-rendering:auto;shape-rendering:auto"
+         id="path2-7"
+         inkscape:connector-curvature="0" />
+    </g>
+  </g>
+  <rect
+     style="fill:#e0e0e0;fill-opacity:1"
+     id="rect842"
+     width="9.5593224"
+     height="0.54237264"
+     x="3.0058463"
+     y="8.1280737"
+     ry="0.27118632" />
+</svg>

+ 80 - 0
editor/icons/icon_tool_add_node.svg

@@ -0,0 +1,80 @@
+<?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="svg8"
+   sodipodi:docname="icon_tool_add_node.svg"
+   inkscape:version="0.92.3 (2405546, 2018-03-11)">
+  <metadata
+     id="metadata14">
+    <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="defs12" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1516"
+     inkscape:window-height="747"
+     id="namedview10"
+     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="g4" />
+  <g
+     transform="translate(0 -1036.4)"
+     id="g6">
+    <g
+       transform="translate(-26.001 -9.8683)"
+       id="g4">
+      <path
+         style="fill:#e0e0e0;fill-opacity:1"
+         d="m 27.917081,1047.5557 c -0.422624,0 -0.763672,0.3411 -0.763672,0.7637 v 11.8301 c 0,0.4226 0.341048,0.7637 0.763672,0.7637 h 12.507813 c 0.422624,0 0.761719,-0.3411 0.761719,-0.7637 v -11.8301 c 0,-0.4226 -0.339095,-0.7637 -0.761719,-0.7637 z m 1.898438,1.6954 h 8.642578 c 0.422624,0 0.763672,0.341 0.763672,0.7636 v 8.5078 c 0,0.4227 -0.341048,0.7618 -0.763672,0.7618 h -8.642578 c -0.422625,0 -0.763672,-0.3391 -0.763672,-0.7618 v -8.5078 c 0,-0.4226 0.341047,-0.7636 0.763672,-0.7636 z"
+         id="rect821"
+         inkscape:connector-curvature="0" />
+      <rect
+         style="fill:#e0e0e0;fill-opacity:1"
+         id="rect826"
+         width="7.7966104"
+         height="2.3728814"
+         x="30.20439"
+         y="1052.9802"
+         ry="0.76286" />
+      <rect
+         style="fill:#e0e0e0;fill-opacity:1;stroke-width:0.88253576"
+         id="rect828"
+         width="2.3728814"
+         height="7.5254235"
+         x="32.916256"
+         y="1050.3361"
+         ry="0.72997814" />
+    </g>
+  </g>
+</svg>

+ 72 - 0
editor/icons/icon_tool_connect.svg

@@ -0,0 +1,72 @@
+<?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="svg8"
+   sodipodi:docname="icon_tool_connect.svg"
+   inkscape:version="0.92.3 (2405546, 2018-03-11)">
+  <metadata
+     id="metadata14">
+    <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="defs12" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1516"
+     inkscape:window-height="747"
+     id="namedview10"
+     showgrid="false"
+     inkscape:zoom="3.6875"
+     inkscape:cx="8.5909556"
+     inkscape:cy="7.8012075"
+     inkscape:window-x="67"
+     inkscape:window-y="27"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="g4" />
+  <g
+     transform="translate(0 -1036.4)"
+     id="g6">
+    <g
+       transform="translate(-26.001 -9.8683)"
+       id="g4">
+      <rect
+         style="fill:#e0e0e0;fill-opacity:1"
+         id="rect849"
+         width="14.305085"
+         height="2.1694915"
+         x="26.766621"
+         y="1053.1389"
+         ry="0.76286" />
+      <path
+         style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:1.16725671px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+         d="m 30.596131,1046.927 v 14.8861 l 8.228847,-7.5722 z"
+         id="path853"
+         inkscape:connector-curvature="0" />
+    </g>
+  </g>
+</svg>

+ 72 - 0
editor/icons/icon_transition_end.svg

@@ -0,0 +1,72 @@
+<?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="svg8"
+   sodipodi:docname="icon_transition_end.svg"
+   inkscape:version="0.92.3 (2405546, 2018-03-11)">
+  <metadata
+     id="metadata14">
+    <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="defs12" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1403"
+     inkscape:window-height="782"
+     id="namedview10"
+     showgrid="false"
+     inkscape:zoom="20.85965"
+     inkscape:cx="10.204146"
+     inkscape:cy="5.3391396"
+     inkscape:window-x="67"
+     inkscape:window-y="27"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg8" />
+  <g
+     transform="translate(-2,-1036.4)"
+     id="g6">
+    <g
+       id="g4">
+      <path
+         d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z"
+         dominant-baseline="auto"
+         style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east_asian:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#e0e0e0;fill-rule:evenodd;color-rendering:auto;image-rendering:auto;shape-rendering:auto"
+         id="path2"
+         inkscape:connector-curvature="0" />
+    </g>
+  </g>
+  <rect
+     style="fill:#e0e0e0;fill-opacity:1"
+     id="rect862"
+     width="3.0681243"
+     height="10.067283"
+     x="11.16989"
+     y="3.0084109"
+     ry="0.76286" />
+</svg>

+ 74 - 0
editor/icons/icon_transition_end_auto.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="svg8"
+   sodipodi:docname="icon_transition_automatic.svg"
+   inkscape:version="0.92.3 (2405546, 2018-03-11)">
+  <metadata
+     id="metadata14">
+    <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="defs12" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1403"
+     inkscape:window-height="782"
+     id="namedview10"
+     showgrid="false"
+     inkscape:zoom="20.85965"
+     inkscape:cx="0.56831798"
+     inkscape:cy="5.1473818"
+     inkscape:window-x="67"
+     inkscape:window-y="27"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg8" />
+  <g
+     transform="translate(-2,-1036.4)"
+     id="g6"
+     style="fill:#77ce57;fill-opacity:1">
+    <g
+       id="g4"
+       style="fill:#77ce57;fill-opacity:1">
+      <path
+         d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z"
+         dominant-baseline="auto"
+         style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east_asian:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#77ce57;fill-rule:evenodd;color-rendering:auto;image-rendering:auto;shape-rendering:auto;fill-opacity:1"
+         id="path2"
+         inkscape:connector-curvature="0" />
+    </g>
+  </g>
+  <rect
+     style="fill:#77ce57;fill-opacity:1"
+     id="rect862"
+     width="3.0681243"
+     height="10.067283"
+     x="11.16989"
+     y="3.0084109"
+     ry="0.76286" />
+</svg>

+ 74 - 0
editor/icons/icon_transition_end_auto_big.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="20"
+   height="20"
+   version="1.1"
+   viewBox="0 0 20 20"
+   id="svg8"
+   sodipodi:docname="icon_transition_automatic_big.svg"
+   inkscape:version="0.92.3 (2405546, 2018-03-11)">
+  <metadata
+     id="metadata14">
+    <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="defs12" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1403"
+     inkscape:window-height="782"
+     id="namedview10"
+     showgrid="false"
+     inkscape:zoom="14.75"
+     inkscape:cx="0.3064671"
+     inkscape:cy="14.348448"
+     inkscape:window-x="67"
+     inkscape:window-y="27"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg8" />
+  <g
+     transform="matrix(1.4099529,0,0,1.4099529,-4.1975887,-1462.5094)"
+     id="g6"
+     style="fill:#77ce57;fill-opacity:1;stroke:#41562e;stroke-opacity:1">
+    <g
+       id="g4"
+       style="fill:#77ce57;fill-opacity:1;stroke:#41562e;stroke-opacity:1">
+      <path
+         d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z"
+         dominant-baseline="auto"
+         style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#77ce57;fill-opacity:1;fill-rule:evenodd;stroke:#41562e;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto"
+         id="path2"
+         inkscape:connector-curvature="0" />
+    </g>
+  </g>
+  <rect
+     style="fill:#77ce57;fill-opacity:1;stroke:#41562e;stroke-width:1.409953;stroke-opacity:1"
+     id="rect862"
+     width="4.3259106"
+     height="14.194397"
+     x="14.371336"
+     y="3.0076122"
+     ry="1.0755967" />
+</svg>

+ 74 - 0
editor/icons/icon_transition_end_big.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="20"
+   height="20"
+   version="1.1"
+   viewBox="0 0 20 20"
+   id="svg8"
+   sodipodi:docname="icon_transition_end_big.svg"
+   inkscape:version="0.92.3 (2405546, 2018-03-11)">
+  <metadata
+     id="metadata14">
+    <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="defs12" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1403"
+     inkscape:window-height="782"
+     id="namedview10"
+     showgrid="false"
+     inkscape:zoom="14.75"
+     inkscape:cx="-1.1122019"
+     inkscape:cy="10.839132"
+     inkscape:window-x="517"
+     inkscape:window-y="261"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg8" />
+  <g
+     transform="matrix(1.4203458,0,0,1.4203458,-4.29479,-1473.1325)"
+     id="g6"
+     style="stroke:#424242;stroke-width:0.99994373;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">
+    <g
+       id="g4"
+       style="stroke:#424242;stroke-width:0.99994373;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">
+      <path
+         d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z"
+         dominant-baseline="auto"
+         style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#e0e0e0;fill-rule:evenodd;stroke:#424242;stroke-width:0.99994373;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto"
+         id="path2"
+         inkscape:connector-curvature="0" />
+    </g>
+  </g>
+  <rect
+     style="fill:#e0e0e0;fill-opacity:1;stroke:#424242;stroke-width:1.42026603;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect862"
+     width="4.3577976"
+     height="14.299023"
+     x="14.411009"
+     y="3.1868868"
+     ry="1.0835251" />
+</svg>

+ 64 - 0
editor/icons/icon_transition_immediate.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="16"
+   height="16"
+   version="1.1"
+   viewBox="0 0 16 16"
+   id="svg8"
+   sodipodi:docname="icon_transition_immediate.svg"
+   inkscape:version="0.92.3 (2405546, 2018-03-11)">
+  <metadata
+     id="metadata14">
+    <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="defs12" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1403"
+     inkscape:window-height="782"
+     id="namedview10"
+     showgrid="false"
+     inkscape:zoom="20.85965"
+     inkscape:cx="4.0199579"
+     inkscape:cy="5.3391396"
+     inkscape:window-x="67"
+     inkscape:window-y="27"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="g4" />
+  <g
+     transform="translate(-2,-1036.4)"
+     id="g6">
+    <g
+       id="g4">
+      <path
+         d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z"
+         dominant-baseline="auto"
+         style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east_asian:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#e0e0e0;fill-rule:evenodd;color-rendering:auto;image-rendering:auto;shape-rendering:auto;fill-opacity:1"
+         id="path2"
+         inkscape:connector-curvature="0" />
+    </g>
+  </g>
+</svg>

+ 64 - 0
editor/icons/icon_transition_immediate_auto.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="16"
+   height="16"
+   version="1.1"
+   viewBox="0 0 16 16"
+   id="svg8"
+   sodipodi:docname="icon_transition_immediate_auto.svg"
+   inkscape:version="0.92.3 (2405546, 2018-03-11)">
+  <metadata
+     id="metadata14">
+    <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="defs12" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1403"
+     inkscape:window-height="782"
+     id="namedview10"
+     showgrid="false"
+     inkscape:zoom="20.85965"
+     inkscape:cx="-9.2592678"
+     inkscape:cy="5.3391396"
+     inkscape:window-x="67"
+     inkscape:window-y="27"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="g4" />
+  <g
+     transform="translate(-2,-1036.4)"
+     id="g6">
+    <g
+       id="g4">
+      <path
+         d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z"
+         dominant-baseline="auto"
+         style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east_asian:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#77ce57;fill-rule:evenodd;color-rendering:auto;image-rendering:auto;shape-rendering:auto;fill-opacity:1"
+         id="path2"
+         inkscape:connector-curvature="0" />
+    </g>
+  </g>
+</svg>

+ 66 - 0
editor/icons/icon_transition_immediate_auto_big.svg

@@ -0,0 +1,66 @@
+<?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="20"
+   height="20"
+   version="1.1"
+   viewBox="0 0 20 20"
+   id="svg8"
+   sodipodi:docname="icon_transition_immediate_auto_big.svg"
+   inkscape:version="0.92.3 (2405546, 2018-03-11)">
+  <metadata
+     id="metadata14">
+    <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="defs12" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1403"
+     inkscape:window-height="782"
+     id="namedview10"
+     showgrid="false"
+     inkscape:zoom="20.85965"
+     inkscape:cx="11.321237"
+     inkscape:cy="3.5752171"
+     inkscape:window-x="517"
+     inkscape:window-y="261"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="g4" />
+  <g
+     transform="matrix(1.571031,0,0,1.571031,-2.7257681,-1630.6239)"
+     id="g6"
+     style="stroke:#404040;stroke-opacity:1">
+    <g
+       id="g4"
+       style="stroke:#404040;stroke-opacity:1">
+      <path
+         d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z"
+         dominant-baseline="auto"
+         style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#77ce57;fill-rule:evenodd;stroke:#41562e;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;fill-opacity:1"
+         id="path2"
+         inkscape:connector-curvature="0" />
+    </g>
+  </g>
+</svg>

+ 66 - 0
editor/icons/icon_transition_immediate_big.svg

@@ -0,0 +1,66 @@
+<?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="20"
+   height="20"
+   version="1.1"
+   viewBox="0 0 20 20"
+   id="svg8"
+   sodipodi:docname="icon_transition_immediate_big.svg"
+   inkscape:version="0.92.3 (2405546, 2018-03-11)">
+  <metadata
+     id="metadata14">
+    <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="defs12" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1403"
+     inkscape:window-height="782"
+     id="namedview10"
+     showgrid="false"
+     inkscape:zoom="20.85965"
+     inkscape:cx="0.19928629"
+     inkscape:cy="4.534006"
+     inkscape:window-x="517"
+     inkscape:window-y="261"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="g4" />
+  <g
+     transform="matrix(1.571031,0,0,1.571031,-2.7257681,-1630.6239)"
+     id="g6"
+     style="stroke:#404040;stroke-opacity:1">
+    <g
+       id="g4"
+       style="stroke:#404040;stroke-opacity:1">
+      <path
+         d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z"
+         dominant-baseline="auto"
+         style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#e0e0e0;fill-rule:evenodd;stroke:#404040;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;fill-opacity:1"
+         id="path2"
+         inkscape:connector-curvature="0" />
+    </g>
+  </g>
+</svg>

+ 72 - 0
editor/icons/icon_transition_sync.svg

@@ -0,0 +1,72 @@
+<?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="svg8"
+   sodipodi:docname="icon_transition_sync.svg"
+   inkscape:version="0.92.3 (2405546, 2018-03-11)">
+  <metadata
+     id="metadata14">
+    <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="defs12" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1403"
+     inkscape:window-height="782"
+     id="namedview10"
+     showgrid="false"
+     inkscape:zoom="20.85965"
+     inkscape:cx="10.204146"
+     inkscape:cy="5.3391396"
+     inkscape:window-x="67"
+     inkscape:window-y="27"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg8" />
+  <g
+     transform="translate(2.5542471,-1036.4)"
+     id="g6">
+    <g
+       id="g4">
+      <path
+         d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z"
+         dominant-baseline="auto"
+         style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#e0e0e0;fill-rule:evenodd;color-rendering:auto;image-rendering:auto;shape-rendering:auto"
+         id="path2"
+         inkscape:connector-curvature="0" />
+    </g>
+  </g>
+  <rect
+     style="fill:#e0e0e0;fill-opacity:1"
+     id="rect862"
+     width="3.0681243"
+     height="10.067283"
+     x="1.9655174"
+     y="3.0084109"
+     ry="0.76286" />
+</svg>

+ 74 - 0
editor/icons/icon_transition_sync_auto.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="svg8"
+   sodipodi:docname="icon_transition_sync_auto.svg"
+   inkscape:version="0.92.3 (2405546, 2018-03-11)">
+  <metadata
+     id="metadata14">
+    <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="defs12" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1403"
+     inkscape:window-height="782"
+     id="namedview10"
+     showgrid="false"
+     inkscape:zoom="20.85965"
+     inkscape:cx="0.56831798"
+     inkscape:cy="5.1473818"
+     inkscape:window-x="67"
+     inkscape:window-y="27"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg8" />
+  <g
+     transform="translate(3.0815809,-1036.4)"
+     id="g6"
+     style="fill:#77ce57;fill-opacity:1">
+    <g
+       id="g4"
+       style="fill:#77ce57;fill-opacity:1">
+      <path
+         d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z"
+         dominant-baseline="auto"
+         style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#77ce57;fill-opacity:1;fill-rule:evenodd;color-rendering:auto;image-rendering:auto;shape-rendering:auto"
+         id="path2"
+         inkscape:connector-curvature="0" />
+    </g>
+  </g>
+  <rect
+     style="fill:#77ce57;fill-opacity:1"
+     id="rect862"
+     width="3.0681243"
+     height="10.067283"
+     x="1.9655174"
+     y="3.0084109"
+     ry="0.76286" />
+</svg>

+ 74 - 0
editor/icons/icon_transition_sync_auto_big.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="20"
+   height="20"
+   version="1.1"
+   viewBox="0 0 20 20"
+   id="svg8"
+   sodipodi:docname="icon_transition_sync_auto_big.svg"
+   inkscape:version="0.92.3 (2405546, 2018-03-11)">
+  <metadata
+     id="metadata14">
+    <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="defs12" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1403"
+     inkscape:window-height="782"
+     id="namedview10"
+     showgrid="false"
+     inkscape:zoom="14.75"
+     inkscape:cx="0.3064671"
+     inkscape:cy="14.348448"
+     inkscape:window-x="67"
+     inkscape:window-y="27"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg8" />
+  <g
+     transform="matrix(1.4099529,0,0,1.4099529,2.1752927,-1462.5094)"
+     id="g6"
+     style="fill:#77ce57;fill-opacity:1;stroke:#41562e;stroke-opacity:1">
+    <g
+       id="g4"
+       style="fill:#77ce57;fill-opacity:1;stroke:#41562e;stroke-opacity:1">
+      <path
+         d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z"
+         dominant-baseline="auto"
+         style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#77ce57;fill-opacity:1;fill-rule:evenodd;stroke:#41562e;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto"
+         id="path2"
+         inkscape:connector-curvature="0" />
+    </g>
+  </g>
+  <rect
+     style="fill:#77ce57;fill-opacity:1;stroke:#41562e;stroke-width:1.409953;stroke-opacity:1"
+     id="rect862"
+     width="4.3259106"
+     height="14.194397"
+     x="1.6255733"
+     y="3.0076122"
+     ry="1.0755967" />
+</svg>

+ 74 - 0
editor/icons/icon_transition_sync_big.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="20"
+   height="20"
+   version="1.1"
+   viewBox="0 0 20 20"
+   id="svg8"
+   sodipodi:docname="icon_transition_sync_big.svg"
+   inkscape:version="0.92.3 (2405546, 2018-03-11)">
+  <metadata
+     id="metadata14">
+    <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="defs12" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1403"
+     inkscape:window-height="782"
+     id="namedview10"
+     showgrid="false"
+     inkscape:zoom="14.75"
+     inkscape:cx="19.226781"
+     inkscape:cy="9.27981"
+     inkscape:window-x="302"
+     inkscape:window-y="226"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg8" />
+  <g
+     transform="matrix(1.4203458,0,0,1.4203458,1.8747015,-1473.1325)"
+     id="g6"
+     style="stroke:#424242;stroke-width:0.99994373;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">
+    <g
+       id="g4"
+       style="stroke:#424242;stroke-width:0.99994373;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">
+      <path
+         d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z"
+         dominant-baseline="auto"
+         style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#e0e0e0;fill-rule:evenodd;stroke:#424242;stroke-width:0.99994373;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto"
+         id="path2"
+         inkscape:connector-curvature="0" />
+    </g>
+  </g>
+  <rect
+     style="fill:#e0e0e0;fill-opacity:1;stroke:#424242;stroke-width:1.42026603;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect862"
+     width="4.3577976"
+     height="14.299023"
+     x="1.4618562"
+     y="3.1868868"
+     ry="1.0835251" />
+</svg>

+ 4 - 4
editor/plugins/animation_blend_space_editor.cpp

@@ -203,7 +203,7 @@ void AnimationNodeBlendSpaceEditor::_blend_space_gui_input(const Ref<InputEvent>
 		blend_pos *= (blend_space->get_max_space() - blend_space->get_min_space());
 		blend_pos += blend_space->get_min_space();
 
-		blend_space->set_blend_pos(blend_pos);
+		blend_space->set_blend_position(blend_pos);
 		blend_space_draw->update();
 	}
 
@@ -237,7 +237,7 @@ void AnimationNodeBlendSpaceEditor::_blend_space_gui_input(const Ref<InputEvent>
 		blend_pos *= (blend_space->get_max_space() - blend_space->get_min_space());
 		blend_pos += blend_space->get_min_space();
 
-		blend_space->set_blend_pos(blend_pos);
+		blend_space->set_blend_position(blend_pos);
 		blend_space_draw->update();
 	}
 }
@@ -490,13 +490,13 @@ void AnimationNodeBlendSpaceEditor::_blend_space_draw() {
 			color.a *= 0.5;
 		}
 
-		Vector2 point = blend_space->get_blend_pos();
+		Vector2 point = blend_space->get_blend_position();
 		point = (point - blend_space->get_min_space()) / (blend_space->get_max_space() - blend_space->get_min_space());
 		point *= s;
 		point.y = s.height - point.y;
 
 		if (blend_space->get_triangle_count()) {
-			Vector2 closest = blend_space->get_closest_point(blend_space->get_blend_pos());
+			Vector2 closest = blend_space->get_closest_point(blend_space->get_blend_position());
 			closest = (closest - blend_space->get_min_space()) / (blend_space->get_max_space() - blend_space->get_min_space());
 			closest *= s;
 			closest.y = s.height - closest.y;

+ 1 - 0
editor/plugins/animation_blend_tree_editor_plugin.cpp

@@ -775,6 +775,7 @@ AnimationNodeBlendTreeEditor::AnimationNodeBlendTreeEditor() {
 	add_options.push_back(AddOption("Transition", "AnimationNodeTransition"));
 	add_options.push_back(AddOption("BlendTree", "AnimationNodeBlendTree"));
 	add_options.push_back(AddOption("BlendSpace", "AnimationNodeBlendSpace"));
+	add_options.push_back(AddOption("StateMachine", "AnimationNodeStateMachine"));
 	_update_options_menu();
 
 	error_panel = memnew(PanelContainer);

+ 1313 - 0
editor/plugins/animation_state_machine_editor.cpp

@@ -0,0 +1,1313 @@
+#include "animation_state_machine_editor.h"
+
+#include "core/io/resource_loader.h"
+#include "core/project_settings.h"
+#include "math/delaunay.h"
+#include "os/input.h"
+#include "os/keyboard.h"
+#include "scene/animation/animation_blend_tree.h"
+#include "scene/animation/animation_player.h"
+#include "scene/gui/menu_button.h"
+#include "scene/gui/panel.h"
+#include "scene/main/viewport.h"
+
+void AnimationNodeStateMachineEditor::edit(AnimationNodeStateMachine *p_state_machine) {
+
+	if (state_machine.is_valid()) {
+		state_machine->disconnect("removed_from_graph", this, "_removed_from_graph");
+	}
+
+	if (p_state_machine) {
+		state_machine = Ref<AnimationNodeStateMachine>(p_state_machine);
+	} else {
+		state_machine.unref();
+	}
+
+	if (state_machine.is_null()) {
+		hide();
+	} else {
+		state_machine->connect("removed_from_graph", this, "_removed_from_graph");
+
+		selected_transition_from = StringName();
+		selected_transition_to = StringName();
+		selected_node = StringName();
+		_update_mode();
+		_update_graph();
+	}
+}
+
+void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEvent> &p_event) {
+
+	Ref<InputEventKey> k = p_event;
+	if (tool_select->is_pressed() && k.is_valid() && k->is_pressed() && k->get_scancode() == KEY_DELETE && !k->is_echo()) {
+		if (selected_node != StringName() || selected_transition_to != StringName() || selected_transition_from != StringName()) {
+			_erase_selected();
+			accept_event();
+		}
+	}
+
+	Ref<InputEventMouseButton> mb = p_event;
+
+	//Add new node
+	if (mb.is_valid() && mb->is_pressed() && ((tool_select->is_pressed() && mb->get_button_index() == BUTTON_RIGHT) || (tool_create->is_pressed() && mb->get_button_index() == BUTTON_LEFT))) {
+		menu->clear();
+		animations_menu->clear();
+		animations_to_add.clear();
+		List<StringName> classes;
+		classes.sort_custom<StringName::AlphCompare>();
+
+		ClassDB::get_inheriters_from_class("AnimationRootNode", &classes);
+		menu->add_submenu_item(TTR("Add Animation"), "animations");
+
+		AnimationGraphPlayer *gp = state_machine->get_graph_player();
+		ERR_FAIL_COND(!gp);
+		if (gp && gp->has_node(gp->get_animation_player())) {
+			AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(gp->get_node(gp->get_animation_player()));
+			if (ap) {
+				List<StringName> names;
+				ap->get_animation_list(&names);
+				for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
+					animations_menu->add_icon_item(get_icon("Animation", "EditorIcons"), E->get());
+					animations_to_add.push_back(E->get());
+				}
+			}
+		}
+
+		for (List<StringName>::Element *E = classes.front(); E; E = E->next()) {
+
+			String name = String(E->get()).replace_first("AnimationNode", "");
+			if (name == "Animation")
+				continue; // nope
+			int idx = menu->get_item_count();
+			menu->add_item(vformat("Add %s", name));
+			menu->set_item_metadata(idx, E->get());
+		}
+
+		menu->set_global_position(state_machine_draw->get_global_transform().xform(mb->get_position()));
+		menu->popup();
+		add_node_pos = mb->get_position() / EDSCALE + state_machine->get_graph_offset();
+	}
+
+	// select node or push a field inside
+	if (mb.is_valid() && !mb->get_shift() && mb->is_pressed() && tool_select->is_pressed() && mb->get_button_index() == BUTTON_LEFT) {
+
+		selected_transition_from = StringName();
+		selected_transition_to = StringName();
+		selected_node = StringName();
+
+		for (int i = node_rects.size() - 1; i >= 0; i--) { //inverse to draw order
+
+			if (node_rects[i].play.has_point(mb->get_position())) { //edit name
+				if (play_mode->get_selected() == 1 || !state_machine->is_playing()) {
+					//start
+					state_machine->start(node_rects[i].node_name);
+				} else {
+					//travel
+					if (!state_machine->travel(node_rects[i].node_name)) {
+
+						state_machine->start(node_rects[i].node_name);
+						//removing this due to usability..
+						//error_time = 5;
+						//error_text = vformat(TTR("No path found from '%s' to '%s'."), state_machine->get_current_node(), node_rects[i].node_name);
+					}
+				}
+				state_machine_draw->update();
+				return;
+			}
+
+			if (node_rects[i].name.has_point(mb->get_position())) { //edit name
+
+				Ref<StyleBox> line_sb = get_stylebox("normal", "LineEdit");
+
+				Rect2 edit_rect = node_rects[i].name;
+				edit_rect.position -= line_sb->get_offset();
+				edit_rect.size += line_sb->get_minimum_size();
+
+				name_edit->set_global_position(state_machine_draw->get_global_transform().xform(edit_rect.position));
+				name_edit->set_size(edit_rect.size);
+				name_edit->set_text(node_rects[i].node_name);
+				name_edit->show_modal();
+				name_edit->grab_focus();
+				name_edit->select_all();
+
+				prev_name = node_rects[i].node_name;
+				return;
+			}
+
+			if (node_rects[i].edit.has_point(mb->get_position())) { //edit name
+				call_deferred("_open_editor", node_rects[i].node_name);
+				return;
+			}
+
+			if (node_rects[i].node.has_point(mb->get_position())) { //select node since nothing else was selected
+				selected_node = node_rects[i].node_name;
+
+				Ref<AnimationNode> anode = state_machine->get_node(selected_node);
+				EditorNode::get_singleton()->push_item(anode.ptr(), "", true);
+				state_machine_draw->update();
+				dragging_selected_attempt = true;
+				dragging_selected = false;
+				drag_from = mb->get_position();
+				snap_x = StringName();
+				snap_y = StringName();
+				_update_mode();
+				return;
+			}
+		}
+
+		//test the lines now
+		int closest = -1;
+		float closest_d = 1e20;
+		for (int i = 0; i < transition_lines.size(); i++) {
+
+			Vector2 s[2] = {
+				transition_lines[i].from,
+				transition_lines[i].to
+			};
+			Vector2 cpoint = Geometry::get_closest_point_to_segment_2d(mb->get_position(), s);
+			float d = cpoint.distance_to(mb->get_position());
+			if (d > transition_lines[i].width) {
+				continue;
+			}
+
+			if (d < closest_d) {
+				closest = i;
+				closest_d = d;
+			}
+		}
+
+		if (closest >= 0) {
+			selected_transition_from = transition_lines[closest].from_node;
+			selected_transition_to = transition_lines[closest].to_node;
+
+			Ref<AnimationNodeStateMachineTransition> tr = state_machine->get_transition(closest);
+			EditorNode::get_singleton()->push_item(tr.ptr(), "", true);
+		}
+
+		state_machine_draw->update();
+		_update_mode();
+	}
+
+	//end moving node
+	if (mb.is_valid() && dragging_selected_attempt && mb->get_button_index() == BUTTON_LEFT && !mb->is_pressed()) {
+
+		if (dragging_selected) {
+
+			Ref<AnimationNode> an = state_machine->get_node(selected_node);
+			updating = true;
+			undo_redo->create_action("Move Node");
+			undo_redo->add_do_method(an.ptr(), "set_position", an->get_position() + drag_ofs / EDSCALE);
+			undo_redo->add_undo_method(an.ptr(), "set_position", an->get_position());
+			undo_redo->add_do_method(this, "_update_graph");
+			undo_redo->add_undo_method(this, "_update_graph");
+			undo_redo->commit_action();
+			updating = false;
+		}
+		snap_x = StringName();
+		snap_y = StringName();
+
+		dragging_selected_attempt = false;
+		dragging_selected = false;
+		state_machine_draw->update();
+	}
+
+	//connect nodes
+	if (mb.is_valid() && ((tool_select->is_pressed() && mb->get_shift()) || tool_connect->is_pressed()) && mb->get_button_index() == BUTTON_LEFT && mb->is_pressed()) {
+
+		for (int i = node_rects.size() - 1; i >= 0; i--) { //inverse to draw order
+			if (node_rects[i].node.has_point(mb->get_position())) { //select node since nothing else was selected
+				connecting = true;
+				connecting_from = node_rects[i].node_name;
+				connecting_to = mb->get_position();
+				connecting_to_node = StringName();
+				return;
+			}
+		}
+	}
+
+	//end connecting nodes
+	if (mb.is_valid() && connecting && mb->get_button_index() == BUTTON_LEFT && !mb->is_pressed()) {
+
+		if (connecting_to_node != StringName()) {
+
+			if (state_machine->has_transition(connecting_from, connecting_to_node)) {
+				EditorNode::get_singleton()->show_warning("Transition exists!");
+
+			} else {
+
+				Ref<AnimationNodeStateMachineTransition> tr;
+				tr.instance();
+				tr->set_switch_mode(AnimationNodeStateMachineTransition::SwitchMode(transition_mode->get_selected()));
+
+				updating = true;
+				undo_redo->create_action("Add Transition");
+				undo_redo->add_do_method(state_machine.ptr(), "add_transition", connecting_from, connecting_to_node, tr);
+				undo_redo->add_undo_method(state_machine.ptr(), "remove_transition", connecting_from, connecting_to_node);
+				undo_redo->add_do_method(this, "_update_graph");
+				undo_redo->add_undo_method(this, "_update_graph");
+				undo_redo->commit_action();
+				updating = false;
+
+				selected_transition_from = connecting_from;
+				selected_transition_to = connecting_to_node;
+
+				EditorNode::get_singleton()->push_item(tr.ptr(), "", true);
+				_update_mode();
+			}
+		}
+		connecting_to_node = StringName();
+		connecting = false;
+		state_machine_draw->update();
+	}
+
+	Ref<InputEventMouseMotion> mm = p_event;
+
+	//pan window
+	if (mm.is_valid() && mm->get_button_mask() & BUTTON_MASK_MIDDLE) {
+
+		h_scroll->set_value(h_scroll->get_value() - mm->get_relative().x);
+		v_scroll->set_value(v_scroll->get_value() - mm->get_relative().y);
+	}
+
+	//move mouse while connecting
+	if (mm.is_valid() && connecting) {
+
+		connecting_to = mm->get_position();
+		connecting_to_node = StringName();
+		state_machine_draw->update();
+
+		for (int i = node_rects.size() - 1; i >= 0; i--) { //inverse to draw order
+			if (node_rects[i].node_name != connecting_from && node_rects[i].node.has_point(connecting_to)) { //select node since nothing else was selected
+				connecting_to_node = node_rects[i].node_name;
+				return;
+			}
+		}
+	}
+
+	//move mouse while moving a node
+	if (mm.is_valid() && dragging_selected_attempt) {
+
+		dragging_selected = true;
+		drag_ofs = mm->get_position() - drag_from;
+		snap_x = StringName();
+		snap_y = StringName();
+		{
+			//snap
+			Vector2 cpos = state_machine->get_node(selected_node)->get_position() + drag_ofs / EDSCALE;
+			List<StringName> nodes;
+			state_machine->get_node_list(&nodes);
+
+			float best_d_x = 1e20;
+			float best_d_y = 1e20;
+
+			for (List<StringName>::Element *E = nodes.front(); E; E = E->next()) {
+				if (E->get() == selected_node)
+					continue;
+				Vector2 npos = state_machine->get_node(E->get())->get_position();
+
+				float d_x = ABS(npos.x - cpos.x);
+				if (d_x < MIN(5, best_d_x)) {
+					drag_ofs.x -= cpos.x - npos.x;
+					best_d_x = d_x;
+					snap_x = E->get();
+				}
+
+				float d_y = ABS(npos.y - cpos.y);
+				if (d_y < MIN(5, best_d_y)) {
+					drag_ofs.y -= cpos.y - npos.y;
+					best_d_y = d_y;
+					snap_y = E->get();
+				}
+			}
+		}
+
+		state_machine_draw->update();
+	}
+
+	//put ibeam (text cursor) over names to make it clearer that they are editable
+	if (mm.is_valid()) {
+
+		state_machine_draw->grab_focus();
+
+		bool over_text_now = false;
+		String new_over_node = StringName();
+		int new_over_node_what = -1;
+		if (tool_select->is_pressed()) {
+
+			for (int i = node_rects.size() - 1; i >= 0; i--) { //inverse to draw order
+
+				if (node_rects[i].name.has_point(mm->get_position())) {
+					over_text_now = true;
+					break;
+				}
+
+				if (node_rects[i].node.has_point(mm->get_position())) {
+					new_over_node = node_rects[i].node_name;
+					if (node_rects[i].play.has_point(mm->get_position())) {
+						new_over_node_what = 0;
+					}
+					if (node_rects[i].edit.has_point(mm->get_position())) {
+						new_over_node_what = 1;
+					}
+				}
+			}
+		}
+
+		if (new_over_node != over_node || new_over_node_what != over_node_what) {
+			over_node = new_over_node;
+			over_node_what = new_over_node_what;
+			state_machine_draw->update();
+		}
+
+		if (over_text != over_text_now) {
+
+			if (over_text_now) {
+				state_machine_draw->set_default_cursor_shape(CURSOR_IBEAM);
+			} else {
+				state_machine_draw->set_default_cursor_shape(CURSOR_ARROW);
+			}
+
+			over_text = over_text_now;
+		}
+	}
+}
+
+void AnimationNodeStateMachineEditor::_add_menu_type(int p_index) {
+
+	String type = menu->get_item_metadata(p_index);
+
+	Object *obj = ClassDB::instance(type);
+	ERR_FAIL_COND(!obj);
+	AnimationNode *an = Object::cast_to<AnimationNode>(obj);
+	ERR_FAIL_COND(!an);
+
+	Ref<AnimationNode> node(an);
+	node->set_position(add_node_pos);
+
+	String base_name = type.replace_first("AnimationNode", "");
+	int base = 1;
+	String name = base_name;
+	while (state_machine->has_node(name)) {
+		base++;
+		name = base_name + " " + itos(base);
+	}
+
+	updating = true;
+	undo_redo->create_action("Add Node");
+	undo_redo->add_do_method(state_machine.ptr(), "add_node", name, node);
+	undo_redo->add_undo_method(state_machine.ptr(), "remove_node", name);
+	undo_redo->add_do_method(this, "_update_graph");
+	undo_redo->add_undo_method(this, "_update_graph");
+	undo_redo->commit_action();
+	updating = false;
+
+	state_machine_draw->update();
+}
+
+void AnimationNodeStateMachineEditor::_add_animation_type(int p_index) {
+
+	Ref<AnimationNodeAnimation> anim;
+	anim.instance();
+
+	anim->set_animation(animations_to_add[p_index]);
+
+	String base_name = animations_to_add[p_index];
+	int base = 1;
+	String name = base_name;
+	while (state_machine->has_node(name)) {
+		base++;
+		name = base_name + " " + itos(base);
+	}
+
+	anim->set_position(add_node_pos);
+
+	updating = true;
+	undo_redo->create_action("Add Node");
+	undo_redo->add_do_method(state_machine.ptr(), "add_node", name, anim);
+	undo_redo->add_undo_method(state_machine.ptr(), "remove_node", name);
+	undo_redo->add_do_method(this, "_update_graph");
+	undo_redo->add_undo_method(this, "_update_graph");
+	undo_redo->commit_action();
+	updating = false;
+
+	state_machine_draw->update();
+}
+
+void AnimationNodeStateMachineEditor::_connection_draw(const Vector2 &p_from, const Vector2 &p_to, AnimationNodeStateMachineTransition::SwitchMode p_mode, bool p_enabled, bool p_selected, bool p_travel, bool p_auto_advance) {
+
+	Color linecolor = get_color("font_color", "Label");
+	Color icon_color(1, 1, 1);
+	Color accent = get_color("accent_color", "Editor");
+
+	if (!p_enabled) {
+		linecolor.a *= 0.2;
+		icon_color.a *= 0.2;
+		accent.a *= 0.6;
+	}
+
+	Ref<Texture> icons[6] = {
+		get_icon("TransitionImmediateBig", "EditorIcons"),
+		get_icon("TransitionSyncBig", "EditorIcons"),
+		get_icon("TransitionEndBig", "EditorIcons"),
+		get_icon("TransitionImmediateAutoBig", "EditorIcons"),
+		get_icon("TransitionSyncAutoBig", "EditorIcons"),
+		get_icon("TransitionEndAutoBig", "EditorIcons")
+	};
+
+	if (p_selected) {
+		state_machine_draw->draw_line(p_from, p_to, accent, 6, true);
+	}
+
+	if (p_travel) {
+		linecolor = accent;
+		linecolor.set_hsv(1.0, linecolor.get_s(), linecolor.get_v());
+	}
+	state_machine_draw->draw_line(p_from, p_to, linecolor, 2, true);
+
+	Ref<Texture> icon = icons[p_mode + (p_auto_advance ? 3 : 0)];
+
+	Transform2D xf;
+	xf.elements[0] = (p_to - p_from).normalized();
+	xf.elements[1] = xf.elements[0].tangent();
+	xf.elements[2] = (p_from + p_to) * 0.5 - xf.elements[1] * icon->get_height() * 0.5 - xf.elements[0] * icon->get_height() * 0.5;
+
+	state_machine_draw->draw_set_transform_matrix(xf);
+	state_machine_draw->draw_texture(icon, Vector2(), icon_color);
+	state_machine_draw->draw_set_transform_matrix(Transform2D());
+}
+
+void AnimationNodeStateMachineEditor::_clip_src_line_to_rect(Vector2 &r_from, Vector2 &r_to, const Rect2 &p_rect) {
+
+	if (r_to == r_from)
+		return;
+
+	//this could be optimized...
+	Vector2 n = (r_to - r_from).normalized();
+	while (p_rect.has_point(r_from)) {
+		r_from += n;
+	}
+}
+
+void AnimationNodeStateMachineEditor::_clip_dst_line_to_rect(Vector2 &r_from, Vector2 &r_to, const Rect2 &p_rect) {
+
+	if (r_to == r_from)
+		return;
+
+	//this could be optimized...
+	Vector2 n = (r_to - r_from).normalized();
+	while (p_rect.has_point(r_to)) {
+		r_to -= n;
+	}
+}
+
+void AnimationNodeStateMachineEditor::_state_machine_draw() {
+
+	Ref<StyleBox> style = get_stylebox("frame", "GraphNode");
+	Ref<StyleBox> style_selected = get_stylebox("selectedframe", "GraphNode");
+
+	Ref<Font> font = get_font("title_font", "GraphNode");
+	Color font_color = get_color("title_color", "GraphNode");
+	Ref<Texture> play = get_icon("Play", "EditorIcons");
+	Ref<Texture> auto_play = get_icon("AutoPlay", "EditorIcons");
+	Ref<Texture> edit = get_icon("Edit", "EditorIcons");
+	Color accent = get_color("accent_color", "Editor");
+	Color linecolor = get_color("font_color", "Label");
+	linecolor.a *= 0.3;
+	Ref<StyleBox> playing_overlay = get_stylebox("position", "GraphNode");
+
+	bool playing = state_machine->is_playing();
+	StringName current = state_machine->get_current_node();
+	StringName blend_from = state_machine->get_blend_from_node();
+	Vector<StringName> travel_path = state_machine->get_travel_path();
+
+	if (state_machine_draw->has_focus()) {
+		state_machine_draw->draw_rect(Rect2(Point2(), state_machine_draw->get_size()), accent, false);
+	}
+	int sep = 3 * EDSCALE;
+
+	List<StringName> nodes;
+	state_machine->get_node_list(&nodes);
+
+	node_rects.clear();
+	Rect2 scroll_range(Point2(), state_machine_draw->get_size());
+
+	//snap lines
+	if (dragging_selected) {
+
+		Vector2 from = (state_machine->get_node(selected_node)->get_position() * EDSCALE) + drag_ofs - state_machine->get_graph_offset() * EDSCALE;
+		if (snap_x != StringName()) {
+			Vector2 to = (state_machine->get_node(snap_x)->get_position() * EDSCALE) - state_machine->get_graph_offset() * EDSCALE;
+			state_machine_draw->draw_line(from, to, linecolor, 2);
+		}
+		if (snap_y != StringName()) {
+			Vector2 to = (state_machine->get_node(snap_y)->get_position() * EDSCALE) - state_machine->get_graph_offset() * EDSCALE;
+			state_machine_draw->draw_line(from, to, linecolor, 2);
+		}
+	}
+
+	//pre pass nodes so we know the rectangles
+	for (List<StringName>::Element *E = nodes.front(); E; E = E->next()) {
+
+		Ref<AnimationNode> anode = state_machine->get_node(E->get());
+		String name = E->get();
+		bool needs_editor = EditorNode::get_singleton()->item_has_editor(anode.ptr());
+		Ref<StyleBox> sb = E->get() == selected_node ? style_selected : style;
+
+		Size2 s = sb->get_minimum_size();
+		int strsize = font->get_string_size(name).width;
+		s.width += strsize;
+		s.height += MAX(font->get_height(), play->get_height());
+		s.width += sep + play->get_width();
+		if (needs_editor) {
+			s.width += sep + edit->get_width();
+		}
+
+		Vector2 offset;
+		offset += anode->get_position() * EDSCALE;
+		if (selected_node == E->get() && dragging_selected) {
+			offset += drag_ofs;
+		}
+		offset -= s / 2;
+		offset = offset.floor();
+
+		//prepre rect
+
+		NodeRect nr;
+		nr.node = Rect2(offset, s);
+		nr.node_name = E->get();
+
+		scroll_range = scroll_range.merge(nr.node); //merge with range
+
+		//now scroll it to draw
+		nr.node.position -= state_machine->get_graph_offset() * EDSCALE;
+
+		node_rects.push_back(nr);
+	}
+
+	transition_lines.clear();
+
+	//draw conecting line for potential new transition
+	if (connecting) {
+		Vector2 from = (state_machine->get_node(connecting_from)->get_position() * EDSCALE) - state_machine->get_graph_offset() * EDSCALE;
+		Vector2 to;
+		if (connecting_to_node != StringName()) {
+			to = (state_machine->get_node(connecting_to_node)->get_position() * EDSCALE) - state_machine->get_graph_offset() * EDSCALE;
+		} else {
+			to = connecting_to;
+		}
+
+		for (int i = 0; i < node_rects.size(); i++) {
+			if (node_rects[i].node_name == connecting_from) {
+				_clip_src_line_to_rect(from, to, node_rects[i].node);
+			}
+			if (node_rects[i].node_name == connecting_to_node) {
+				_clip_dst_line_to_rect(from, to, node_rects[i].node);
+			}
+		}
+
+		_connection_draw(from, to, AnimationNodeStateMachineTransition::SwitchMode(transition_mode->get_selected()), true, false, false, false);
+	}
+
+	Ref<Texture> tr_reference_icon = get_icon("TransitionImmediateBig", "EditorIcons");
+	float tr_bidi_offset = int(tr_reference_icon->get_height() * 0.8);
+
+	//draw transition lines
+	for (int i = 0; i < state_machine->get_transition_count(); i++) {
+
+		TransitionLine tl;
+		tl.from_node = state_machine->get_transition_from(i);
+		Vector2 ofs_from = (dragging_selected && tl.from_node == selected_node) ? drag_ofs : Vector2();
+		tl.from = (state_machine->get_node(tl.from_node)->get_position() * EDSCALE) + ofs_from - state_machine->get_graph_offset() * EDSCALE;
+
+		tl.to_node = state_machine->get_transition_to(i);
+		Vector2 ofs_to = (dragging_selected && tl.to_node == selected_node) ? drag_ofs : Vector2();
+		tl.to = (state_machine->get_node(tl.to_node)->get_position() * EDSCALE) + ofs_to - state_machine->get_graph_offset() * EDSCALE;
+
+		Ref<AnimationNodeStateMachineTransition> tr = state_machine->get_transition(i);
+		tl.disabled = tr->is_disabled();
+		tl.auto_advance = tr->has_auto_advance();
+		tl.mode = tr->get_switch_mode();
+		tl.width = tr_bidi_offset;
+
+		if (state_machine->has_transition(tl.to_node, tl.from_node)) { //offset if same exists
+			Vector2 offset = -(tl.from - tl.to).normalized().tangent() * tr_bidi_offset;
+			tl.from += offset;
+			tl.to += offset;
+		}
+
+		for (int i = 0; i < node_rects.size(); i++) {
+			if (node_rects[i].node_name == tl.from_node) {
+				_clip_src_line_to_rect(tl.from, tl.to, node_rects[i].node);
+			}
+			if (node_rects[i].node_name == tl.to_node) {
+				_clip_dst_line_to_rect(tl.from, tl.to, node_rects[i].node);
+			}
+		}
+
+		bool selected = selected_transition_from == tl.from_node && selected_transition_to == tl.to_node;
+
+		bool travel = false;
+
+		if (blend_from == tl.from_node && current == tl.to_node) {
+			travel = true;
+		}
+
+		if (travel_path.size()) {
+
+			if (current == tl.from_node && travel_path[0] == tl.to_node) {
+				travel = true;
+			} else {
+				for (int j = 0; j < travel_path.size() - 1; j++) {
+					if (travel_path[j] == tl.from_node && travel_path[j + 1] == tl.to_node) {
+						travel = true;
+						break;
+					}
+				}
+			}
+		}
+		_connection_draw(tl.from, tl.to, tl.mode, !tl.disabled, selected, travel, tl.auto_advance);
+
+		transition_lines.push_back(tl);
+	}
+
+	//draw actual nodes
+	for (int i = 0; i < node_rects.size(); i++) {
+
+		String name = node_rects[i].node_name;
+		Ref<AnimationNode> anode = state_machine->get_node(name);
+		bool needs_editor = EditorNode::get_singleton()->item_has_editor(anode.ptr());
+		Ref<StyleBox> sb = name == selected_node ? style_selected : style;
+		int strsize = font->get_string_size(name).width;
+
+		NodeRect &nr = node_rects[i];
+
+		Vector2 offset = nr.node.position;
+		int h = nr.node.size.height;
+
+		//prepre rect
+
+		//now scroll it to draw
+		state_machine_draw->draw_style_box(sb, nr.node);
+
+		if (playing && (blend_from == name || current == name || travel_path.find(name) != -1)) {
+			state_machine_draw->draw_style_box(playing_overlay, nr.node);
+		}
+
+		bool onstart = state_machine->get_start_node() == name;
+		if (onstart) {
+			state_machine_draw->draw_string(font, offset + Vector2(0, -font->get_height() - 3 * EDSCALE + font->get_ascent()), TTR("Start"), font_color);
+		}
+
+		if (state_machine->get_end_node() == name) {
+
+			int endofs = nr.node.size.x - font->get_string_size(TTR("End")).x;
+			state_machine_draw->draw_string(font, offset + Vector2(endofs, -font->get_height() - 3 * EDSCALE + font->get_ascent()), TTR("End"), font_color);
+		}
+
+		offset.x += sb->get_offset().x;
+
+		nr.play.position = offset + Vector2(0, (h - play->get_height()) / 2).floor();
+		nr.play.size = play->get_size();
+
+		Ref<Texture> play_tex = onstart ? auto_play : play;
+
+		if (over_node == name && over_node_what == 0) {
+			state_machine_draw->draw_texture(play_tex, nr.play.position, accent);
+		} else {
+			state_machine_draw->draw_texture(play_tex, nr.play.position);
+		}
+		offset.x += sep + play->get_width();
+
+		nr.name.position = offset + Vector2(0, (h - font->get_height()) / 2).floor();
+		nr.name.size = Vector2(strsize, font->get_height());
+
+		state_machine_draw->draw_string(font, nr.name.position + Vector2(0, font->get_ascent()), name, font_color);
+		offset.x += strsize + sep;
+
+		if (needs_editor) {
+			nr.edit.position = offset + Vector2(0, (h - edit->get_height()) / 2).floor();
+			nr.edit.size = edit->get_size();
+
+			if (over_node == name && over_node_what == 1) {
+				state_machine_draw->draw_texture(edit, nr.edit.position, accent);
+			} else {
+				state_machine_draw->draw_texture(edit, nr.edit.position);
+			}
+			offset.x += sep + edit->get_width();
+		}
+	}
+
+	scroll_range = scroll_range.grow(200 * EDSCALE);
+
+	//adjust scrollbars
+	updating = true;
+	h_scroll->set_min(scroll_range.position.x);
+	h_scroll->set_max(scroll_range.position.x + scroll_range.size.x);
+	h_scroll->set_page(state_machine_draw->get_size().x);
+	h_scroll->set_value(state_machine->get_graph_offset().x);
+
+	v_scroll->set_min(scroll_range.position.y);
+	v_scroll->set_max(scroll_range.position.y + scroll_range.size.y);
+	v_scroll->set_page(state_machine_draw->get_size().y);
+	v_scroll->set_value(state_machine->get_graph_offset().y);
+	updating = false;
+
+	state_machine_play_pos->update();
+}
+
+void AnimationNodeStateMachineEditor::_state_machine_pos_draw() {
+
+	if (!state_machine->is_playing())
+		return;
+
+	int idx = -1;
+	for (int i = 0; node_rects.size(); i++) {
+		if (node_rects[i].node_name == state_machine->get_current_node()) {
+			idx = i;
+			break;
+		}
+	}
+
+	if (idx == -1)
+		return;
+
+	NodeRect &nr = node_rects[idx];
+
+	Vector2 from;
+	from.x = nr.play.position.x;
+	from.y = (nr.play.position.y + nr.play.size.y + nr.node.position.y + nr.node.size.y) * 0.5;
+
+	Vector2 to;
+	if (nr.edit.size.x) {
+		to.x = nr.edit.position.x + nr.edit.size.x;
+	} else {
+		to.x = nr.name.position.x + nr.name.size.x;
+	}
+	to.y = from.y;
+
+	float len = MAX(0.0001, state_machine->get_current_length());
+
+	float pos = CLAMP(state_machine->get_current_play_pos(), 0, len);
+	float c = pos / len;
+	Color fg = get_color("font_color", "Label");
+	Color bg = fg;
+	bg.a *= 0.3;
+
+	state_machine_play_pos->draw_line(from, to, bg, 2);
+
+	to = from.linear_interpolate(to, c);
+
+	state_machine_play_pos->draw_line(from, to, fg, 2);
+}
+
+void AnimationNodeStateMachineEditor::_update_graph() {
+
+	if (updating)
+		return;
+
+	updating = true;
+
+	if (state_machine->get_parent().is_valid()) {
+		goto_parent_hbox->show();
+	} else {
+		goto_parent_hbox->hide();
+	}
+
+	state_machine_draw->update();
+
+	updating = false;
+}
+
+void AnimationNodeStateMachineEditor::_notification(int p_what) {
+
+	if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {
+		error_panel->add_style_override("panel", get_stylebox("bg", "Tree"));
+		error_label->add_color_override("font_color", get_color("error_color", "Editor"));
+		panel->add_style_override("panel", get_stylebox("bg", "Tree"));
+		goto_parent->set_icon(get_icon("MoveUp", "EditorIcons"));
+
+		tool_select->set_icon(get_icon("ToolSelect", "EditorIcons"));
+		tool_create->set_icon(get_icon("ToolAddNode", "EditorIcons"));
+		tool_connect->set_icon(get_icon("ToolConnect", "EditorIcons"));
+
+		transition_mode->clear();
+		transition_mode->add_icon_item(get_icon("TransitionImmediate", "EditorIcons"), TTR("Immediate"));
+		transition_mode->add_icon_item(get_icon("TransitionSync", "EditorIcons"), TTR("Sync"));
+		transition_mode->add_icon_item(get_icon("TransitionEnd", "EditorIcons"), TTR("At End"));
+
+		//force filter on those, so they deform better
+		get_icon("TransitionImmediateBig", "EditorIcons")->set_flags(Texture::FLAG_FILTER);
+		get_icon("TransitionEndBig", "EditorIcons")->set_flags(Texture::FLAG_FILTER);
+		get_icon("TransitionSyncBig", "EditorIcons")->set_flags(Texture::FLAG_FILTER);
+		get_icon("TransitionImmediateAutoBig", "EditorIcons")->set_flags(Texture::FLAG_FILTER);
+		get_icon("TransitionEndAutoBig", "EditorIcons")->set_flags(Texture::FLAG_FILTER);
+		get_icon("TransitionSyncAutoBig", "EditorIcons")->set_flags(Texture::FLAG_FILTER);
+
+		tool_erase->set_icon(get_icon("Remove", "EditorIcons"));
+		tool_autoplay->set_icon(get_icon("AutoPlay", "EditorIcons"));
+		tool_end->set_icon(get_icon("AutoEnd", "EditorIcons"));
+
+		play_mode->clear();
+		play_mode->add_icon_item(get_icon("PlayTravel", "EditorIcons"), TTR("Travel"));
+		play_mode->add_icon_item(get_icon("Play", "EditorIcons"), TTR("Immediate"));
+	}
+
+	if (p_what == NOTIFICATION_PROCESS) {
+
+		String error;
+
+		if (error_time > 0) {
+			error = error_text;
+			error_time -= get_process_delta_time();
+		} else if (!state_machine->get_graph_player()) {
+			error = TTR("StateMachine does not belong to an AnimationGraphPlayer node.");
+		} else if (!state_machine->get_graph_player()->is_active()) {
+			error = TTR("AnimationGraphPlayer is inactive.\nActivate to enable playback, check node warnings if activation fails.");
+		} else if (state_machine->get_graph_player()->is_state_invalid()) {
+			error = state_machine->get_graph_player()->get_invalid_state_reason();
+		} else if (state_machine->get_parent().is_valid() && state_machine->get_parent()->is_class("AnimationNodeStateMachine")) {
+			if (state_machine->get_start_node() == StringName() || state_machine->get_end_node() == StringName()) {
+				error = TTR("Start and end nodes are needed for a sub-transition.");
+			}
+		}
+
+		if (error != error_label->get_text()) {
+			error_label->set_text(error);
+			if (error != String()) {
+				error_panel->show();
+			} else {
+				error_panel->hide();
+			}
+		}
+
+		for (int i = 0; i < transition_lines.size(); i++) {
+			int tidx = -1;
+			for (int j = 0; j < state_machine->get_transition_count(); j++) {
+				if (transition_lines[i].from_node == state_machine->get_transition_from(j) && transition_lines[i].to_node == state_machine->get_transition_to(j)) {
+					tidx = j;
+					break;
+				}
+			}
+
+			if (tidx == -1) { //missing transition, should redraw
+				state_machine_draw->update();
+				break;
+			}
+
+			if (transition_lines[i].disabled != state_machine->get_transition(tidx)->is_disabled()) {
+				state_machine_draw->update();
+				break;
+			}
+
+			if (transition_lines[i].auto_advance != state_machine->get_transition(tidx)->has_auto_advance()) {
+				state_machine_draw->update();
+				break;
+			}
+
+			if (transition_lines[i].mode != state_machine->get_transition(tidx)->get_switch_mode()) {
+				state_machine_draw->update();
+				break;
+			}
+		}
+
+		bool same_travel_path = true;
+		Vector<StringName> tp = state_machine->get_travel_path();
+
+		{
+
+			if (last_travel_path.size() != tp.size()) {
+				same_travel_path = false;
+			} else {
+				for (int i = 0; i < last_travel_path.size(); i++) {
+					if (last_travel_path[i] != tp[i]) {
+						same_travel_path = false;
+						break;
+					}
+				}
+			}
+		}
+
+		//update if travel state changed
+		if (!same_travel_path || last_active != state_machine->is_playing() || last_current_node != state_machine->get_current_node() || last_blend_from_node != state_machine->get_blend_from_node()) {
+
+			state_machine_draw->update();
+			last_travel_path = tp;
+			last_current_node = state_machine->get_current_node();
+			last_active = state_machine->is_playing();
+			last_blend_from_node = state_machine->get_blend_from_node();
+			state_machine_play_pos->update();
+		}
+
+		if (last_play_pos != state_machine->get_current_play_pos()) {
+
+			last_play_pos = state_machine->get_current_play_pos();
+			state_machine_play_pos->update();
+		}
+	}
+
+	if (p_what == NOTIFICATION_VISIBILITY_CHANGED) {
+		over_node = StringName();
+	}
+}
+
+void AnimationNodeStateMachineEditor::_open_editor(const String &p_name) {
+	Ref<AnimationNode> an = state_machine->get_node(p_name);
+	ERR_FAIL_COND(!an.is_valid());
+	EditorNode::get_singleton()->edit_item(an.ptr());
+}
+
+void AnimationNodeStateMachineEditor::_goto_parent() {
+
+	EditorNode::get_singleton()->edit_item(state_machine->get_parent().ptr());
+}
+
+void AnimationNodeStateMachineEditor::_removed_from_graph() {
+	EditorNode::get_singleton()->edit_item(NULL);
+}
+
+void AnimationNodeStateMachineEditor::_name_edited(const String &p_text) {
+
+	String new_name = p_text;
+
+	ERR_FAIL_COND(new_name == "" || new_name.find(".") != -1 || new_name.find("/") != -1)
+
+	ERR_FAIL_COND(new_name == prev_name);
+
+	String base_name = new_name;
+	int base = 1;
+	String name = base_name;
+	while (state_machine->has_node(name)) {
+		base++;
+		name = base_name + " " + itos(base);
+	}
+
+	updating = true;
+	undo_redo->create_action("Node Renamed");
+	undo_redo->add_do_method(state_machine.ptr(), "rename_node", prev_name, name);
+	undo_redo->add_undo_method(state_machine.ptr(), "rename_node", name, prev_name);
+	undo_redo->add_do_method(this, "_update_graph");
+	undo_redo->add_undo_method(this, "_update_graph");
+	undo_redo->commit_action();
+	updating = false;
+
+	state_machine_draw->update();
+
+	name_edit->hide();
+}
+
+void AnimationNodeStateMachineEditor::_scroll_changed(double) {
+	if (updating)
+		return;
+
+	state_machine->set_graph_offset(Vector2(h_scroll->get_value(), v_scroll->get_value()));
+	state_machine_draw->update();
+}
+
+void AnimationNodeStateMachineEditor::_erase_selected() {
+
+	if (selected_node != StringName() && state_machine->has_node(selected_node)) {
+		updating = true;
+		undo_redo->create_action("Node Removed");
+		undo_redo->add_do_method(state_machine.ptr(), "remove_node", selected_node);
+		undo_redo->add_undo_method(state_machine.ptr(), "add_node", selected_node, state_machine->get_node(selected_node));
+		for (int i = 0; i < state_machine->get_transition_count(); i++) {
+			String from = state_machine->get_transition_from(i);
+			String to = state_machine->get_transition_to(i);
+			if (from == selected_node || to == selected_node) {
+				undo_redo->add_undo_method(state_machine.ptr(), "add_transition", from, to, state_machine->get_transition(i));
+			}
+		}
+		if (String(state_machine->get_start_node()) == selected_node) {
+			undo_redo->add_undo_method(state_machine.ptr(), "set_start_node", selected_node);
+		}
+		undo_redo->add_do_method(this, "_update_graph");
+		undo_redo->add_undo_method(this, "_update_graph");
+		undo_redo->commit_action();
+		updating = false;
+		selected_node = StringName();
+	}
+
+	if (selected_transition_to != StringName() && selected_transition_from != StringName() && state_machine->has_transition(selected_transition_from, selected_transition_to)) {
+
+		Ref<AnimationNodeStateMachineTransition> tr = state_machine->get_transition(state_machine->find_transition(selected_transition_from, selected_transition_to));
+		updating = true;
+		undo_redo->create_action("Transition Removed");
+		undo_redo->add_do_method(state_machine.ptr(), "remove_transition", selected_transition_from, selected_transition_to);
+		undo_redo->add_undo_method(state_machine.ptr(), "add_transition", selected_transition_from, selected_transition_to, tr);
+		undo_redo->add_do_method(this, "_update_graph");
+		undo_redo->add_undo_method(this, "_update_graph");
+		undo_redo->commit_action();
+		updating = false;
+		selected_transition_from = StringName();
+		selected_transition_to = StringName();
+	}
+
+	state_machine_draw->update();
+}
+
+void AnimationNodeStateMachineEditor::_autoplay_selected() {
+
+	if (selected_node != StringName() && state_machine->has_node(selected_node)) {
+
+		StringName new_start_node;
+		if (state_machine->get_start_node() == selected_node) { //toggle it
+			new_start_node = StringName();
+		} else {
+			new_start_node = selected_node;
+		}
+
+		updating = true;
+		undo_redo->create_action("Set Start Node (Autoplay)");
+		undo_redo->add_do_method(state_machine.ptr(), "set_start_node", new_start_node);
+		undo_redo->add_undo_method(state_machine.ptr(), "set_start_node", state_machine->get_start_node());
+		undo_redo->add_do_method(this, "_update_graph");
+		undo_redo->add_undo_method(this, "_update_graph");
+		undo_redo->commit_action();
+		updating = false;
+		state_machine_draw->update();
+	}
+}
+
+void AnimationNodeStateMachineEditor::_end_selected() {
+
+	if (selected_node != StringName() && state_machine->has_node(selected_node)) {
+
+		StringName new_end_node;
+		if (state_machine->get_end_node() == selected_node) { //toggle it
+			new_end_node = StringName();
+		} else {
+			new_end_node = selected_node;
+		}
+
+		updating = true;
+		undo_redo->create_action("Set Start Node (Autoplay)");
+		undo_redo->add_do_method(state_machine.ptr(), "set_end_node", new_end_node);
+		undo_redo->add_undo_method(state_machine.ptr(), "set_end_node", state_machine->get_end_node());
+		undo_redo->add_do_method(this, "_update_graph");
+		undo_redo->add_undo_method(this, "_update_graph");
+		undo_redo->commit_action();
+		updating = false;
+		state_machine_draw->update();
+	}
+}
+void AnimationNodeStateMachineEditor::_update_mode() {
+
+	if (tool_select->is_pressed()) {
+		tool_erase_hb->show();
+		tool_erase->set_disabled(selected_node == StringName() && selected_transition_from == StringName() && selected_transition_to == StringName());
+		tool_autoplay->set_disabled(selected_node == StringName());
+		tool_end->set_disabled(selected_node == StringName());
+	} else {
+		tool_erase_hb->hide();
+	}
+}
+
+void AnimationNodeStateMachineEditor::_bind_methods() {
+
+	ClassDB::bind_method("_state_machine_gui_input", &AnimationNodeStateMachineEditor::_state_machine_gui_input);
+	ClassDB::bind_method("_state_machine_draw", &AnimationNodeStateMachineEditor::_state_machine_draw);
+	ClassDB::bind_method("_state_machine_pos_draw", &AnimationNodeStateMachineEditor::_state_machine_pos_draw);
+	ClassDB::bind_method("_update_graph", &AnimationNodeStateMachineEditor::_update_graph);
+
+	ClassDB::bind_method("_add_menu_type", &AnimationNodeStateMachineEditor::_add_menu_type);
+	ClassDB::bind_method("_add_animation_type", &AnimationNodeStateMachineEditor::_add_animation_type);
+
+	ClassDB::bind_method("_name_edited", &AnimationNodeStateMachineEditor::_name_edited);
+
+	ClassDB::bind_method("_goto_parent", &AnimationNodeStateMachineEditor::_goto_parent);
+	ClassDB::bind_method("_removed_from_graph", &AnimationNodeStateMachineEditor::_removed_from_graph);
+
+	ClassDB::bind_method("_open_editor", &AnimationNodeStateMachineEditor::_open_editor);
+	ClassDB::bind_method("_scroll_changed", &AnimationNodeStateMachineEditor::_scroll_changed);
+
+	ClassDB::bind_method("_erase_selected", &AnimationNodeStateMachineEditor::_erase_selected);
+	ClassDB::bind_method("_autoplay_selected", &AnimationNodeStateMachineEditor::_autoplay_selected);
+	ClassDB::bind_method("_end_selected", &AnimationNodeStateMachineEditor::_end_selected);
+	ClassDB::bind_method("_update_mode", &AnimationNodeStateMachineEditor::_update_mode);
+}
+
+AnimationNodeStateMachineEditor *AnimationNodeStateMachineEditor::singleton = NULL;
+
+AnimationNodeStateMachineEditor::AnimationNodeStateMachineEditor() {
+
+	singleton = this;
+	updating = false;
+
+	HBoxContainer *top_hb = memnew(HBoxContainer);
+	add_child(top_hb);
+
+	goto_parent_hbox = memnew(HBoxContainer);
+	goto_parent = memnew(ToolButton);
+	goto_parent->connect("pressed", this, "_goto_parent", varray(), CONNECT_DEFERRED);
+	goto_parent_hbox->add_child(goto_parent);
+	goto_parent_hbox->add_child(memnew(VSeparator));
+	top_hb->add_child(goto_parent_hbox);
+
+	Ref<ButtonGroup> bg;
+	bg.instance();
+
+	tool_select = memnew(ToolButton);
+	top_hb->add_child(tool_select);
+	tool_select->set_toggle_mode(true);
+	tool_select->set_button_group(bg);
+	tool_select->set_pressed(true);
+	tool_select->set_tooltip(TTR("Select and move nodes.\nRMB to add new nodes.\nShift+LMB to create connections."));
+	tool_select->connect("pressed", this, "_update_mode", varray(), CONNECT_DEFERRED);
+
+	tool_create = memnew(ToolButton);
+	top_hb->add_child(tool_create);
+	tool_create->set_toggle_mode(true);
+	tool_create->set_button_group(bg);
+	tool_create->set_tooltip(TTR("Create new nodes."));
+	tool_create->connect("pressed", this, "_update_mode", varray(), CONNECT_DEFERRED);
+
+	tool_connect = memnew(ToolButton);
+	top_hb->add_child(tool_connect);
+	tool_connect->set_toggle_mode(true);
+	tool_connect->set_button_group(bg);
+	tool_connect->set_tooltip(TTR("Connect nodes."));
+	tool_connect->connect("pressed", this, "_update_mode", varray(), CONNECT_DEFERRED);
+
+	tool_erase_hb = memnew(HBoxContainer);
+	top_hb->add_child(tool_erase_hb);
+	tool_erase_hb->add_child(memnew(VSeparator));
+	tool_erase = memnew(ToolButton);
+	tool_erase->set_tooltip(TTR("Remove selected node or transition"));
+	tool_erase_hb->add_child(tool_erase);
+	tool_erase->connect("pressed", this, "_erase_selected");
+	tool_erase->set_disabled(true);
+
+	tool_erase_hb->add_child(memnew(VSeparator));
+
+	tool_autoplay = memnew(ToolButton);
+	tool_autoplay->set_tooltip(TTR("Toggle autoplay this animation on start, restart or seek to zero."));
+	tool_erase_hb->add_child(tool_autoplay);
+	tool_autoplay->connect("pressed", this, "_autoplay_selected");
+	tool_autoplay->set_disabled(true);
+
+	tool_end = memnew(ToolButton);
+	tool_end->set_tooltip(TTR("Set the end animation. This is useful for sub-transitions."));
+	tool_erase_hb->add_child(tool_end);
+	tool_end->connect("pressed", this, "_end_selected");
+	tool_end->set_disabled(true);
+
+	top_hb->add_child(memnew(VSeparator));
+	top_hb->add_child(memnew(Label(TTR("Transition: "))));
+	transition_mode = memnew(OptionButton);
+	top_hb->add_child(transition_mode);
+
+	top_hb->add_spacer();
+
+	top_hb->add_child(memnew(Label("Play Mode:")));
+	play_mode = memnew(OptionButton);
+	top_hb->add_child(play_mode);
+
+	GridContainer *main_grid = memnew(GridContainer);
+	main_grid->set_columns(2);
+	add_child(main_grid);
+	main_grid->set_v_size_flags(SIZE_EXPAND_FILL);
+
+	panel = memnew(PanelContainer);
+	panel->set_clip_contents(true);
+	main_grid->add_child(panel);
+	panel->set_h_size_flags(SIZE_EXPAND_FILL);
+
+	state_machine_draw = memnew(Control);
+	state_machine_draw->connect("gui_input", this, "_state_machine_gui_input");
+	state_machine_draw->connect("draw", this, "_state_machine_draw");
+	state_machine_draw->set_focus_mode(FOCUS_ALL);
+
+	state_machine_play_pos = memnew(Control);
+	state_machine_draw->add_child(state_machine_play_pos);
+	state_machine_play_pos->set_mouse_filter(MOUSE_FILTER_PASS); //pass all to parent
+	state_machine_play_pos->set_anchors_and_margins_preset(PRESET_WIDE);
+	state_machine_play_pos->connect("draw", this, "_state_machine_pos_draw");
+
+	panel->add_child(state_machine_draw);
+	panel->set_v_size_flags(SIZE_EXPAND_FILL);
+
+	v_scroll = memnew(VScrollBar);
+	main_grid->add_child(v_scroll);
+	v_scroll->connect("value_changed", this, "_scroll_changed");
+
+	h_scroll = memnew(HScrollBar);
+	main_grid->add_child(h_scroll);
+	h_scroll->connect("value_changed", this, "_scroll_changed");
+
+	main_grid->add_child(memnew(Control)); //empty bottom right
+
+	error_panel = memnew(PanelContainer);
+	add_child(error_panel);
+	error_label = memnew(Label);
+	error_panel->add_child(error_label);
+	error_label->set_text("eh");
+
+	undo_redo = EditorNode::get_singleton()->get_undo_redo();
+
+	set_custom_minimum_size(Size2(0, 300 * EDSCALE));
+
+	menu = memnew(PopupMenu);
+	add_child(menu);
+	menu->connect("index_pressed", this, "_add_menu_type");
+
+	animations_menu = memnew(PopupMenu);
+	menu->add_child(animations_menu);
+	animations_menu->set_name("animations");
+	animations_menu->connect("index_pressed", this, "_add_animation_type");
+
+	name_edit = memnew(LineEdit);
+	state_machine_draw->add_child(name_edit);
+	name_edit->hide();
+	name_edit->connect("text_entered", this, "_name_edited");
+	name_edit->set_as_toplevel(true);
+
+	over_text = false;
+
+	over_node_what = -1;
+	dragging_selected_attempt = false;
+	connecting = false;
+
+	last_active = false;
+
+	error_time = 0;
+}
+
+void AnimationNodeStateMachineEditorPlugin::edit(Object *p_object) {
+
+	anim_tree_editor->edit(Object::cast_to<AnimationNodeStateMachine>(p_object));
+}
+
+bool AnimationNodeStateMachineEditorPlugin::handles(Object *p_object) const {
+
+	return p_object->is_class("AnimationNodeStateMachine");
+}
+
+void AnimationNodeStateMachineEditorPlugin::make_visible(bool p_visible) {
+
+	if (p_visible) {
+		//editor->hide_animation_player_editors();
+		//editor->animation_panel_make_visible(true);
+		button->show();
+		editor->make_bottom_panel_item_visible(anim_tree_editor);
+		anim_tree_editor->set_process(true);
+	} else {
+
+		if (anim_tree_editor->is_visible_in_tree())
+			editor->hide_bottom_panel();
+		button->hide();
+		anim_tree_editor->set_process(false);
+	}
+}
+
+AnimationNodeStateMachineEditorPlugin::AnimationNodeStateMachineEditorPlugin(EditorNode *p_node) {
+
+	editor = p_node;
+	anim_tree_editor = memnew(AnimationNodeStateMachineEditor);
+	anim_tree_editor->set_custom_minimum_size(Size2(0, 300));
+
+	button = editor->add_bottom_panel_item(TTR("StateMachine"), anim_tree_editor);
+	button->hide();
+}
+
+AnimationNodeStateMachineEditorPlugin::~AnimationNodeStateMachineEditorPlugin() {
+}

+ 167 - 0
editor/plugins/animation_state_machine_editor.h

@@ -0,0 +1,167 @@
+#ifndef ANIMATION_STATE_MACHINE_EDITOR_H
+#define ANIMATION_STATE_MACHINE_EDITOR_H
+
+#include "editor/editor_node.h"
+#include "editor/editor_plugin.h"
+#include "editor/property_editor.h"
+#include "scene/animation/animation_node_state_machine.h"
+#include "scene/gui/button.h"
+#include "scene/gui/graph_edit.h"
+#include "scene/gui/popup.h"
+#include "scene/gui/tree.h"
+
+class AnimationNodeStateMachineEditor : public VBoxContainer {
+
+	GDCLASS(AnimationNodeStateMachineEditor, VBoxContainer);
+
+	Ref<AnimationNodeStateMachine> state_machine;
+
+	ToolButton *tool_select;
+	ToolButton *tool_create;
+	ToolButton *tool_connect;
+	LineEdit *name_edit;
+
+	HBoxContainer *tool_erase_hb;
+	ToolButton *tool_erase;
+	ToolButton *tool_autoplay;
+	ToolButton *tool_end;
+
+	OptionButton *transition_mode;
+	OptionButton *play_mode;
+
+	HBoxContainer *goto_parent_hbox;
+	ToolButton *goto_parent;
+
+	PanelContainer *panel;
+
+	StringName selected_node;
+
+	HScrollBar *h_scroll;
+	VScrollBar *v_scroll;
+
+	Control *state_machine_draw;
+	Control *state_machine_play_pos;
+
+	PanelContainer *error_panel;
+	Label *error_label;
+
+	bool updating;
+
+	UndoRedo *undo_redo;
+
+	static AnimationNodeStateMachineEditor *singleton;
+
+	void _state_machine_gui_input(const Ref<InputEvent> &p_event);
+	void _connection_draw(const Vector2 &p_from, const Vector2 &p_to, AnimationNodeStateMachineTransition::SwitchMode p_mode, bool p_enabled, bool p_selected, bool p_travel, bool p_auto_advance);
+	void _state_machine_draw();
+	void _state_machine_pos_draw();
+
+	void _update_graph();
+
+	PopupMenu *menu;
+	PopupMenu *animations_menu;
+	Vector<String> animations_to_add;
+
+	Vector2 add_node_pos;
+
+	bool dragging_selected_attempt;
+	bool dragging_selected;
+	Vector2 drag_from;
+	Vector2 drag_ofs;
+	StringName snap_x;
+	StringName snap_y;
+
+	bool connecting;
+	StringName connecting_from;
+	Vector2 connecting_to;
+	StringName connecting_to_node;
+
+	void _add_menu_type(int p_index);
+	void _add_animation_type(int p_index);
+
+	void _goto_parent();
+
+	void _removed_from_graph();
+
+	struct NodeRect {
+		StringName node_name;
+		Rect2 node;
+		Rect2 play;
+		Rect2 name;
+		Rect2 edit;
+	};
+
+	Vector<NodeRect> node_rects;
+
+	struct TransitionLine {
+		StringName from_node;
+		StringName to_node;
+		Vector2 from;
+		Vector2 to;
+		AnimationNodeStateMachineTransition::SwitchMode mode;
+		bool disabled;
+		bool auto_advance;
+		float width;
+	};
+
+	Vector<TransitionLine> transition_lines;
+
+	StringName selected_transition_from;
+	StringName selected_transition_to;
+
+	bool over_text;
+	StringName over_node;
+	int over_node_what;
+
+	String prev_name;
+	void _name_edited(const String &p_text);
+	void _open_editor(const String &p_name);
+	void _scroll_changed(double);
+
+	void _clip_src_line_to_rect(Vector2 &r_from, Vector2 &r_to, const Rect2 &p_rect);
+	void _clip_dst_line_to_rect(Vector2 &r_from, Vector2 &r_to, const Rect2 &p_rect);
+
+	void _erase_selected();
+	void _update_mode();
+	void _autoplay_selected();
+	void _end_selected();
+
+	bool last_active;
+	StringName last_blend_from_node;
+	StringName last_current_node;
+	Vector<StringName> last_travel_path;
+	float last_play_pos;
+
+	float error_time;
+	String error_text;
+
+protected:
+	void _notification(int p_what);
+	static void _bind_methods();
+
+public:
+	static AnimationNodeStateMachineEditor *get_singleton() { return singleton; }
+	void edit(AnimationNodeStateMachine *p_state_machine);
+	AnimationNodeStateMachineEditor();
+};
+
+class AnimationNodeStateMachineEditorPlugin : public EditorPlugin {
+
+	GDCLASS(AnimationNodeStateMachineEditorPlugin, EditorPlugin);
+
+	AnimationNodeStateMachineEditor *anim_tree_editor;
+	EditorNode *editor;
+	Button *button;
+
+public:
+	virtual String get_name() const { return "StateMachine"; }
+	bool has_main_screen() const { return false; }
+	virtual void edit(Object *p_object);
+	virtual bool handles(Object *p_object) const;
+	virtual void make_visible(bool p_visible);
+
+	AnimationNodeStateMachineEditorPlugin(EditorNode *p_node);
+	~AnimationNodeStateMachineEditorPlugin();
+};
+
+#endif // ANIMATION_STATE_MACHINE_EDITOR_H

+ 5 - 5
scene/animation/animation_blend_space.cpp

@@ -209,10 +209,10 @@ Vector2 AnimationNodeBlendSpace::get_snap() const {
 	return snap;
 }
 
-void AnimationNodeBlendSpace::set_blend_pos(const Vector2 &p_pos) {
+void AnimationNodeBlendSpace::set_blend_position(const Vector2 &p_pos) {
 	blend_pos = p_pos;
 }
-Vector2 AnimationNodeBlendSpace::get_blend_pos() const {
+Vector2 AnimationNodeBlendSpace::get_blend_position() const {
 	return blend_pos;
 }
 
@@ -503,8 +503,8 @@ void AnimationNodeBlendSpace::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_snap", "snap"), &AnimationNodeBlendSpace::set_snap);
 	ClassDB::bind_method(D_METHOD("get_snap"), &AnimationNodeBlendSpace::get_snap);
 
-	ClassDB::bind_method(D_METHOD("set_blend_pos", "pos"), &AnimationNodeBlendSpace::set_blend_pos);
-	ClassDB::bind_method(D_METHOD("get_blend_pos"), &AnimationNodeBlendSpace::get_blend_pos);
+	ClassDB::bind_method(D_METHOD("set_blend_position", "pos"), &AnimationNodeBlendSpace::set_blend_position);
+	ClassDB::bind_method(D_METHOD("get_blend_position"), &AnimationNodeBlendSpace::get_blend_position);
 
 	ClassDB::bind_method(D_METHOD("set_x_label", "text"), &AnimationNodeBlendSpace::set_x_label);
 	ClassDB::bind_method(D_METHOD("get_x_label"), &AnimationNodeBlendSpace::get_x_label);
@@ -532,7 +532,7 @@ void AnimationNodeBlendSpace::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "min_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_min_space", "get_min_space");
 	ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "max_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_max_space", "get_max_space");
 	ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "snap", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_snap", "get_snap");
-	ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "blend_pos", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_blend_pos", "get_blend_pos");
+	ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "blend_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_blend_position", "get_blend_position");
 	ADD_PROPERTY(PropertyInfo(Variant::STRING, "x_label", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_x_label", "get_x_label");
 	ADD_PROPERTY(PropertyInfo(Variant::STRING, "y_label", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_y_label", "get_y_label");
 }

+ 4 - 4
scene/animation/animation_blend_space.h

@@ -70,8 +70,8 @@ public:
 	void set_snap(const Vector2 &p_snap);
 	Vector2 get_snap() const;
 
-	void set_blend_pos(const Vector2 &p_pos);
-	Vector2 get_blend_pos() const;
+	void set_blend_position(const Vector2 &p_pos);
+	Vector2 get_blend_position() const;
 
 	void set_x_label(const String &p_label);
 	String get_x_label() const;
@@ -79,8 +79,8 @@ public:
 	void set_y_label(const String &p_label);
 	String get_y_label() const;
 
-	float process(float p_time, bool p_seek);
-	String get_caption() const;
+	virtual float process(float p_time, bool p_seek);
+	virtual String get_caption() const;
 
 	Vector2 get_closest_point(const Vector2 &p_point);
 

+ 794 - 0
scene/animation/animation_node_state_machine.cpp

@@ -0,0 +1,794 @@
+#include "animation_node_state_machine.h"
+
+/////////////////////////////////////////////////
+
+void AnimationNodeStateMachineTransition::set_switch_mode(SwitchMode p_mode) {
+
+	switch_mode = p_mode;
+}
+
+AnimationNodeStateMachineTransition::SwitchMode AnimationNodeStateMachineTransition::get_switch_mode() const {
+
+	return switch_mode;
+}
+
+void AnimationNodeStateMachineTransition::set_auto_advance(bool p_enable) {
+	auto_advance = p_enable;
+}
+
+bool AnimationNodeStateMachineTransition::has_auto_advance() const {
+	return auto_advance;
+}
+
+void AnimationNodeStateMachineTransition::set_xfade_time(float p_xfade) {
+
+	ERR_FAIL_COND(p_xfade < 0);
+	xfade = p_xfade;
+	emit_changed();
+}
+
+float AnimationNodeStateMachineTransition::get_xfade_time() const {
+	return xfade;
+}
+
+void AnimationNodeStateMachineTransition::set_disabled(bool p_disabled) {
+	disabled = p_disabled;
+	emit_changed();
+}
+
+bool AnimationNodeStateMachineTransition::is_disabled() const {
+	return disabled;
+}
+
+void AnimationNodeStateMachineTransition::set_priority(int p_priority) {
+	priority = p_priority;
+	emit_changed();
+}
+
+int AnimationNodeStateMachineTransition::get_priority() const {
+	return priority;
+}
+
+void AnimationNodeStateMachineTransition::_bind_methods() {
+	ClassDB::bind_method(D_METHOD("set_switch_mode", "mode"), &AnimationNodeStateMachineTransition::set_switch_mode);
+	ClassDB::bind_method(D_METHOD("get_switch_mode"), &AnimationNodeStateMachineTransition::get_switch_mode);
+
+	ClassDB::bind_method(D_METHOD("set_auto_advance", "auto_advance"), &AnimationNodeStateMachineTransition::set_auto_advance);
+	ClassDB::bind_method(D_METHOD("has_auto_advance"), &AnimationNodeStateMachineTransition::has_auto_advance);
+
+	ClassDB::bind_method(D_METHOD("set_xfade_time", "secs"), &AnimationNodeStateMachineTransition::set_xfade_time);
+	ClassDB::bind_method(D_METHOD("get_xfade_time"), &AnimationNodeStateMachineTransition::get_xfade_time);
+
+	ClassDB::bind_method(D_METHOD("set_disabled", "disabled"), &AnimationNodeStateMachineTransition::set_disabled);
+	ClassDB::bind_method(D_METHOD("is_disabled"), &AnimationNodeStateMachineTransition::is_disabled);
+
+	ClassDB::bind_method(D_METHOD("set_priority", "priority"), &AnimationNodeStateMachineTransition::set_priority);
+	ClassDB::bind_method(D_METHOD("get_priority"), &AnimationNodeStateMachineTransition::get_priority);
+
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "switch_mode", PROPERTY_HINT_ENUM, "Immediate,Sync,AtEnd"), "set_switch_mode", "get_switch_mode");
+	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_advance"), "set_auto_advance", "has_auto_advance");
+	ADD_PROPERTY(PropertyInfo(Variant::REAL, "xfade_time", PROPERTY_HINT_RANGE, "0,240,0.01"), "set_xfade_time", "get_xfade_time");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "priority", PROPERTY_HINT_RANGE, "0,32,1"), "set_priority", "get_priority");
+	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disabled"), "set_disabled", "is_disabled");
+
+	BIND_CONSTANT(SWITCH_MODE_IMMEDIATE);
+	BIND_CONSTANT(SWITCH_MODE_SYNC);
+	BIND_CONSTANT(SWITCH_MODE_AT_END);
+}
+
+AnimationNodeStateMachineTransition::AnimationNodeStateMachineTransition() {
+
+	switch_mode = SWITCH_MODE_IMMEDIATE;
+	auto_advance = false;
+	xfade = 0;
+	disabled = false;
+	priority = 1;
+}
+
+///////////////////////////////////////////////////////
+void AnimationNodeStateMachine::add_node(const StringName &p_name, Ref<AnimationNode> p_node) {
+
+	ERR_FAIL_COND(states.has(p_name));
+	ERR_FAIL_COND(p_node.is_null());
+	ERR_FAIL_COND(p_node->get_parent().is_valid());
+	ERR_FAIL_COND(p_node->get_graph_player() != NULL);
+	ERR_FAIL_COND(String(p_name).find("/") != -1);
+	states[p_name] = p_node;
+
+	p_node->set_parent(this);
+	p_node->set_graph_player(get_graph_player());
+
+	emit_changed();
+}
+
+Ref<AnimationNode> AnimationNodeStateMachine::get_node(const StringName &p_name) const {
+
+	ERR_FAIL_COND_V(!states.has(p_name), Ref<AnimationNode>());
+
+	return states[p_name];
+}
+
+StringName AnimationNodeStateMachine::get_node_name(const Ref<AnimationNode> &p_node) const {
+	for (Map<StringName, Ref<AnimationRootNode> >::Element *E = states.front(); E; E = E->next()) {
+		if (E->get() == p_node) {
+			return E->key();
+		}
+	}
+
+	ERR_FAIL_V(StringName());
+}
+
+bool AnimationNodeStateMachine::has_node(const StringName &p_name) const {
+	return states.has(p_name);
+}
+void AnimationNodeStateMachine::remove_node(const StringName &p_name) {
+
+	ERR_FAIL_COND(!states.has(p_name));
+
+	{
+		//erase node connections
+		Ref<AnimationNode> node = states[p_name];
+		for (int i = 0; i < node->get_input_count(); i++) {
+			node->set_input_connection(i, StringName());
+		}
+		node->set_parent(NULL);
+		node->set_graph_player(NULL);
+	}
+
+	states.erase(p_name);
+	path.erase(p_name);
+
+	for (int i = 0; i < transitions.size(); i++) {
+		if (transitions[i].from == p_name || transitions[i].to == p_name) {
+			transitions.remove(i);
+			i--;
+		}
+	}
+
+	if (start_node == p_name) {
+		start_node = StringName();
+	}
+
+	if (end_node == p_name) {
+		end_node = StringName();
+	}
+
+	if (playing && current == p_name) {
+		stop();
+	}
+	emit_changed();
+}
+
+void AnimationNodeStateMachine::rename_node(const StringName &p_name, const StringName &p_new_name) {
+
+	ERR_FAIL_COND(!states.has(p_name));
+	ERR_FAIL_COND(states.has(p_new_name));
+
+	states[p_new_name] = states[p_name];
+	states.erase(p_name);
+
+	for (int i = 0; i < transitions.size(); i++) {
+		if (transitions[i].from == p_name) {
+			transitions[i].from = p_new_name;
+		}
+
+		if (transitions[i].to == p_name) {
+			transitions[i].to = p_new_name;
+		}
+	}
+
+	if (start_node == p_name) {
+		start_node = p_new_name;
+	}
+
+	if (end_node == p_name) {
+		end_node = p_new_name;
+	}
+
+	if (playing && current == p_name) {
+		current = p_new_name;
+	}
+
+	path.clear(); //clear path
+}
+
+void AnimationNodeStateMachine::get_node_list(List<StringName> *r_nodes) const {
+
+	List<StringName> nodes;
+	for (Map<StringName, Ref<AnimationRootNode> >::Element *E = states.front(); E; E = E->next()) {
+		nodes.push_back(E->key());
+	}
+	nodes.sort_custom<StringName::AlphCompare>();
+
+	for (List<StringName>::Element *E = nodes.front(); E; E = E->next()) {
+		r_nodes->push_back(E->get());
+	}
+}
+
+bool AnimationNodeStateMachine::has_transition(const StringName &p_from, const StringName &p_to) const {
+
+	for (int i = 0; i < transitions.size(); i++) {
+		if (transitions[i].from == p_from && transitions[i].to == p_to)
+			return true;
+	}
+	return false;
+}
+
+int AnimationNodeStateMachine::find_transition(const StringName &p_from, const StringName &p_to) const {
+
+	for (int i = 0; i < transitions.size(); i++) {
+		if (transitions[i].from == p_from && transitions[i].to == p_to)
+			return i;
+	}
+	return -1;
+}
+
+void AnimationNodeStateMachine::add_transition(const StringName &p_from, const StringName &p_to, const Ref<AnimationNodeStateMachineTransition> &p_transition) {
+
+	ERR_FAIL_COND(p_from == p_to);
+	ERR_FAIL_COND(!states.has(p_from));
+	ERR_FAIL_COND(!states.has(p_to));
+	ERR_FAIL_COND(p_transition.is_null());
+
+	for (int i = 0; i < transitions.size(); i++) {
+		ERR_FAIL_COND(transitions[i].from == p_from && transitions[i].to == p_to);
+	}
+
+	Transition tr;
+	tr.from = p_from;
+	tr.to = p_to;
+	tr.transition = p_transition;
+
+	transitions.push_back(tr);
+}
+
+Ref<AnimationNodeStateMachineTransition> AnimationNodeStateMachine::get_transition(int p_transition) const {
+	ERR_FAIL_INDEX_V(p_transition, transitions.size(), Ref<AnimationNodeStateMachineTransition>());
+	return transitions[p_transition].transition;
+}
+StringName AnimationNodeStateMachine::get_transition_from(int p_transition) const {
+
+	ERR_FAIL_INDEX_V(p_transition, transitions.size(), StringName());
+	return transitions[p_transition].from;
+}
+StringName AnimationNodeStateMachine::get_transition_to(int p_transition) const {
+
+	ERR_FAIL_INDEX_V(p_transition, transitions.size(), StringName());
+	return transitions[p_transition].to;
+}
+
+int AnimationNodeStateMachine::get_transition_count() const {
+
+	return transitions.size();
+}
+void AnimationNodeStateMachine::remove_transition(const StringName &p_from, const StringName &p_to) {
+
+	for (int i = 0; i < transitions.size(); i++) {
+		if (transitions[i].from == p_from && transitions[i].to == p_to) {
+			transitions.remove(i);
+			return;
+		}
+	}
+
+	if (playing) {
+		path.clear();
+	}
+}
+
+void AnimationNodeStateMachine::remove_transition_by_index(int p_transition) {
+
+	transitions.remove(p_transition);
+	if (playing) {
+		path.clear();
+	}
+}
+
+void AnimationNodeStateMachine::set_start_node(const StringName &p_node) {
+
+	ERR_FAIL_COND(p_node != StringName() && !states.has(p_node));
+	start_node = p_node;
+}
+
+String AnimationNodeStateMachine::get_start_node() const {
+
+	return start_node;
+}
+
+void AnimationNodeStateMachine::set_end_node(const StringName &p_node) {
+
+	ERR_FAIL_COND(p_node != StringName() && !states.has(p_node));
+	end_node = p_node;
+}
+
+String AnimationNodeStateMachine::get_end_node() const {
+
+	return end_node;
+}
+
+void AnimationNodeStateMachine::set_graph_offset(const Vector2 &p_offset) {
+	graph_offset = p_offset;
+}
+
+Vector2 AnimationNodeStateMachine::get_graph_offset() const {
+	return graph_offset;
+}
+
+float AnimationNodeStateMachine::process(float p_time, bool p_seek) {
+
+	//if not playing and it can restart, then restart
+	if (!playing) {
+		if (start_node) {
+			start(start_node);
+		} else {
+			return 0;
+		}
+	}
+
+	bool do_start = (p_seek && p_time == 0) || play_start || current == StringName();
+
+	if (do_start) {
+
+		if (start_node != StringName() && p_seek && p_time == 0) {
+			current = start_node;
+		}
+
+		len_current = blend_node(states[current], 0, true, 1.0, FILTER_IGNORE, false);
+		pos_current = 0;
+		loops_current = 0;
+		play_start = false;
+	}
+
+	float fade_blend = 1.0;
+
+	if (fading_from != StringName()) {
+
+		if (!p_seek) {
+			fading_pos += p_time;
+		}
+		fade_blend = MIN(1.0, fading_pos / fading_time);
+		if (fade_blend >= 1.0) {
+			fading_from = StringName();
+		}
+	}
+
+	float rem = blend_node(states[current], p_time, p_seek, fade_blend, FILTER_IGNORE, false);
+
+	if (fading_from != StringName()) {
+
+		blend_node(states[fading_from], p_time, p_seek, 1.0 - fade_blend, FILTER_IGNORE, false);
+	}
+
+	//guess playback position
+	if (rem > len_current) { // weird but ok
+		len_current = rem;
+	}
+
+	{ //advance and loop check
+
+		float next_pos = len_current - rem;
+
+		if (next_pos < pos_current) {
+			loops_current++;
+		}
+		pos_current = next_pos; //looped
+	}
+
+	//find next
+	StringName next;
+	float next_xfade = 0;
+	AnimationNodeStateMachineTransition::SwitchMode switch_mode = AnimationNodeStateMachineTransition::SWITCH_MODE_IMMEDIATE;
+
+	if (path.size()) {
+
+		for (int i = 0; i < transitions.size(); i++) {
+			if (transitions[i].from == current && transitions[i].to == path[0]) {
+				next_xfade = transitions[i].transition->get_xfade_time();
+				switch_mode = transitions[i].transition->get_switch_mode();
+				next = path[0];
+			}
+		}
+	} else {
+		float priority_best = 1e20;
+		int auto_advance_to = -1;
+		for (int i = 0; i < transitions.size(); i++) {
+			if (transitions[i].from == current && transitions[i].transition->has_auto_advance()) {
+
+				if (transitions[i].transition->get_priority() < priority_best) {
+					auto_advance_to = i;
+				}
+			}
+		}
+
+		if (auto_advance_to != -1) {
+			next = transitions[auto_advance_to].to;
+			next_xfade = transitions[auto_advance_to].transition->get_xfade_time();
+			switch_mode = transitions[auto_advance_to].transition->get_switch_mode();
+		}
+	}
+
+	//if next, see when to transition
+	if (next != StringName()) {
+
+		bool goto_next = false;
+
+		if (switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_IMMEDIATE) {
+			goto_next = fading_from == StringName();
+		} else {
+			goto_next = next_xfade >= (len_current - pos_current) || loops_current > 0;
+			if (loops_current > 0) {
+				next_xfade = 0;
+			}
+		}
+
+		if (goto_next) { //loops should be used because fade time may be too small or zero and animation may have looped
+
+			if (next_xfade) {
+				//time to fade, baby
+				fading_from = current;
+				fading_time = next_xfade;
+				fading_pos = 0;
+			} else {
+				fading_from = StringName();
+				fading_pos = 0;
+			}
+
+			if (path.size()) { //if it came from path, remove path
+				path.remove(0);
+			}
+			current = next;
+			if (switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_SYNC) {
+				len_current = blend_node(states[current], 0, true, 0, FILTER_IGNORE, false);
+				pos_current = MIN(pos_current, len_current);
+				blend_node(states[current], pos_current, true, 0, FILTER_IGNORE, false);
+
+			} else {
+				len_current = blend_node(states[current], 0, true, 0, FILTER_IGNORE, false);
+				pos_current = 0;
+			}
+
+			rem = len_current; //so it does not show 0 on transition
+			loops_current = 0;
+		}
+	}
+
+	//compute time left for transitions by using the end node
+
+	if (end_node != StringName() && end_node != current) {
+
+		rem = blend_node(states[end_node], 0, true, 0, FILTER_IGNORE, false);
+	}
+
+	if (get_parent().is_valid() && get_parent()->is_class("AnimationNodeStateMachine")) {
+		print_line("rem: " + rtos(rem));
+	}
+
+	return rem;
+}
+
+bool AnimationNodeStateMachine::travel(const StringName &p_state) {
+	ERR_FAIL_COND_V(!playing, false);
+	ERR_FAIL_COND_V(!states.has(p_state), false);
+	ERR_FAIL_COND_V(!states.has(current), false);
+
+	path.clear(); //a new one will be needed
+
+	if (current == p_state)
+		return true; //nothing to do
+
+	loops_current = 0; // reset loops, so fade does not happen immediately
+
+	Vector2 current_pos = states[current]->get_position();
+	Vector2 target_pos = states[p_state]->get_position();
+
+	Map<StringName, AStarCost> cost_map;
+
+	List<int> open_list;
+
+	//build open list
+	for (int i = 0; i < transitions.size(); i++) {
+		if (transitions[i].from == current) {
+			open_list.push_back(i);
+			float cost = states[transitions[i].to]->get_position().distance_to(current_pos);
+			cost *= transitions[i].transition->get_priority();
+			AStarCost ap;
+			ap.prev = current;
+			ap.distance = cost;
+			cost_map[transitions[i].to] = ap;
+
+			if (transitions[i].to == p_state) { //prematurely found it! :D
+				path.push_back(p_state);
+				return true;
+			}
+		}
+	}
+
+	//begin astar
+	bool found_route = false;
+	while (!found_route) {
+
+		if (open_list.size() == 0) {
+			return false; //no path found
+		}
+
+		//find the last cost transition
+		List<int>::Element *least_cost_transition = NULL;
+		float least_cost = 1e20;
+
+		for (List<int>::Element *E = open_list.front(); E; E = E->next()) {
+
+			float cost = cost_map[transitions[E->get()].to].distance;
+			cost += states[transitions[E->get()].to]->get_position().distance_to(target_pos);
+
+			if (cost < least_cost) {
+				least_cost_transition = E;
+			}
+		}
+
+		StringName transition_prev = transitions[least_cost_transition->get()].from;
+		StringName transition = transitions[least_cost_transition->get()].to;
+
+		for (int i = 0; i < transitions.size(); i++) {
+			if (transitions[i].from != transition || transitions[i].to == transition_prev) {
+				continue; //not interested on those
+			}
+
+			float distance = states[transitions[i].from]->get_position().distance_to(states[transitions[i].to]->get_position());
+			distance *= transitions[i].transition->get_priority();
+			distance += cost_map[transitions[i].from].distance;
+
+			if (cost_map.has(transitions[i].to)) {
+				//oh this was visited already, can we win the cost?
+				if (distance < cost_map[transitions[i].to].distance) {
+					cost_map[transitions[i].to].distance = distance;
+					cost_map[transitions[i].to].prev = transitions[i].from;
+				}
+			} else {
+				//add to open list
+				AStarCost ac;
+				ac.prev = transitions[i].from;
+				ac.distance = distance;
+				cost_map[transitions[i].to] = ac;
+
+				open_list.push_back(i);
+
+				if (transitions[i].to == p_state) {
+					found_route = true;
+					break;
+				}
+			}
+		}
+
+		if (found_route) {
+			break;
+		}
+
+		open_list.erase(least_cost_transition);
+	}
+
+	//make path
+	StringName at = p_state;
+	while (at != current) {
+		path.push_back(at);
+		at = cost_map[at].prev;
+	}
+
+	path.invert();
+
+	return true;
+}
+
+void AnimationNodeStateMachine::start(const StringName &p_state) {
+
+	ERR_FAIL_COND(!states.has(p_state));
+	path.clear();
+	current = p_state;
+	playing = true;
+	play_start = true;
+}
+void AnimationNodeStateMachine::stop() {
+	playing = false;
+	play_start = false;
+	current = StringName();
+}
+bool AnimationNodeStateMachine::is_playing() const {
+
+	return playing;
+}
+StringName AnimationNodeStateMachine::get_current_node() const {
+	if (!playing) {
+		return StringName();
+	}
+
+	return current;
+}
+
+StringName AnimationNodeStateMachine::get_blend_from_node() const {
+	if (!playing) {
+		return StringName();
+	}
+
+	return fading_from;
+}
+
+float AnimationNodeStateMachine::get_current_play_pos() const {
+	return pos_current;
+}
+float AnimationNodeStateMachine::get_current_length() const {
+	return len_current;
+}
+
+Vector<StringName> AnimationNodeStateMachine::get_travel_path() const {
+	return path;
+}
+String AnimationNodeStateMachine::get_caption() const {
+	return "StateMachine";
+}
+
+void AnimationNodeStateMachine::_notification(int p_what) {
+}
+
+void AnimationNodeStateMachine::set_graph_player(AnimationGraphPlayer *p_player) {
+
+	AnimationNode::set_graph_player(p_player);
+
+	for (Map<StringName, Ref<AnimationRootNode> >::Element *E = states.front(); E; E = E->next()) {
+		Ref<AnimationRootNode> node = E->get();
+		node->set_graph_player(p_player);
+	}
+}
+
+bool AnimationNodeStateMachine::_set(const StringName &p_name, const Variant &p_value) {
+
+	String name = p_name;
+	if (name.begins_with("states/")) {
+		String node_name = name.get_slicec('/', 1);
+		String what = name.get_slicec('/', 2);
+
+		if (what == "node") {
+			Ref<AnimationNode> anode = p_value;
+			if (anode.is_valid()) {
+				add_node(node_name, p_value);
+			}
+			return true;
+		}
+
+		if (what == "position") {
+
+			if (states.has(node_name)) {
+				states[node_name]->set_position(p_value);
+			}
+			return true;
+		}
+	} else if (name == "transitions") {
+
+		Array trans = p_value;
+		ERR_FAIL_COND_V(trans.size() % 3 != 0, false);
+
+		for (int i = 0; i < trans.size(); i += 3) {
+			add_transition(trans[i], trans[i + 1], trans[i + 2]);
+		}
+		return true;
+	} else if (name == "start_node") {
+		set_start_node(p_value);
+		return true;
+	} else if (name == "end_node") {
+		set_end_node(p_value);
+		return true;
+	} else if (name == "graph_offset") {
+		set_graph_offset(p_value);
+		return true;
+	}
+
+	return false;
+}
+
+bool AnimationNodeStateMachine::_get(const StringName &p_name, Variant &r_ret) const {
+
+	String name = p_name;
+	if (name.begins_with("states/")) {
+		String node_name = name.get_slicec('/', 1);
+		String what = name.get_slicec('/', 2);
+
+		if (what == "node") {
+			if (states.has(node_name)) {
+				r_ret = states[node_name];
+				return true;
+			}
+		}
+
+		if (what == "position") {
+
+			if (states.has(node_name)) {
+				r_ret = states[node_name]->get_position();
+				return true;
+			}
+		}
+	} else if (name == "transitions") {
+		Array trans;
+		trans.resize(transitions.size() * 3);
+
+		for (int i = 0; i < transitions.size(); i++) {
+			trans[i * 3 + 0] = transitions[i].from;
+			trans[i * 3 + 1] = transitions[i].to;
+			trans[i * 3 + 2] = transitions[i].transition;
+		}
+
+		r_ret = trans;
+		return true;
+	} else if (name == "start_node") {
+		r_ret = get_start_node();
+		return true;
+	} else if (name == "end_node") {
+		r_ret = get_end_node();
+		return true;
+	} else if (name == "graph_offset") {
+		r_ret = get_graph_offset();
+		return true;
+	}
+
+	return false;
+}
+void AnimationNodeStateMachine::_get_property_list(List<PropertyInfo> *p_list) const {
+
+	List<StringName> names;
+	for (Map<StringName, Ref<AnimationRootNode> >::Element *E = states.front(); E; E = E->next()) {
+		names.push_back(E->key());
+	}
+	names.sort_custom<StringName::AlphCompare>();
+
+	for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
+		String name = E->get();
+		p_list->push_back(PropertyInfo(Variant::OBJECT, "states/" + name + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationNode", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE));
+		p_list->push_back(PropertyInfo(Variant::VECTOR2, "states/" + name + "/position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+	}
+
+	p_list->push_back(PropertyInfo(Variant::ARRAY, "transitions", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+	p_list->push_back(PropertyInfo(Variant::STRING, "start_node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+	p_list->push_back(PropertyInfo(Variant::STRING, "end_node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+	p_list->push_back(PropertyInfo(Variant::VECTOR2, "graph_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+}
+
+void AnimationNodeStateMachine::_bind_methods() {
+
+	ClassDB::bind_method(D_METHOD("add_node", "name", "node"), &AnimationNodeStateMachine::add_node);
+	ClassDB::bind_method(D_METHOD("get_node", "name"), &AnimationNodeStateMachine::get_node);
+	ClassDB::bind_method(D_METHOD("remove_node", "name"), &AnimationNodeStateMachine::remove_node);
+	ClassDB::bind_method(D_METHOD("rename_node", "name", "new_name"), &AnimationNodeStateMachine::rename_node);
+	ClassDB::bind_method(D_METHOD("has_node", "name"), &AnimationNodeStateMachine::has_node);
+	ClassDB::bind_method(D_METHOD("get_node_name", "node"), &AnimationNodeStateMachine::get_node_name);
+
+	ClassDB::bind_method(D_METHOD("has_transition", "from", "to"), &AnimationNodeStateMachine::add_transition);
+	ClassDB::bind_method(D_METHOD("add_transition", "from", "to", "transition"), &AnimationNodeStateMachine::add_transition);
+	ClassDB::bind_method(D_METHOD("get_transition", "idx"), &AnimationNodeStateMachine::get_transition);
+	ClassDB::bind_method(D_METHOD("get_transition_from", "idx"), &AnimationNodeStateMachine::get_transition_from);
+	ClassDB::bind_method(D_METHOD("get_transition_to", "idx"), &AnimationNodeStateMachine::get_transition_to);
+	ClassDB::bind_method(D_METHOD("get_transition_count"), &AnimationNodeStateMachine::get_transition_count);
+	ClassDB::bind_method(D_METHOD("remove_transition_by_index", "idx"), &AnimationNodeStateMachine::remove_transition_by_index);
+	ClassDB::bind_method(D_METHOD("remove_transition", "from", "to"), &AnimationNodeStateMachine::remove_transition);
+
+	ClassDB::bind_method(D_METHOD("set_start_node", "name"), &AnimationNodeStateMachine::set_start_node);
+	ClassDB::bind_method(D_METHOD("get_start_node"), &AnimationNodeStateMachine::get_start_node);
+
+	ClassDB::bind_method(D_METHOD("set_end_node", "name"), &AnimationNodeStateMachine::set_end_node);
+	ClassDB::bind_method(D_METHOD("get_end_node"), &AnimationNodeStateMachine::get_end_node);
+
+	ClassDB::bind_method(D_METHOD("set_graph_offset", "name"), &AnimationNodeStateMachine::set_graph_offset);
+	ClassDB::bind_method(D_METHOD("get_graph_offset"), &AnimationNodeStateMachine::get_graph_offset);
+
+	ClassDB::bind_method(D_METHOD("travel", "to_node"), &AnimationNodeStateMachine::travel);
+	ClassDB::bind_method(D_METHOD("start", "node"), &AnimationNodeStateMachine::start);
+	ClassDB::bind_method(D_METHOD("stop"), &AnimationNodeStateMachine::stop);
+	ClassDB::bind_method(D_METHOD("is_playing"), &AnimationNodeStateMachine::is_playing);
+	ClassDB::bind_method(D_METHOD("get_current_node"), &AnimationNodeStateMachine::get_current_node);
+	ClassDB::bind_method(D_METHOD("get_travel_path"), &AnimationNodeStateMachine::get_travel_path);
+}
+
+AnimationNodeStateMachine::AnimationNodeStateMachine() {
+
+	play_start = false;
+
+	playing = false;
+	len_current = 0;
+
+	fading_time = 0;
+}

+ 142 - 0
scene/animation/animation_node_state_machine.h

@@ -0,0 +1,142 @@
+#ifndef ANIMATION_NODE_STATE_MACHINE_H
+#define ANIMATION_NODE_STATE_MACHINE_H
+
+#include "scene/animation/animation_graph_player.h"
+
+class AnimationNodeStateMachineTransition : public Resource {
+	GDCLASS(AnimationNodeStateMachineTransition, Resource)
+public:
+	enum SwitchMode {
+		SWITCH_MODE_IMMEDIATE,
+		SWITCH_MODE_SYNC,
+		SWITCH_MODE_AT_END,
+	};
+
+private:
+	SwitchMode switch_mode;
+	bool auto_advance;
+	float xfade;
+	bool disabled;
+	int priority;
+
+protected:
+	static void _bind_methods();
+
+public:
+	void set_switch_mode(SwitchMode p_mode);
+	SwitchMode get_switch_mode() const;
+
+	void set_auto_advance(bool p_enable);
+	bool has_auto_advance() const;
+
+	void set_xfade_time(float p_xfade);
+	float get_xfade_time() const;
+
+	void set_disabled(bool p_disabled);
+	bool is_disabled() const;
+
+	void set_priority(int p_priority);
+	int get_priority() const;
+
+	AnimationNodeStateMachineTransition();
+};
+
+VARIANT_ENUM_CAST(AnimationNodeStateMachineTransition::SwitchMode)
+
+class AnimationNodeStateMachine : public AnimationRootNode {
+
+	GDCLASS(AnimationNodeStateMachine, AnimationRootNode);
+
+private:
+	Map<StringName, Ref<AnimationRootNode> > states;
+
+	struct Transition {
+
+		StringName from;
+		StringName to;
+		Ref<AnimationNodeStateMachineTransition> transition;
+	};
+
+	struct AStarCost {
+		float distance;
+		StringName prev;
+	};
+
+	Vector<Transition> transitions;
+
+	float len_total;
+
+	float len_current;
+	float pos_current;
+	int loops_current;
+
+	bool play_start;
+	StringName start_node;
+	StringName end_node;
+
+	Vector2 graph_offset;
+
+	StringName current;
+
+	StringName fading_from;
+	float fading_time;
+	float fading_pos;
+
+	Vector<StringName> path;
+	bool playing;
+
+protected:
+	void _notification(int p_what);
+	static void _bind_methods();
+
+	bool _set(const StringName &p_name, const Variant &p_value);
+	bool _get(const StringName &p_name, Variant &r_ret) const;
+	void _get_property_list(List<PropertyInfo> *p_list) const;
+
+public:
+	void add_node(const StringName &p_name, Ref<AnimationNode> p_node);
+	Ref<AnimationNode> get_node(const StringName &p_name) const;
+	void remove_node(const StringName &p_name);
+	void rename_node(const StringName &p_name, const StringName &p_new_name);
+	bool has_node(const StringName &p_name) const;
+	StringName get_node_name(const Ref<AnimationNode> &p_node) const;
+	void get_node_list(List<StringName> *r_nodes) const;
+
+	bool has_transition(const StringName &p_from, const StringName &p_to) const;
+	int find_transition(const StringName &p_from, const StringName &p_to) const;
+	void add_transition(const StringName &p_from, const StringName &p_to, const Ref<AnimationNodeStateMachineTransition> &p_transition);
+	Ref<AnimationNodeStateMachineTransition> get_transition(int p_transition) const;
+	StringName get_transition_from(int p_transition) const;
+	StringName get_transition_to(int p_transition) const;
+	int get_transition_count() const;
+	void remove_transition_by_index(int p_transition);
+	void remove_transition(const StringName &p_from, const StringName &p_to);
+
+	void set_start_node(const StringName &p_node);
+	String get_start_node() const;
+
+	void set_end_node(const StringName &p_node);
+	String get_end_node() const;
+
+	void set_graph_offset(const Vector2 &p_offset);
+	Vector2 get_graph_offset() const;
+
+	virtual float process(float p_time, bool p_seek);
+	virtual String get_caption() const;
+
+	bool travel(const StringName &p_state);
+	void start(const StringName &p_state);
+	void stop();
+	bool is_playing() const;
+	StringName get_current_node() const;
+	StringName get_blend_from_node() const;
+	Vector<StringName> get_travel_path() const;
+	float get_current_play_pos() const;
+	float get_current_length() const;
+
+	virtual void set_graph_player(AnimationGraphPlayer *p_player);
+
+	AnimationNodeStateMachine();
+};
+
+#endif // ANIMATION_NODE_STATE_MACHINE_H

+ 3 - 0
scene/register_scene_types.cpp

@@ -66,6 +66,7 @@
 #include "scene/animation/animation_blend_space.h"
 #include "scene/animation/animation_blend_tree.h"
 #include "scene/animation/animation_graph_player.h"
+#include "scene/animation/animation_node_state_machine.h"
 #include "scene/animation/animation_player.h"
 #include "scene/animation/animation_tree_player.h"
 #include "scene/animation/tween.h"
@@ -390,6 +391,8 @@ void register_scene_types() {
 	ClassDB::register_class<AnimationRootNode>();
 	ClassDB::register_class<AnimationNodeBlendTree>();
 	ClassDB::register_class<AnimationNodeBlendSpace>();
+	ClassDB::register_class<AnimationNodeStateMachine>();
+	ClassDB::register_class<AnimationNodeStateMachineTransition>();
 	ClassDB::register_class<AnimationNodeOutput>();
 	ClassDB::register_class<AnimationNodeOneShot>();
 	ClassDB::register_class<AnimationNodeAnimation>();