Browse Source

[animgraph] Refactored Param list into FancyArray

Clément Espeute 7 months ago
parent
commit
89dde14df4
4 changed files with 582 additions and 149 deletions
  1. 97 0
      bin/style.css
  2. 118 0
      bin/style.less
  3. 191 11
      hide/comp/FancyArray.hx
  4. 176 138
      hide/view/animgraph/AnimGraphEditor.hx

+ 97 - 0
bin/style.css

@@ -4441,6 +4441,103 @@ graph-editor-root properties-container .anim-list {
   flex-grow: 0;
   flex-shrink: 0;
 }
+fancy-array {
+  padding: var(--basic-padding);
+  display: flex;
+  flex-direction: column;
+  list-style: none;
+  align-items: stretch;
+  overflow-y: auto;
+  gap: 2px;
+}
+fancy-array fancy-item {
+  display: flex;
+  flex-direction: column;
+  justify-items: stretch;
+  border: var(--basic-border);
+  border-radius: var(--basic-border-radius);
+  position: relative;
+}
+fancy-array fancy-item header {
+  display: flex;
+  align-items: center;
+  background-color: #202020;
+  padding: var(--basic-padding);
+  gap: 3px;
+}
+fancy-array fancy-item header .fill {
+  flex-grow: 1;
+}
+fancy-array fancy-item header input {
+  border-radius: var(--basic-border-radius);
+  border: var(--basic-border);
+}
+fancy-array fancy-item header .reorder {
+  padding: var(--basic-padding);
+}
+fancy-array fancy-item content {
+  border-top: var(--basic-border);
+  padding: var(--basic-padding);
+}
+fancy-array fancy-item content > ul {
+  padding: var(--basic-padding);
+  display: flex;
+  flex-direction: column;
+  list-style: none;
+  display: grid;
+  column-gap: 10px;
+  row-gap: 1px;
+  grid-template-columns: 2fr 5fr;
+}
+fancy-array fancy-item content > ul li {
+  grid-column: 1 / -1;
+  display: grid;
+  grid-template-columns: subgrid;
+  align-items: center;
+}
+fancy-array fancy-item content > ul li dd {
+  margin: 0;
+  text-align: right;
+}
+fancy-array fancy-item content > ul li > .hide-range {
+  display: flex;
+}
+fancy-array fancy-item content > ul li > .hide-range input[type=range] {
+  flex-grow: 1;
+}
+fancy-array fancy-item content > ul li > .hide-range input[type=text] {
+  width: 0;
+  flex-basis: 44px;
+}
+fancy-array fancy-item.folded content {
+  display: none;
+}
+fancy-array fancy-item.folded .ico-chevron-down {
+  transform: rotate(-90deg);
+}
+fancy-array fancy-item .ico-chevron-down {
+  transition: transform 0.25s;
+  transform: rotate(0deg);
+}
+fancy-array fancy-item.hovertop:before,
+fancy-array fancy-item.hoverbot:after {
+  display: block;
+  position: absolute;
+  z-index: 100;
+  margin: 0 auto;
+  width: 100%;
+  content: "";
+}
+fancy-array fancy-item:before {
+  border-top: 10px solid rgba(114, 180, 255, 0.75);
+  top: 0px;
+  pointer-events: none;
+}
+fancy-array fancy-item:after {
+  border-bottom: 10px solid rgba(114, 180, 255, 0.75);
+  bottom: 0px;
+  pointer-events: none;
+}
 center-content {
   width: 100%;
   height: 100%;

+ 118 - 0
bin/style.less

@@ -5271,6 +5271,124 @@ graph-editor-root {
 	}
 }
 
+fancy-array {
+	padding: var(--basic-padding);
+	display: flex;
+	flex-direction: column;
+	list-style: none;
+	align-items: stretch;
+
+	overflow-y: auto;
+	gap: 2px;
+	fancy-item {
+		display: flex;
+		flex-direction: column;
+		justify-items: stretch;
+
+		border: var(--basic-border);
+		border-radius: var(--basic-border-radius);
+		position: relative;
+
+		header {
+			display: flex;
+			align-items: center;
+			background-color: #202020;
+			padding: var(--basic-padding);
+
+			gap: 3px;
+
+			.fill {
+				flex-grow: 1;
+			}
+
+			input {
+				border-radius: var(--basic-border-radius);
+				border: var(--basic-border);
+			}
+
+			.reorder {
+				padding: var(--basic-padding);
+			}
+		}
+
+		content {
+			border-top: var(--basic-border);
+			padding: var(--basic-padding);
+
+			> ul {
+				padding: var(--basic-padding);
+				display: flex;
+				flex-direction: column;
+				list-style: none;
+
+				display: grid;
+				column-gap: 10px;
+				row-gap: 1px;
+				grid-template-columns: 2fr 5fr;
+
+				li {
+					grid-column: 1 / -1;
+					display: grid;
+					grid-template-columns: subgrid;
+					align-items: center;
+
+					dd {
+						margin: 0;
+						text-align: right;
+					}
+
+					>.hide-range {
+						display: flex;
+						input[type=range] {
+							flex-grow: 1;
+						}
+						input[type=text] {
+							width: 0;
+							flex-basis: 44px;
+						}
+					}
+				}
+			}
+		}
+
+		&.folded content {
+			display: none;
+		}
+
+		&.folded .ico-chevron-down {
+			transform: rotate(-90deg);
+		}
+
+		.ico-chevron-down {
+			transition: transform 0.25s;
+			transform: rotate(0deg);
+		}
+
+
+		&.hovertop:before, &.hoverbot:after {
+			display: block;
+			position: absolute;
+			z-index: 100;
+			margin: 0 auto;
+			width: 100%;
+			content: "";
+		}
+
+		&:before {
+			border-top: 10px solid rgba(114, 180, 255, 0.75);
+			top: 0px;
+			pointer-events: none;
+		}
+
+		&:after {
+			border-bottom: 10px solid rgba(114, 180, 255, 0.75);
+			bottom: 0px;
+			pointer-events: none;
+		}
+	}
+}
+
+
 center-content {
 	width: 100%;
 	height: 100%;

+ 191 - 11
hide/comp/FancyArray.hx

@@ -1,34 +1,214 @@
 package hide.comp;
 
+typedef FancyItemState = {
+	?open: Bool,
+}
 
 class FancyArray<T> extends hide.comp.Component {
-	public function new(parent: Element, e: Element, undo: hide.ui.UndoHistory) {
+	var itemState : Array<FancyItemState>;
+	var name : String;
+
+	public function new(parent: Element = null, e: Element = null, name: String, displayKey: String) {
 		if (e == null)
-			e = new Element("<ul></ul>");
+			e = new Element("<fancy-array></fancy-array>");
 		super(parent, e);
-		element.addClass("fancy-array");
+
+		this.name = name;
+		saveDisplayKey = displayKey + "/" + name;
+
+		try {
+			itemState = cast haxe.Json.parse(getDisplayState("state")) ?? [];
+		} catch(_) {
+			itemState = [];
+		}
+	}
+
+	var dragKeyName : String;
+	public function getDragKeyName() {
+		if (dragKeyName == null)
+			dragKeyName = '$name:index'.toLowerCase();
+		return dragKeyName;
+	}
+
+	function saveState() {
+		saveDisplayState("state", haxe.Json.stringify(itemState));
 	}
 
 	public function refresh() : Void {
 		element.empty();
 		var items = getItems();
 
+		for (i => item in items) {
+			var paramElement = new Element('<fancy-item>
+				<header>
+					<div class="reorder ico ico-reorder" draggable="true"></div>
+					<div class="ico ico-chevron-down toggle-open"></div>
+					<input type="text" value="${getItemName(item)}" class="fill"></input>
+					<button-2 class="menu"><div class="ico ico-ellipsis-v"/></button-2>
+				</header>
+			</fancy-item>').appendTo(element);
+
+			itemState[i] ??= {};
+			var state = itemState[i];
+			var open : Bool = state.open ?? false;
+			paramElement.toggleClass("folded", !open);
+
+			var name = paramElement.find("input");
+
+			if (setItemName != null) {
+				name.on("change", (e) -> {
+					setItemName(item, name.val());
+				});
+
+				name.on("keydown", (e) -> {
+					if (e.keyCode == 13) {
+						name.blur();
+						e.stopPropagation();
+					}
+				});
+			}
+
+			name.on("contextmenu", (e) -> {
+                e.stopPropagation();
+            });
+
+			var reorder = paramElement.find(".reorder");
+			if (reorderItem != null) {
+
+				inline function isAfter(e) {
+					return e.clientY > (paramElement.offset().top + paramElement.outerHeight() / 2.0);
+				}
+
+				reorder.get(0).ondragstart = (e: js.html.DragEvent) -> {
+					e.dataTransfer.setDragImage(paramElement.get(0), Std.int(paramElement.width()), 0);
+
+					e.dataTransfer.setData(getDragKeyName(), '${i}');
+					e.dataTransfer.dropEffect = "move";
+				}
 
+				paramElement.get(0).addEventListener("dragover", function(e : js.html.DragEvent) {
+					if (!e.dataTransfer.types.contains(getDragKeyName()))
+						return;
+					var after = isAfter(e);
+					paramElement.toggleClass("hovertop", !after);
+					paramElement.toggleClass("hoverbot", after);
+					e.preventDefault();
+				});
+
+				paramElement.get(0).addEventListener("dragleave", function(e : js.html.DragEvent) {
+					if (!e.dataTransfer.types.contains(getDragKeyName()))
+						return;
+					paramElement.toggleClass("hovertop", false);
+					paramElement.toggleClass("hoverbot", false);
+				});
+
+				paramElement.get(0).addEventListener("dragenter", function(e : js.html.DragEvent) {
+					if (!e.dataTransfer.types.contains(getDragKeyName()))
+						return;
+					e.preventDefault();
+				});
+
+				paramElement.get(0).addEventListener("drop", function(e : js.html.DragEvent) {
+					var toMoveIndex = Std.parseInt(e.dataTransfer.getData(getDragKeyName()));
+					paramElement.toggleClass("hovertop", false);
+					paramElement.toggleClass("hoverbot", false);
+					if (i == null)
+						return;
+					var after = isAfter(e);
+
+					var newIndex = i;
+
+					if (!after) newIndex -= 1;
+					if (toMoveIndex == newIndex)
+						return;
+					if (newIndex < i) {
+						newIndex += 1;
+					}
+					reorderItem(toMoveIndex, newIndex);
+				});
+			} else {
+				reorder.remove();
+			}
+
+			var toggleOpen = paramElement.find(".toggle-open");
+			if (getItemContent != null) {
+				var contentElement = getItemContent(item);
+				if (contentElement != null) {
+					var content = new Element("<content></content>").appendTo(paramElement);
+					contentElement.appendTo(content);
+
+					toggleOpen.on("click", (e) -> {
+						state.open = !state.open;
+						saveState();
+						paramElement.toggleClass("folded", !state.open);
+					});
+				} else {
+					toggleOpen.remove();
+				}
+			} else {
+				toggleOpen.remove();
+			}
+
+			if (removeItem != null) {
+				paramElement.find("header").get(0).addEventListener("contextmenu", function (e : js.html.MouseEvent) {
+					e.preventDefault();
+					hide.comp.ContextMenu.createFromEvent(e, [
+						{label: "Delete", click: () -> removeItem(i)}
+					]);
+				});
+
+				var menu = paramElement.find(".menu");
+				menu.on("click", (e) -> {
+					e.preventDefault();
+					hide.comp.ContextMenu.createDropdown(menu.get(0), [
+						{label: "Delete", click: () -> removeItem(i)}
+					]);
+				});
+			}			paramElement.find("header").get(0).addEventListener("contextmenu", function (e : js.html.MouseEvent) {
+				e.preventDefault();
+				hide.comp.ContextMenu.createFromEvent(e, [
+					{label: "Delete", click: () -> removeItem(i)}
+				]);
+			});
+
+			var menu = paramElement.find(".menu");
+			menu.on("click", (e) -> {
+				e.preventDefault();
+				hide.comp.ContextMenu.createDropdown(menu.get(0), [
+					{label: "Delete", click: () -> removeItem(i)}
+				]);
+			});
+		}
 	}
 
-	public var reorder : (oldIndex, newIndex) -> Void = null;
-	public var insert : (index) -> Void = null;
-	public var remove : (index) -> Void = null;
+	public var reorderItem : (oldIndex: Int, newIndex: Int) -> Void = null;
+
+	/**
+		If left null, no item cannot be added to the list
+	**/
+	public var insertItem : (index: Int) -> Void = null;
+
+	/**
+		If left null, the items cannot be removed from the list
+	**/
+	public var removeItem : (index: Int) -> Void = null;
 
-	dynamic function getItems() : Array<T> {
+	/**
+		If left null, the item name is read only
+	**/
+	public var setItemName: (item: T, name: String) -> Void = null;
+
+	/**
+		If left null, or if the function returns null, the item cannot be open
+	**/
+	public var getItemContent: (item: T) -> Element;
+
+	public dynamic function getItems() : Array<T> {
 		return [];
 	}
 
-	dynamic function getItemName(item: T) : String {
+	public dynamic function getItemName(item: T) : String {
 		return null;
 	}
 
-	dynamic function setItemName(name: String, item: T) : Void {
-
-	}
 }

+ 176 - 138
hide/view/animgraph/AnimGraphEditor.hx

@@ -17,7 +17,7 @@ class AnimGraphEditor extends GenericGraphEditor {
     var animGraph : hrt.animgraph.AnimGraph;
     public var previewPrefab : hrt.prefab.Prefab;
 
-    var parametersList : hide.Element;
+    var parametersList : hide.comp.FancyArray<hrt.animgraph.AnimGraph.Parameter>;
     var previewAnimation : AnimGraphInstance = null;
 
     var previewNode : hrt.animgraph.nodes.AnimNode = null;
@@ -50,7 +50,52 @@ class AnimGraphEditor extends GenericGraphEditor {
         addParameterBtn.click((e) -> {
             addParameter();
         });
-        parametersList = new Element("<ul></ul>").appendTo(parameters);
+
+        parametersList = new hide.comp.FancyArray<hrt.animgraph.AnimGraph.Parameter>(parameters, "Parameters", saveDisplayKey);
+        parametersList.getItems = () -> animGraph.parameters;
+        parametersList.getItemName = (param) -> param.name;
+        parametersList.setItemName = (param, name) -> {
+            var prev = param.name;
+            param.name = name;
+            undo.change(Field(param, "name", prev), () ->  {
+                var toRefresh = animGraph.nodes.filter((n) -> Std.downcast(n, hrt.animgraph.nodes.FloatParameter)?.parameter == param);
+                for (node in toRefresh) {
+                    graphEditor.refreshBox(node.id);
+                }
+                parametersList.refresh();
+            });
+            var toRefresh = animGraph.nodes.filter((n) -> Std.downcast(n, hrt.animgraph.nodes.FloatParameter)?.parameter == param);
+            for (node in toRefresh) {
+                graphEditor.refreshBox(node.id);
+            }
+        }
+        parametersList.reorderItem = (oldIndex: Int, newIndex: Int) -> {
+            execMoveParameterTo(oldIndex, newIndex);
+        }
+        parametersList.removeItem = (index: Int) -> {
+            execRemoveParam(index);
+        }
+        parametersList.getItemContent = (param: hrt.animgraph.AnimGraph.Parameter) -> {
+            if (previewAnimation != null) {
+                var props = new Element("<ul>");
+                var slider = new Element('<li><dd>Preview</dd><input type="range" min="-1.0" max="1.0" step="0.01" value="${param.runtimeValue}"></input></li>').appendTo(props).find("input");
+                var range = new hide.comp.Range(null,slider);
+
+                range.setOnChangeUndo(undo, () -> param.runtimeValue, (v:Float) -> {
+                    param.runtimeValue = v;
+                    var runtimeParam = previewAnimation.parameterMap.get(param.name);
+                    if (runtimeParam != null) {
+                        runtimeParam.runtimeValue = param.runtimeValue;
+                    }
+                });
+
+                var def = new Element('<li><dd>Default</dd><input type="range" min="-1.0" max="1.0" step="0.01" value="${param.defaultValue}"></input></li>').appendTo(props).find("input");
+                var range = new hide.comp.Range(null,def);
+                range.setOnChangeUndo(undo, () -> param.defaultValue, (v:Float) -> param.defaultValue = v);
+                return props;
+            }
+            return null;
+        }
 
         refreshPamamList();
 
@@ -65,7 +110,7 @@ class AnimGraphEditor extends GenericGraphEditor {
         new AnimList(propertiesContainer, null, getAnims(scenePreview, {animDirectory: animGraph.animFolder, assetPath: state.path}));
 
         graphEditor.element.get(0).addEventListener("dragover", (e: js.html.DragEvent) -> {
-            if (e.dataTransfer.types.contains("index"))
+            if (e.dataTransfer.types.contains(parametersList.getDragKeyName()))
                 e.preventDefault(); // prevent default to allow drop
 
             if (e.dataTransfer.types.contains(AnimList.dragEventKey))
@@ -78,7 +123,7 @@ class AnimGraphEditor extends GenericGraphEditor {
             // Handle drag from Parameters list
 
 
-            var paramIndex = Std.parseInt(e.dataTransfer.getData("index"));
+            var paramIndex = Std.parseInt(e.dataTransfer.getData(parametersList.getDragKeyName()));
             if (paramIndex != null) {
                 e.preventDefault();
                 var inst = new hrt.animgraph.nodes.FloatParameter();
@@ -321,132 +366,132 @@ class AnimGraphEditor extends GenericGraphEditor {
     }
 
     function refreshPamamList() {
-        parametersList.html("");
-        for (paramIndex => param in animGraph.parameters) {
-            var paramElement = new Element('<graph-parameter>
-                <header>
-                    <div class="reorder ico ico-reorder" draggable="true"></div>
-                    <div class="ico ico-chevron-down toggle-open"></div>
-                    <input type="text" value="${param.name}" class="fill"></input>
-                    <button-2 class="menu"><div class="ico ico-ellipsis-v"/></button-2>
-                </header>
-            </graph-parameter>').appendTo(parametersList);
-
-            var open : Bool = getDisplayState('param.${paramIndex}') ?? false;
-            paramElement.toggleClass("folded", open);
-
-            var name = paramElement.find("input");
-            name.on("change", (e) -> {
-                var prev = param.name;
-                var curr = name.val();
-
-                function exec(isUndo: Bool) {
-                    if (!isUndo) {
-                        param.name = curr;
-                    } else {
-                        param.name = prev;
-                    }
-                    name.val(param.name);
-                    var toRefresh = animGraph.nodes.filter((n) -> Std.downcast(n, hrt.animgraph.nodes.FloatParameter)?.parameter == param);
-                    for (node in toRefresh) {
-                        graphEditor.refreshBox(node.id);
-                    }
-                }
-
-                exec(false);
-                undo.change(Custom(exec));
-            });
-
-            name.on("contextmenu", (e) -> {
-                e.stopPropagation();
-            });
-
-            var toggleOpen = paramElement.find(".toggle-open");
-            toggleOpen.on("click", (e) -> {
-                open = !open;
-                saveDisplayState('param.${paramIndex}', open);
-                paramElement.toggleClass("folded", open);
-            });
-
-            var reorder = paramElement.find(".reorder");
-            reorder.get(0).ondragstart = (e: js.html.DragEvent) -> {
-                e.dataTransfer.setDragImage(paramElement.get(0), Std.int(paramElement.width()), 0);
-
-                e.dataTransfer.setData("index", '${paramIndex}');
-                e.dataTransfer.dropEffect = "move";
-            }
-
-            inline function isAfter(e) {
-                return e.clientY > (paramElement.offset().top + paramElement.outerHeight() / 2.0);
-            }
-
-            paramElement.get(0).addEventListener("dragover", function(e : js.html.DragEvent) {
-                if (!e.dataTransfer.types.contains("index"))
-                    return;
-                var after = isAfter(e);
-                paramElement.toggleClass("hovertop", !after);
-                paramElement.toggleClass("hoverbot", after);
-                e.preventDefault();
-            });
-
-            paramElement.get(0).addEventListener("dragleave", function(e : js.html.DragEvent) {
-                if (!e.dataTransfer.types.contains("index"))
-                    return;
-                paramElement.toggleClass("hovertop", false);
-                paramElement.toggleClass("hoverbot", false);
-            });
-
-            paramElement.get(0).addEventListener("dragenter", function(e : js.html.DragEvent) {
-                if (!e.dataTransfer.types.contains("index"))
-                    return;
-                e.preventDefault();
-            });
-
-            paramElement.get(0).addEventListener("drop", function(e : js.html.DragEvent) {
-                var toMoveIndex = Std.parseInt(e.dataTransfer.getData("index"));
-                paramElement.toggleClass("hovertop", false);
-                paramElement.toggleClass("hoverbot", false);
-                if (paramIndex == null)
-                    return;
-                var after = isAfter(e);
-                execMoveParameterTo(toMoveIndex, paramIndex, after);
-            });
-
-
-            var content = new Element("<content></content>").appendTo(paramElement);
-            var props = new Element("<ul>").appendTo(content);
-            if (previewAnimation != null) {
-                var slider = new Element('<li><dd>Preview</dd><input type="range" min="-1.0" max="1.0" step="0.01" value="${param.runtimeValue}"></input></li>').appendTo(props).find("input");
-                var range = new hide.comp.Range(null,slider);
-
-                range.setOnChangeUndo(undo, () -> param.runtimeValue, (v:Float) -> {
-                    param.runtimeValue = v;
-                    var runtimeParam = previewAnimation.parameterMap.get(param.name);
-                    if (runtimeParam != null) {
-                        runtimeParam.runtimeValue = param.runtimeValue;
-                    }
-                });
-
-                var def = new Element('<li><dd>Default</dd><input type="range" min="-1.0" max="1.0" step="0.01" value="${param.defaultValue}"></input></li>').appendTo(props).find("input");
-                var range = new hide.comp.Range(null,def);
-                range.setOnChangeUndo(undo, () -> param.defaultValue, (v:Float) -> param.defaultValue = v);
-            }
-
-            paramElement.find("header").get(0).addEventListener("contextmenu", function (e : js.html.MouseEvent) {
-                e.preventDefault();
-                hide.comp.ContextMenu.createFromEvent(e, [
-                    {label: "Delete", click: () -> execRemoveParam(paramIndex)}
-                ]);
-            });
-
-            var menu = paramElement.find(".menu");
-            menu.on("click", (e) -> {
-                e.preventDefault();
-                hide.comp.ContextMenu.createDropdown(menu.get(0), [
-                    {label: "Delete", click: () -> execRemoveParam(paramIndex)}
-                ]);
-            });
-        }
+        parametersList.refresh();
+        // for (paramIndex => param in animGraph.parameters) {
+        //     var paramElement = new Element('<graph-parameter>
+        //         <header>
+        //             <div class="reorder ico ico-reorder" draggable="true"></div>
+        //             <div class="ico ico-chevron-down toggle-open"></div>
+        //             <input type="text" value="${param.name}" class="fill"></input>
+        //             <button-2 class="menu"><div class="ico ico-ellipsis-v"/></button-2>
+        //         </header>
+        //     </graph-parameter>').appendTo(parametersList);
+
+        //     var open : Bool = getDisplayState('param.${paramIndex}') ?? false;
+        //     paramElement.toggleClass("folded", open);
+
+        //     var name = paramElement.find("input");
+        //     name.on("change", (e) -> {
+        //         var prev = param.name;
+        //         var curr = name.val();
+
+        //         function exec(isUndo: Bool) {
+        //             if (!isUndo) {
+        //                 param.name = curr;
+        //             } else {
+        //                 param.name = prev;
+        //             }
+        //             name.val(param.name);
+        //             var toRefresh = animGraph.nodes.filter((n) -> Std.downcast(n, hrt.animgraph.nodes.FloatParameter)?.parameter == param);
+        //             for (node in toRefresh) {
+        //                 graphEditor.refreshBox(node.id);
+        //             }
+        //         }
+
+        //         exec(false);
+        //         undo.change(Custom(exec));
+        //     });
+
+        //     name.on("contextmenu", (e) -> {
+        //         e.stopPropagation();
+        //     });
+
+        //     var toggleOpen = paramElement.find(".toggle-open");
+        //     toggleOpen.on("click", (e) -> {
+        //         open = !open;
+        //         saveDisplayState('param.${paramIndex}', open);
+        //         paramElement.toggleClass("folded", open);
+        //     });
+
+        //     var reorder = paramElement.find(".reorder");
+        //     reorder.get(0).ondragstart = (e: js.html.DragEvent) -> {
+        //         e.dataTransfer.setDragImage(paramElement.get(0), Std.int(paramElement.width()), 0);
+
+        //         e.dataTransfer.setData(parametersList.getDragKeyName(), '${paramIndex}');
+        //         e.dataTransfer.dropEffect = "move";
+        //     }
+
+        //     inline function isAfter(e) {
+        //         return e.clientY > (paramElement.offset().top + paramElement.outerHeight() / 2.0);
+        //     }
+
+        //     paramElement.get(0).addEventListener("dragover", function(e : js.html.DragEvent) {
+        //         if (!e.dataTransfer.types.contains(parametersList.getDragKeyName()))
+        //             return;
+        //         var after = isAfter(e);
+        //         paramElement.toggleClass("hovertop", !after);
+        //         paramElement.toggleClass("hoverbot", after);
+        //         e.preventDefault();
+        //     });
+
+        //     paramElement.get(0).addEventListener("dragleave", function(e : js.html.DragEvent) {
+        //         if (!e.dataTransfer.types.contains(parametersList.getDragKeyName()))
+        //             return;
+        //         paramElement.toggleClass("hovertop", false);
+        //         paramElement.toggleClass("hoverbot", false);
+        //     });
+
+        //     paramElement.get(0).addEventListener("dragenter", function(e : js.html.DragEvent) {
+        //         if (!e.dataTransfer.types.contains(parametersList.getDragKeyName()))
+        //             return;
+        //         e.preventDefault();
+        //     });
+
+        //     paramElement.get(0).addEventListener("drop", function(e : js.html.DragEvent) {
+        //         var toMoveIndex = Std.parseInt(e.dataTransfer.getData(parametersList.getDragKeyName()));
+        //         paramElement.toggleClass("hovertop", false);
+        //         paramElement.toggleClass("hoverbot", false);
+        //         if (paramIndex == null)
+        //             return;
+        //         var after = isAfter(e);
+        //         execMoveParameterTo(toMoveIndex, paramIndex, after);
+        //     });
+
+
+        //     var content = new Element("<content></content>").appendTo(paramElement);
+        //     var props = new Element("<ul>").appendTo(content);
+        //     if (previewAnimation != null) {
+        //         var slider = new Element('<li><dd>Preview</dd><input type="range" min="-1.0" max="1.0" step="0.01" value="${param.runtimeValue}"></input></li>').appendTo(props).find("input");
+        //         var range = new hide.comp.Range(null,slider);
+
+        //         range.setOnChangeUndo(undo, () -> param.runtimeValue, (v:Float) -> {
+        //             param.runtimeValue = v;
+        //             var runtimeParam = previewAnimation.parameterMap.get(param.name);
+        //             if (runtimeParam != null) {
+        //                 runtimeParam.runtimeValue = param.runtimeValue;
+        //             }
+        //         });
+
+        //         var def = new Element('<li><dd>Default</dd><input type="range" min="-1.0" max="1.0" step="0.01" value="${param.defaultValue}"></input></li>').appendTo(props).find("input");
+        //         var range = new hide.comp.Range(null,def);
+        //         range.setOnChangeUndo(undo, () -> param.defaultValue, (v:Float) -> param.defaultValue = v);
+        //     }
+
+        //     paramElement.find("header").get(0).addEventListener("contextmenu", function (e : js.html.MouseEvent) {
+        //         e.preventDefault();
+        //         hide.comp.ContextMenu.createFromEvent(e, [
+        //             {label: "Delete", click: () -> execRemoveParam(paramIndex)}
+        //         ]);
+        //     });
+
+        //     var menu = paramElement.find(".menu");
+        //     menu.on("click", (e) -> {
+        //         e.preventDefault();
+        //         hide.comp.ContextMenu.createDropdown(menu.get(0), [
+        //             {label: "Delete", click: () -> execRemoveParam(paramIndex)}
+        //         ]);
+        //     });
+        // }
 
         scenePreview.onObjectLoaded = () -> {
             setPreview(cast animGraph.nodes.find((f) -> Std.downcast(f, hrt.animgraph.nodes.Output) != null));
@@ -469,14 +514,7 @@ class AnimGraphEditor extends GenericGraphEditor {
         undo.change(Custom(exec));
     }
 
-    function execMoveParameterTo(oldIndex: Int, newIndex: Int, after: Bool) {
-        if (!after) newIndex -= 1;
-		if (oldIndex == newIndex)
-			return;
-        if (newIndex < oldIndex) {
-            newIndex += 1;
-        }
-
+    function execMoveParameterTo(oldIndex: Int, newIndex: Int) {
 		function exec(isUndo: Bool) {
             if (!isUndo) {
                 var param = animGraph.parameters.splice(oldIndex, 1)[0];