2
0
Эх сурвалжийг харах

[shgraph] Updated parameters list to a Fancy-array

Clément Espeute 6 сар өмнө
parent
commit
bc12804c38

+ 71 - 143
bin/style.css

@@ -2156,138 +2156,14 @@ input[type=checkbox].indeterminate:after {
   display: flex;
   flex-direction: column;
 }
-.shader-editor #rightPanel .hide-block #parametersList {
-  padding: 2px;
-  flex: 1;
-  display: flex;
-  flex-direction: column;
-  overflow-y: auto;
-  gap: 1px;
-  box-sizing: border-box;
-  width: 100%;
-}
-.shader-editor #rightPanel .hide-block #parametersList .parameter {
-  border: 1px solid #272727;
-  background-color: #222;
-  position: relative;
-  /*.content {
-						background-color: #b3b3b3;
-						padding: 5px;
-						box-shadow: inset 0px 13px 6px -15px black;
-
-						div {
-							width: 100%;
-							height: 20px;
-
-							span {
-								float: left;
-								color: black;
-								font-size: 13px;
-								padding-right: 5px;
-								font-weight: normal;
-							}
-						}
-						.texture-preview {
-							background-repeat: no-repeat;
-							background-size: 20px 20px!important;
-							border: 2px #444444 solid;
-							width: 20px;
-						}
-						.action-btns {
-							input {
-								float: right;
-							}
-						}
-					}*/
-}
-.shader-editor #rightPanel .hide-block #parametersList .parameter.reveal {
-  outline: 2px solid;
-  animation-duration: 0.75s;
-  animation-name: focus-show;
-  outline-color: transparent;
-  outline-offset: -2px;
-  z-index: 1;
-}
-@keyframes focus-show {
-  0%,
-  25%,
-  50%,
-  75% {
-    outline-color: #43578f;
-  }
-  12%,
-  37%,
-  62% {
-    outline-color: #7b8cb9;
-  }
-  100% {
-    outline-color: rgba(67, 87, 143, 0);
-  }
-}
-.shader-editor #rightPanel .hide-block #parametersList .parameter .hide-range {
-  display: block;
-}
-.shader-editor #rightPanel .hide-block #parametersList .parameter.hovertop:before,
-.shader-editor #rightPanel .hide-block #parametersList .parameter.hoverbot:after {
-  display: block;
-  position: absolute;
-  z-index: 100;
-  margin: 0 auto;
-  width: 100%;
-  content: "";
-}
-.shader-editor #rightPanel .hide-block #parametersList .parameter:before {
-  border-top: 10px solid rgba(114, 180, 255, 0.75);
-  top: 0px;
-  pointer-events: none;
-}
-.shader-editor #rightPanel .hide-block #parametersList .parameter:after {
-  border-bottom: 10px solid rgba(114, 180, 255, 0.75);
-  bottom: 0px;
-  pointer-events: none;
-}
-.shader-editor #rightPanel .hide-block #parametersList .parameter .header {
-  display: flex;
-  align-items: center;
-  line-height: 0px;
-  background-color: #393939;
-  height: 20px;
-  padding: 1px;
-  color: #c3c3c3;
-  cursor: pointer;
-}
-.shader-editor #rightPanel .hide-block #parametersList .parameter .header .ico {
-  font-size: 8pt;
-  width: 16px;
-  text-align: center;
-}
-.shader-editor #rightPanel .hide-block #parametersList .parameter .header .input-title {
-  flex: 1 0 130px;
-  background: none;
-  border: none;
-  width: 100%;
-  padding: 0;
-  margin: 0;
-}
-.shader-editor #rightPanel .hide-block #parametersList .parameter .header .input-title:hover {
-  background-color: rgba(114, 180, 255, 0.5);
-}
-.shader-editor #rightPanel .hide-block #parametersList .parameter .header .type {
-  flex-grow: 1;
-  text-align: right;
+.shader-editor #rightPanel .hide-block .parameters fancy-item-header .type,
+.shader-editor #rightPanel .hide-block .variables fancy-item-header .type {
   font-style: italic;
-  font-weight: 300;
-  font-size: 13px;
-  padding-right: 5px;
-}
-.shader-editor #rightPanel .hide-block #parametersList .parameter .content {
-  padding: 4px;
 }
-.shader-editor #rightPanel .hide-block #parametersList .parameter .content > div {
-  margin-top: 2px;
-}
-.shader-editor #rightPanel .hide-block #parametersList .parameter > span {
-  font-size: 12px;
+.shader-editor #rightPanel .hide-block .parameters .content .values,
+.shader-editor #rightPanel .hide-block .variables .content .values {
+  display: flex;
+  flex-direction: column;
 }
 .shader-editor #rightPanel .options-block {
   flex: 0;
@@ -4128,6 +4004,7 @@ hide-popover hide-content {
   margin: 0em 0.5em;
   font-size: 9pt;
   color: #888;
+  white-space: nowrap;
 }
 .context-menu2 menu separator hr {
   margin-top: 0.8em;
@@ -4538,6 +4415,8 @@ fancy-button {
   position: relative;
   --size: 28px;
   border: 1px solid var(--fancy-border-color);
+  flex-shrink: 0;
+  border-radius: var(--radius);
   text-align: center;
   height: var(--size);
   min-width: var(--size);
@@ -4549,8 +4428,10 @@ fancy-button {
   color: #DDD;
   gap: 0.5em;
   font-size: calc(var(--size) * 0.5);
-  margin-left: -1px;
+  margin-left: 0px;
   --radius: 3px;
+  /* remove start rounded corner if previous element is a fancy button */
+  /* remove end rounded corners if next element is a fancy-button */
 }
 fancy-button:has(.label),
 fancy-button.dropdown {
@@ -4591,16 +4472,14 @@ fancy-button.compact {
 fancy-button.compact .ico {
   font-size: 0.7em;
 }
-fancy-button:first-child,
-:not(fancy-button) + fancy-button {
-  margin-left: 0px;
-  border-start-start-radius: var(--radius);
-  border-end-start-radius: var(--radius);
+fancy-button:has( + fancy-button) {
+  border-start-end-radius: 0;
+  border-end-end-radius: 0;
 }
-fancy-button:last-child,
-fancy-button:has( + :not(fancy-button)) {
-  border-start-end-radius: var(--radius);
-  border-end-end-radius: var(--radius);
+fancy-button + fancy-button {
+  border-start-start-radius: 0;
+  border-end-start-radius: 0;
+  margin-left: -1px;
 }
 fancy-button.selected {
   color: #000;
@@ -4646,11 +4525,36 @@ fancy-array fancy-items fancy-item {
   flex-direction: column;
   position: relative;
 }
+fancy-array fancy-items fancy-item.reveal {
+  outline: 2px solid;
+  animation-duration: 0.75s;
+  animation-name: focus-show;
+  outline-color: transparent;
+  outline-offset: -2px;
+  z-index: 1;
+}
+@keyframes focus-show {
+  0%,
+  25%,
+  50%,
+  75% {
+    outline-color: #43578f;
+  }
+  12%,
+  37%,
+  62% {
+    outline-color: #7b8cb9;
+  }
+  100% {
+    outline-color: rgba(67, 87, 143, 0);
+  }
+}
 fancy-array fancy-items fancy-item fancy-item-header {
   display: flex;
   color: var(--fancy-quiet-text-color);
   border-radius: 3px;
   background-color: #444;
+  align-items: center;
 }
 fancy-array fancy-items fancy-item fancy-item-header > input[type="text"] {
   min-width: 0;
@@ -4671,11 +4575,9 @@ fancy-array fancy-items fancy-item fancy-item-header > input[type="text"]:focus
 }
 fancy-array fancy-items fancy-item fancy-item-content {
   display: none;
-  margin-top: 2px;
-  padding: 0.2em 1em;
+  padding: 1em 1em;
   padding-right: 0px;
   padding-right: 0;
-  margin-bottom: 0.5em;
 }
 fancy-array fancy-items fancy-item .toggle-open * {
   transition: transform 0.2s;
@@ -4683,8 +4585,16 @@ fancy-array fancy-items fancy-item .toggle-open * {
 fancy-array fancy-items fancy-item.open > fancy-item-header .toggle-open * {
   transform: rotate(90deg);
 }
+fancy-array fancy-items fancy-item.open fancy-item-header {
+  border-bottom-left-radius: 0;
+  border-bottom-right-radius: 0;
+}
 fancy-array fancy-items fancy-item.open > fancy-item-content {
   display: block;
+  background-color: #2d2d2d;
+  border-top: none;
+  border-bottom-left-radius: var(--basic-border-radius);
+  border-bottom-right-radius: var(--basic-border-radius);
 }
 fancy-array fancy-items fancy-item.hovertop:before,
 fancy-array fancy-items fancy-item.hoverbot:after {
@@ -4926,3 +4836,21 @@ blend-space-2d-root properties-container .hide-properties dl > div .hide-range i
   padding: var(--basic-padding);
   border-radius: var(--basic-border-radius);
 }
+.merge-bottom {
+  border-bottom-right-radius: 0;
+}
+.merge-top {
+  border-top-right-radius: 0;
+  border-top-left-radius: 0;
+  align-self: flex-end;
+  margin-top: -1px;
+  background-color: #353535;
+  border: none;
+}
+.subtle-title {
+  margin: 0;
+  margin-left: 0.5em;
+  font-weight: normal;
+  font-style: italic;
+  font-size: 1.2em;
+}

+ 79 - 155
bin/style.less

@@ -2453,150 +2453,16 @@ input[type=checkbox] {
 			display: flex;
 			flex-direction: column;
 
-			#parametersList {
-				padding: 2px;
-				flex: 1;
-				display: flex;
-				flex-direction: column;
-				overflow-y: auto;
-				gap: 1px;
-				box-sizing: border-box;
-				width: 100%;
-
-				.parameter {
-					border: 1px solid #272727;
-					background-color: #222;
-					position: relative;
-
-					&.reveal {
-						outline: 2px solid;
-						animation-duration: 0.75s;
-						animation-name: focus-show;
-						outline-color: transparent;
-						outline-offset: -2px;
-						z-index: 1;
-					}
-
-					@keyframes focus-show {
-						0%, 25%, 50%, 75% {
-							outline-color: rgba(67, 87, 143, 1.0);
-						}
-
-						12%, 37%, 62% {
-							outline-color: rgb(123, 140, 185);
-						}
-
-						100% {
-							outline-color: rgba(67, 87, 143, 0.0);
-						}
-					}
-
-					.hide-range {
-						display: block;
-					}
-
-					&.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;
-					}
-
-
-					.header {
-						display: flex;
-						align-items: center;
-						line-height: 0px;
-						background-color: #393939;
-						height: 20px;
-						padding: 1px;
-						color: #c3c3c3;
-						cursor: pointer;
-
-						.ico {
-							font-size: 8pt;
-							width: 16px;
-							text-align: center;
-						}
-
-						.input-title {
-							flex: 1 0 130px;
-							background: none;
-							border: none;
-							width: 100%;
-							padding: 0;
-							margin: 0;
-							&:hover {
-								background-color: rgba(114, 180, 255, 0.5);
-							}
-						}
-
-						.type {
-							flex-grow: 1;
-							text-align: right;
-
-							font-style: italic;
-							font-weight: 300;
-							font-size: 13px;
-							padding-right: 5px;
-						}
+			.parameters, .variables {
+				fancy-item-header {
+					.type {
+						font-style: italic;
 					}
+				}
 
-					.content {
-						padding: 4px;
-
-						>div {
-							margin-top: 2px;
-						}
-					}
-
-					/*.content {
-						background-color: #b3b3b3;
-						padding: 5px;
-						box-shadow: inset 0px 13px 6px -15px black;
-
-						div {
-							width: 100%;
-							height: 20px;
-
-							span {
-								float: left;
-								color: black;
-								font-size: 13px;
-								padding-right: 5px;
-								font-weight: normal;
-							}
-						}
-						.texture-preview {
-							background-repeat: no-repeat;
-							background-size: 20px 20px!important;
-							border: 2px #444444 solid;
-							width: 20px;
-						}
-						.action-btns {
-							input {
-								float: right;
-							}
-						}
-					}*/
-
-					& > span {
-						font-size: 12px;
-					}
+				.content .values {
+					display: flex;
+					flex-direction: column;
 				}
 			}
 		}
@@ -4855,6 +4721,7 @@ hide-popover {
 				margin: 0em 0.5em;
 				font-size: 9pt;
 				color: #888;
+				white-space: nowrap;
 			}
 			hr {
 				margin-top: 0.8em;
@@ -5386,7 +5253,9 @@ fancy-button {
 	position: relative;
 	--size: 28px;
 	border: 1px solid var(--fancy-border-color);
+	flex-shrink: 0;
 
+	border-radius: var(--radius);
 
 	text-align: center;
 
@@ -5456,18 +5325,22 @@ fancy-button {
 		}
 	}
 
-	margin-left: -1px;
+	margin-left: 0px;
 
 	--radius: 3px;
-	&:first-child, :not(&) + & {
-		margin-left: 0px;
-		border-start-start-radius: var(--radius);
-		border-end-start-radius: var(--radius);
+
+	/* remove start rounded corner if previous element is a fancy button */
+	&:has(+ &) {
+		border-start-end-radius: 0;
+		border-end-end-radius: 0;
+
 	}
 
-	&:last-child, &:has(+ :not(&)) {
-		border-start-end-radius: var(--radius);
-		border-end-end-radius: var(--radius);
+	/* remove end rounded corners if next element is a fancy-button */
+	& + & {
+		border-start-start-radius: 0;
+		border-end-start-radius: 0;
+		margin-left: -1px;
 	}
 
 	&.selected {
@@ -5525,6 +5398,29 @@ fancy-array {
 			flex-direction: column;
 			position: relative;
 
+			&.reveal {
+				outline: 2px solid;
+				animation-duration: 0.75s;
+				animation-name: focus-show;
+				outline-color: transparent;
+				outline-offset: -2px;
+				z-index: 1;
+			}
+
+			@keyframes focus-show {
+				0%, 25%, 50%, 75% {
+					outline-color: rgba(67, 87, 143, 1.0);
+				}
+
+				12%, 37%, 62% {
+					outline-color: rgb(123, 140, 185);
+				}
+
+				100% {
+					outline-color: rgba(67, 87, 143, 0.0);
+				}
+			}
+
 			fancy-item-header {
 				display: flex;
 
@@ -5532,6 +5428,7 @@ fancy-array {
 				border-radius: 3px;
 
 				background-color: #444;
+				align-items: center;
 
 				>input[type="text"] {
 					min-width: 0;
@@ -5560,13 +5457,9 @@ fancy-array {
 
 			fancy-item-content {
 				display: none;
-				margin-top: 2px;
-
-				padding:  0.2em 1em;
+				padding:  1em 1em;
 				padding-right: 0px;
 				padding-right: 0;
-
-				margin-bottom: 0.5em;
 			}
 
 			.toggle-open * {
@@ -5578,8 +5471,17 @@ fancy-array {
 					transform: rotate(90deg);
 				}
 
+				fancy-item-header {
+					border-bottom-left-radius: 0;
+					border-bottom-right-radius: 0;
+				}
+
 				> fancy-item-content{
 					display: block;
+					background-color: #2d2d2d;
+					border-top: none;
+					border-bottom-left-radius: var(--basic-border-radius);
+					border-bottom-right-radius: var(--basic-border-radius);
 				}
 			}
 
@@ -5882,4 +5784,26 @@ blend-space-2d-root {
 
 		border-radius: var(--basic-border-radius);
 	}
+}
+
+
+.merge-bottom {
+	border-bottom-right-radius: 0;
+}
+
+.merge-top {
+	border-top-right-radius: 0;
+	border-top-left-radius: 0;
+	align-self: flex-end;
+	margin-top: -1px;
+	background-color: #353535;
+	border: none;
+}
+
+.subtle-title {
+	margin: 0;
+	margin-left: 0.5em;
+	font-weight: normal;
+	font-style: italic;
+	font-size: 1.2em;
 }

+ 52 - 3
hide/comp/FancyArray.hx

@@ -8,6 +8,7 @@ class FancyArray<T> extends hide.comp.Component {
 	var itemState : Array<FancyItemState>;
 	var name : String;
 	var fancyItems: Element;
+	var itemsElements : Array<Element> = [];
 
 	public function new(parent: Element = null, e: Element = null, name: String, displayKey: String) {
 		if (e == null)
@@ -33,13 +34,31 @@ class FancyArray<T> extends hide.comp.Component {
 		return dragKeyName;
 	}
 
+	/**
+		Check if the given drag event comes from this FancyArray,
+		and if that's the case, returns the relevant Item index that
+		was dragged from this array
+	**/
+	public function getDragIndex(e:js.html.DragEvent) : Null<Int> {
+		if (!e.dataTransfer.types.contains(getDragKeyName()))
+			return null;
+		return Std.parseInt(e.dataTransfer.getData(getDragKeyName()));
+	}
+
 	function saveState() {
 		saveDisplayState("state", haxe.Json.stringify(itemState));
 	}
 
+	function toggleItem(index:Int, ?forceState: Bool) {
+		itemState[index].open = forceState ?? !itemState[index].open;
+		saveState();
+		fancyItems.children()[index].classList.toggle("open", itemState[index].open);
+	}
+
 	public function refresh() : Void {
 		fancyItems.empty();
 		var items = getItems();
+		itemsElements = [];
 
 		for (i => item in items) {
 			var paramElement = new Element('<fancy-item>
@@ -55,6 +74,8 @@ class FancyArray<T> extends hide.comp.Component {
 				</fancy-item-header>
 			</fancy-item>').appendTo(fancyItems);
 
+			itemsElements.push(paramElement);
+
 			itemState[i] ??= {};
 			var state = itemState[i];
 			var open : Bool = state.open ?? false;
@@ -73,6 +94,8 @@ class FancyArray<T> extends hide.comp.Component {
 						e.stopPropagation();
 					}
 				});
+			} else {
+				name.attr("readonly");
 			}
 
 			name.on("contextmenu", (e) -> {
@@ -145,9 +168,7 @@ class FancyArray<T> extends hide.comp.Component {
 					contentElement.appendTo(content);
 
 					toggleOpen.on("click", (e) -> {
-						state.open = !state.open;
-						saveState();
-						paramElement.toggleClass("open", state.open);
+						toggleItem(i);
 					});
 				} else {
 					toggleOpen.remove();
@@ -185,9 +206,31 @@ class FancyArray<T> extends hide.comp.Component {
 					{label: "Delete", click: () -> removeItem(i)}
 				]);
 			});
+
+			if (customizeHeader != null) {
+				customizeHeader(item, paramElement.find("fancy-item-header"));
+			}
 		}
 	}
 
+	// Open target item index, and make it flash briefly
+	public function reveal(index: Int) {
+		for (i => param in itemsElements) {
+			toggleItem(i, i == index);
+		}
+		var param = itemsElements[index].get(0);
+		param.onanimationend = (e) -> {
+			param.classList.remove("reveal");
+		};
+		param.classList.remove("reveal");
+		param.classList.add("reveal");
+	}
+
+	// Focus the title bar of the given index for editing
+	public function editTitle(index: Int) {
+		itemsElements[index].find("input").focus().select();
+	}
+
 	public var reorderItem : (oldIndex: Int, newIndex: Int) -> Void = null;
 
 	/**
@@ -210,6 +253,12 @@ class FancyArray<T> extends hide.comp.Component {
 	**/
 	public var getItemContent: (item: T) -> Element;
 
+	/**
+		If set, the function will be called after the header element is created so the user could customize it's appearence
+		like adding icons or type information
+	**/
+	public var customizeHeader: (item: T, header: Element) -> Void;
+
 	public dynamic function getItems() : Array<T> {
 		return [];
 	}

+ 268 - 359
hide/view/shadereditor/ShaderEditor.hx

@@ -166,7 +166,7 @@ class ShaderEditor extends hide.view.FileView implements GraphInterface.IGraphEd
 	var meshPreviewRenderProps : hrt.prefab.Prefab;
 	var meshPreviewRenderPropsRoot : h3d.scene.Object;
 
-	var parametersList : JQuery;
+	var parametersList : hide.comp.FancyArray<hrt.shgraph.ShaderGraph.Parameter>;
 	var variableList : hide.comp.FancyArray<ShaderGraphVariable>;
 
 	var previewElem : Element;
@@ -229,11 +229,12 @@ class ShaderEditor extends hide.view.FileView implements GraphInterface.IGraphEd
 				graphEditor.opBox(inst, true, graphEditor.currentUndoBuffer);
 				graphEditor.commitUndo();
 			}
-			if (e.dataTransfer.types.contains(variableList.getDragKeyName())) {
-				var index = Std.parseInt(e.dataTransfer.getData(variableList.getDragKeyName()));
+
+			var variableIndex = variableList.getDragIndex(e);
+			if (variableIndex != null) {
 				var hasAnyWrite = false;
 				shaderGraph.mapShaderVar((v) -> {
-					if (v.varId == index && Std.downcast(v, hrt.shgraph.nodes.VarWrite) != null) {
+					if (v.varId == variableIndex && Std.downcast(v, hrt.shgraph.nodes.VarWrite) != null) {
 						hasAnyWrite = true;
 						return false;
 					}
@@ -242,20 +243,20 @@ class ShaderEditor extends hide.view.FileView implements GraphInterface.IGraphEd
 
 				if (hasAnyWrite) {
 					var read = new hrt.shgraph.nodes.VarRead();
-					read.varId = index;
+					read.varId = variableIndex;
 					addNode(read);
 				} else {
 					hide.comp.ContextMenu.createFromPoint(e.clientX, e.clientY, [{
 						label: "Write", click: () -> {
 							var write = new hrt.shgraph.nodes.VarWrite();
-							write.varId = index;
+							write.varId = variableIndex;
 							addNode(write);
 						}
 					},
 					{
 						label: "Read", click: () -> {
 							var read = new hrt.shgraph.nodes.VarRead();
-							read.varId = index;
+							read.varId = variableIndex;
 							addNode(read);
 						}
 					}]);
@@ -263,28 +264,33 @@ class ShaderEditor extends hide.view.FileView implements GraphInterface.IGraphEd
 				return;
 			}
 
-			var inst = new ShaderParam();
-			inst.parameterId = draggedParamId;
-			addNode(inst);
+			var paramIndex = parametersList.getDragIndex(e);
+			if (paramIndex != null) {
+				var inst = new ShaderParam();
+				inst.parameterId = paramIndex;
+				addNode(inst);
+				return;
+			}
 		};
 
 		var rightPannel = new Element(
 			'<div id="rightPanel">
 				<div class="hide-block flexible" >
-					<span>Parameters</span>
+					<h1 class="subtle-title">Parameters</h1 class="subtle-title">
 
-					<div id="parametersList" class="hide-scene-tree hide-list">
-					</div>
+					<fancy-array class="parameters merge-bottom" style="flex-grow: 1">
 
-					<input id="createParameter" type="button" value="Add parameter" />
+					</fancy-array>
+					<fancy-button class="fancy-small createParameter merge-top"><div class="icon ico ico-plus"></div></fancy-button>
 				</div>
 
 
 				<div class="hide-block flexible" >
-					<fancy-array class="variables" style="flex-grow: 1">
+					<h1 class="subtle-title">Variables</h1 class="subtle-title">
+					<fancy-array class="variables merge-bottom" style="flex-grow: 1">
 
 					</fancy-array>
-					<fancy-button class="fancy-small add-variable">Add Variable</fancy-button>
+					<fancy-button class="fancy-small add-variable merge-top"><div class="icon ico ico-plus"></div></fancy-button>
 				</div>
 
 				<div class="options-block hide-block">
@@ -309,37 +315,58 @@ class ShaderEditor extends hide.view.FileView implements GraphInterface.IGraphEd
 		variableList.removeItem = removeVariable;
 		variableList.setItemName = renameVariable;
 		variableList.getItemContent = getVariableContent;
+		variableList.customizeHeader = (v:ShaderGraphVariable, header:Element) -> {
+			var type = switch(v.type) {
+				case SgFloat(1): "Float";
+				case SgFloat(n): "Vec " + n;
+				case SgSampler: "Texture";
+				case SgInt: "Int";
+				case SgBool: "Bool";
+				default: "Unknown Type";
+			};
+			header.find("input").after(new Element('<div class="type">$type</div>'));
+		}
 
 		variableList.refresh();
 
 		var addVariable = rightPannel.find(".add-variable");
+		var createVariableMenu : Array<hide.comp.ContextMenu.MenuItem> = [
+			{
+				label: "Int",
+				click: () -> createVariable(SgInt),
+			},
+			{
+				label: "Float",
+				click: () -> createVariable(SgFloat(1)),
+			},
+			{
+				label: "Vec 2",
+				click: () -> createVariable(SgFloat(2)),
+			},
+			{
+				label: "Vec 3",
+				click: () -> createVariable(SgFloat(3)),
+			},
+			{
+				label: "Vec 4",
+				click: () -> createVariable(SgFloat(4)),
+			},
+			{
+				label: "Color",
+				click: () -> createVariable(SgFloat(4), true),
+			},
+		];
+
 		addVariable.on("click", (e) -> {
-			hide.comp.ContextMenu.createDropdown(addVariable.get(0), [
-				{
-					label: "Int",
-					click: () -> createVariable(SgInt),
-				},
-				{
-					label: "Float",
-					click: () -> createVariable(SgFloat(1)),
-				},
-				{
-					label: "Vec 2",
-					click: () -> createVariable(SgFloat(2)),
-				},
-				{
-					label: "Vec 3",
-					click: () -> createVariable(SgFloat(3)),
-				},
-				{
-					label: "Vec 4",
-					click: () -> createVariable(SgFloat(4)),
-				},
-				{
-					label: "Color",
-					click: () -> createVariable(SgFloat(4), true),
-				},
-			]);
+			hide.comp.ContextMenu.createDropdown(addVariable.get(0), createVariableMenu);
+		});
+
+		variableList.element.on("contextmenu", function(e) {
+			e.preventDefault();
+			e.stopPropagation();
+			var vars = createVariableMenu.copy();
+			vars.unshift({label:"New", isSeparator: true});
+			hide.comp.ContextMenu.createFromEvent(e.originalEvent, vars);
 		});
 
 		rightPannel.find("#centerView").click((e) -> graphEditor.centerView());
@@ -377,27 +404,44 @@ class ShaderEditor extends hide.view.FileView implements GraphInterface.IGraphEd
 			{ label : "Texture", click : () -> createParameter(HxslType.TSampler(T2D,false)) },
 		];
 
-		var createParameter = rightPannel.find("#createParameter");
-		createParameter.on("click", function() {
+		var createParameter = rightPannel.find(".createParameter");
+		createParameter.on("click", function(e) {
 			hide.comp.ContextMenu.createDropdown(createParameter.get(0), newParamCtxMenu);
 		});
 
-		parametersList = rightPannel.find("#parametersList");
-		parametersList.on("contextmenu", function(e) {
+
+		parametersList = new hide.comp.FancyArray(null, rightPannel.find(".parameters"), "parameters", "parameters");
+		parametersList.element.on("contextmenu", function(e) {
 			e.preventDefault();
 			e.stopPropagation();
-			hide.comp.ContextMenu.createFromEvent(cast e, [
-				{
-					label : "Add Parameter",
-					menu : newParamCtxMenu,
-				},
-			]);
+			var params = newParamCtxMenu.copy();
+			params.unshift({label:"New", isSeparator: true});
+			hide.comp.ContextMenu.createFromEvent(e.originalEvent, params);
 		});
 
-		for (k in shaderGraph.parametersKeys) {
-			var pElt = addParameter(shaderGraph.parametersAvailable.get(k), shaderGraph.parametersAvailable.get(k).defaultValue);
+		parametersList.getItems = () -> {
+			var values = shaderGraph.parametersAvailable.array();
+			values.sort((a, b) -> Reflect.compare(a.index, b.index));
+			return values;
+		};
+		parametersList.getItemName = (v: Parameter) -> v.name;
+		parametersList.reorderItem = reorderParameter;
+		parametersList.removeItem = removeParameter;
+		parametersList.setItemName = renameParameter;
+		parametersList.getItemContent = getParameterContent;
+		parametersList.customizeHeader = (p:Parameter, header:Element) -> {
+			var type = switch(p.type) {
+				case TFloat: "Number";
+				case TVec(4, VFloat): "Color";
+				case TVec(1, VFloat): "Float";
+				case TVec(n, VFloat): "Vec " + n;
+				case TSampler(_): "Texture";
+				default: "Unknown Type";
+			};
+			header.find("input").after(new Element('<div class="type">$type</div>'));
 		}
 
+		parametersList.refresh();
 
 		rightPannel.find("#debugMenu").click((e) -> {
 			hide.comp.ContextMenu.createDropdown(rightPannel.find("#debugMenu").get(0), [
@@ -534,6 +578,7 @@ class ShaderEditor extends hide.view.FileView implements GraphInterface.IGraphEd
 		}
 		exec(false);
 		undo.change(Custom(exec));
+		variableList.editTitle(shaderGraph.variables.length-1);
 	}
 
 	var validNameCheck = ~/^[_a-zA-Z][_a-zA-Z0-9]*$/;
@@ -546,6 +591,12 @@ class ShaderEditor extends hide.view.FileView implements GraphInterface.IGraphEd
 			return;
 		}
 
+		if (shaderGraph.variables.find(v -> v != variable && v.name == newName) != null) {
+			variableList.refresh();
+			ide.quickError('Variable name "$newName" already exist in this shadergraph');
+			return;
+		}
+
 		var oldName = variable.name;
 		function exec(isUndo: Bool) {
 			variable.name = !isUndo ? newName : oldName;
@@ -705,91 +756,41 @@ class ShaderEditor extends hide.view.FileView implements GraphInterface.IGraphEd
 		undo.change(Custom(exec));
 	}
 
-	function createParameter(type : HxslType) {
-		@:privateAccess var paramShaderID : Int = shaderGraph.current_param_id++;
-		@:privateAccess
-		function exec(isUndo:Bool) {
-			if (!isUndo) {
-				var name = "Param_" + paramShaderID;
-				shaderGraph.parametersAvailable.set(paramShaderID, {id: paramShaderID, name : name, type : type, defaultValue : null, variable : shaderGraph.generateParameter(name, type), index : shaderGraph.parametersKeys.length});
-				shaderGraph.parametersKeys.push(paramShaderID);
+	function renameParameter(item: Parameter, name: String) : Void {
+		if (!validNameCheck.match(name))
+		{
+			parametersList.refresh();
+			ide.quickError('"$name" is not a valid parameter name (must start with _ or a letter, and only contains letters, numbers and underscores)');
+			return;
+		}
 
-				var paramShader = shaderGraph.getParameter(paramShaderID);
-				var elt = addParameter(paramShader, null);
-				elt.find(".input-title").focus();
-			} else {
-				shaderGraph.parametersAvailable.remove(paramShaderID);
-				shaderGraph.parametersKeys.remove(paramShaderID);
-				parametersUpdate.remove(paramShaderID);
-				shaderGraph.checkParameterIndex();
-				parametersList.find("#param_" + paramShaderID).remove();
-			}
+		if (shaderGraph.parametersAvailable.find(p -> p != item && p.name == name) != null) {
+			parametersList.refresh();
+			ide.quickError('Parameter name "$name" already exist in this shadergraph');
+			return;
 		}
 
+		var oldName = item.name;
+		function exec(isUndo: Bool) {
+			item.name = !isUndo ? name : oldName;
+			item.variable.name = item.name;
+			for (node in currentGraph.nodes) {
+				var param = Std.downcast(node, ShaderParam);
+				if (param == null)
+					continue;
+				graphEditor.refreshBox(node.id);
+			}
+			requestRecompile();
+			parametersList.refresh();
+		}
 		exec(false);
 		undo.change(Custom(exec));
 	}
 
-	function moveParameter(parameter : Parameter, up : Bool) {
-		var parameterElt = parametersList.find("#param_" + parameter.id);
-		var parameterPrev = shaderGraph.parametersAvailable.get(shaderGraph.parametersKeys[shaderGraph.parametersKeys.indexOf(parameter.id) + (up? -1 : 1)]);
-		execMoveParameterTo(parameter, parameterPrev, !up);
-	}
-
-	function updateParam(id : Int) : Bool {
-		meshPreviewScene.setCurrent(); // needed for texture changes
-
-		var param = shaderGraph.getParameter(id);
-		var init = compiledShader.inits.find((i) -> i.variable.name == param.name);
-		if (init != null) {
-			setParamValue(meshPreviewShader, init.variable, param.defaultValue);
-			return true;
-		}
-		return false;
-	}
-
-	var parametersUpdate : Map<Int, (Dynamic) -> Void> = [];
-
-	function addParameter(parameter : Parameter, ?value : Dynamic) {
-
-		var elt = new Element('<div id="param_${parameter.id}" class="parameter" draggable="true"></div>').appendTo(parametersList);
-		elt.on("click", function(e) {e.stopPropagation();});
-		elt.on("contextmenu", function(e) {
-			var elements = [];
-			e.stopPropagation();
-			var newCtxMenu : Array<hide.comp.ContextMenu.MenuItem> = [
-				{ label: "Select Nodes", click : () -> {
-					var list : Array<IGraphNode> = [];
-					for (node in currentGraph.getNodes()) {
-						var param = Std.downcast(node, ShaderParam);
-						if (param != null && param.parameterId == parameter.id) {
-							list.push(param);
-						}
-					}
-					graphEditor.setSelection(list);
-					graphEditor.centerSelection();
-				}},
-				{ isSeparator: true},
-				{ label : "Move up", click : () -> {
-					//beforeChange();
-					moveParameter(parameter, true);
-					//afterChange();
-				}, enabled: shaderGraph.parametersKeys.indexOf(parameter.id) > 0},
-				{ label : "Move down", click : () -> {
-					//beforeChange();
-					moveParameter(parameter, false);
-					//afterChange();
-				}, enabled: shaderGraph.parametersKeys.indexOf(parameter.id) < shaderGraph.parametersKeys.length-1}
-			];
-			hide.comp.ContextMenu.createFromEvent(cast e, newCtxMenu);
-			e.preventDefault();
-
-		});
+	function getParameterContent(parameter: Parameter) : Element {
 		var content = new Element('<div class="content" ></div>');
-		content.hide();
-		var defaultValue = new Element("<div><span>Default: </span></div>").appendTo(content);
+		var defaultValue = new Element('<div class="values"><span>Default: </span></div>').appendTo(content);
 
-		var typeName = "";
 		switch(parameter.type) {
 			case TFloat:
 				var parentRange = new Element('<input type="range" min="-1" max="1" />').appendTo(defaultValue);
@@ -797,19 +798,9 @@ class ShaderEditor extends hide.view.FileView implements GraphInterface.IGraphEd
 				var rangeInput = @:privateAccess range.f;
 
 				var save : Null<Float> = null;
-				rangeInput.on("mousedown", function(e) {
-					elt.attr("draggable", "false");
-					//beforeChange();
-				});
-				rangeInput.on("mouseup", function(e) {
-					elt.attr("draggable", "true");
-					//afterChange();
-				});
-				if (value == null) value = 0;
-				range.value = value;
 
-				parametersUpdate.set(parameter.id, (v:Dynamic) -> range.value = v);
-				shaderGraph.setParameterDefaultValue(parameter.id, value);
+				if (parameter.defaultValue == null) parameter.defaultValue = 0;
+				range.value = parameter.defaultValue;
 
 				var saveValue : Null<Float> = null;
 				range.onChange = function(moving) {
@@ -823,8 +814,8 @@ class ShaderEditor extends hide.view.FileView implements GraphInterface.IGraphEd
 						saveValue = null;
 						function exec(isUndo : Bool) {
 							var v = isUndo ? old : curr;
-							shaderGraph.setParameterDefaultValue(parameter.id, v);
-							parametersUpdate[parameter.id](v);
+							parameter.defaultValue = v;
+							range.value = v;
 							updateParam(parameter.id);
 						}
 						exec(false);
@@ -836,20 +827,15 @@ class ShaderEditor extends hide.view.FileView implements GraphInterface.IGraphEd
 						return;
 					updateParam(parameter.id);
 				};
-				typeName = "Number";
 			case TVec(4, VFloat):
 				var parentPicker = new Element('<div style="width: 35px; height: 25px; display: inline-block;"></div>').appendTo(defaultValue);
 				var picker = new hide.comp.ColorPicker.ColorBox(null, parentPicker, true, true);
 
-
-				if (value == null)
-					value = [0, 0, 0, 1];
-				var start : h3d.Vector = h3d.Vector.fromArray(value);
-				shaderGraph.setParameterDefaultValue(parameter.id, value);
+				if (parameter.defaultValue == null)
+					parameter.defaultValue = [0, 0, 0, 1];
+				var start : h3d.Vector = h3d.Vector.fromArray(parameter.defaultValue);
 				picker.value = start.toColor();
 
-				parametersUpdate.set(parameter.id, (v:Dynamic) -> picker.value = v.toColor());
-
 				var saveValue : Null<h3d.Vector4> = null;
 				picker.onChange = function(move) {
 					if (saveValue == null) {
@@ -862,8 +848,8 @@ class ShaderEditor extends hide.view.FileView implements GraphInterface.IGraphEd
 						saveValue = null;
 						function exec(isUndo : Bool) {
 							var v = isUndo ? old : curr;
-							parametersUpdate[parameter.id](v);
-							shaderGraph.setParameterDefaultValue(parameter.id, [v.x, v.y, v.z, v.w]);
+							picker.value = v.toColor();
+							parameter.defaultValue = [v.x, v.y, v.z, v.w];
 							updateParam(parameter.id);
 						}
 						exec(false);
@@ -876,14 +862,10 @@ class ShaderEditor extends hide.view.FileView implements GraphInterface.IGraphEd
 					//setBoxesParam(parameter.id);
 					updateParam(parameter.id);
 				};
-				typeName = "Color";
 			case TVec(n, VFloat):
-				if (value == null)
-					value = [for (i in 0...n) 0.0];
-
-				shaderGraph.setParameterDefaultValue(parameter.id, value);
+				if (parameter.defaultValue == null)
+					parameter.defaultValue = [for (i in 0...n) 0.0];
 
-				//var row = new Element('<div class="flex"/>').appendTo(defaultValue);
 
 				var ranges : Array<hide.comp.Range> = [];
 
@@ -893,17 +875,9 @@ class ShaderEditor extends hide.view.FileView implements GraphInterface.IGraphEd
 					var parentRange = new Element('<input type="range" min="-1" max="1" />').appendTo(defaultValue);
 					var range = new hide.comp.Range(null, parentRange);
 					ranges.push(range);
-					range.value = value[i];
+					range.value = parameter.defaultValue[i];
 
 					var rangeInput = @:privateAccess range.f;
-					rangeInput.on("mousedown", function(e) {
-						elt.attr("draggable", "false");
-						//beforeChange();
-					});
-					rangeInput.on("mouseup", function(e) {
-						elt.attr("draggable", "true");
-						//afterChange();
-					});
 
 					range.onChange = function(move) {
 						if (saveValue == null) {
@@ -916,8 +890,11 @@ class ShaderEditor extends hide.view.FileView implements GraphInterface.IGraphEd
 							saveValue = null;
 							function exec(isUndo : Bool) {
 								var v = isUndo ? old : curr;
+								parameter.defaultValue = parameter.id;
 								shaderGraph.setParameterDefaultValue(parameter.id, v);
-								parametersUpdate[parameter.id](v);
+								for (i in 0 ... n)
+									ranges[i].value = v[i];
+
 								updateParam(parameter.id);
 							}
 							exec(false);
@@ -925,34 +902,19 @@ class ShaderEditor extends hide.view.FileView implements GraphInterface.IGraphEd
 							return;
 						}
 
-						value[i] = range.value;
-						if (!shaderGraph.setParameterDefaultValue(parameter.id, value))
-							return;
-						//setBoxesParam(parameter.id);
+						parameter.defaultValue[i] = range.value;
 						updateParam(parameter.id);
 					};
-					//if(min == null) min = isColor ? 0.0 : -1.0;
-					//if(max == null)	max = 1.0;
-					//e.attr("min", "" + min);
-					//e.attr("max", "" + max);
 				}
-				parametersUpdate.set(parameter.id, (v:Dynamic) -> {
-					for (i in 0...n) {
-						ranges[i].value = v[i];
-					}
-				});
-
-				typeName = "Vec" + n;
 			case TSampler(_):
 				var parentSampler = new Element('<input type="texturepath" field="sampler2d"/>').appendTo(defaultValue);
 
 				var tselect = new hide.comp.TextureChoice(null, parentSampler);
-				parametersUpdate.set(parameter.id, (v:Dynamic) -> tselect.value = v);
 				var saveValue : String = null;
-				tselect.value = value;
+				tselect.value = parameter.defaultValue;
 				tselect.onChange = function(notTmpChange: Bool) {
 					if (saveValue == null) {
-						saveValue = haxe.Json.stringify(shaderGraph.parametersAvailable.get(parameter.id).defaultValue);
+						saveValue = haxe.Json.stringify(parameter.defaultValue);
 					}
 					var currentValue = haxe.Json.parse(haxe.Json.stringify(tselect.value));
 					if (notTmpChange) {
@@ -961,8 +923,8 @@ class ShaderEditor extends hide.view.FileView implements GraphInterface.IGraphEd
 						var curr = currentValue;
 						function exec(isUndo: Bool) {
 							var v = !isUndo ? curr : prev;
-							shaderGraph.setParameterDefaultValue(parameter.id, v);
-							parametersUpdate[parameter.id](v);
+							parameter.defaultValue = v;
+							tselect.value = v;
 
 							if (!updateParam(parameter.id)) {
 								// If the graph was initialised without the variable,
@@ -982,19 +944,10 @@ class ShaderEditor extends hide.view.FileView implements GraphInterface.IGraphEd
 						requestRecompile();
 					}
 				}
-				typeName = "Texture";
 
 			default:
 		}
 
-		var header = new Element('<div class="header">
-									<i class="ico ico-angle-right" ></i>
-									<input class="input-title" type="input" value="${parameter.name}" />
-									<div class="type">
-										${typeName}
-									</div>
-								</div>');
-
 		var internal = new Element('<div><input type="checkbox" name="internal" id="internal"></input><label for="internal">Internal</label><div>').appendTo(content).find("#internal");
 		internal.prop("checked", parameter.internal ?? false);
 
@@ -1006,13 +959,6 @@ class ShaderEditor extends hide.view.FileView implements GraphInterface.IGraphEd
 
 		var perInstanceCb = new Element('<div><input type="checkbox" name="perinstance"/><label for="perinstance">Per instance</label><div>');
 		var shaderParams : Array<ShaderParam> = [];
-		// for (b in listOfBoxes) {
-		// 	var tmpShaderParam = Std.downcast(b.getInstance(), ShaderParam);
-		// 	if (tmpShaderParam != null && tmpShaderParam.parameterId == parameter.id) {
-		// 		shaderParams.push(tmpShaderParam);
-		// 		break;
-		// 	}
-		// }
 
 		var checkbox = perInstanceCb.find("input");
 		if (shaderParams.length > 0)
@@ -1027,137 +973,141 @@ class ShaderEditor extends hide.view.FileView implements GraphInterface.IGraphEd
 		});
 		perInstanceCb.appendTo(content);
 
-		header.appendTo(elt);
-		content.appendTo(elt);
-		var actionBtns = new Element('<div class="action-btns" ></div>').appendTo(content);
-		var deleteBtn = new Element('<input type="button" value="Delete" />');
-		deleteBtn.on("click", function() {
-			@:privateAccess
-			for (graph in shaderGraph.graphs) {
-				for (node in graph.getNodes()) {
-					var shaderParam = Std.downcast(node, ShaderParam);
-					if (shaderParam != null && shaderParam.parameterId == parameter.id) {
-						Ide.inst.quickError("This parameter is used in the graph.");
-						return;
-					}
-				}
-			}
+		return content;
 
-			function exec(isUndo : Bool) {
-				if (!isUndo) {
-					shaderGraph.parametersAvailable.remove(parameter.id);
-					shaderGraph.parametersKeys.remove(parameter.id);
-					parametersUpdate.remove(parameter.id);
-					shaderGraph.checkParameterIndex();
-					elt.remove();
-				} else {
-					shaderGraph.parametersAvailable.set(parameter.id, parameter);
-					shaderGraph.parametersKeys.insert(parameter.index, parameter.id);
-					shaderGraph.checkParameterIndex();
+	}
 
-					updateParam(parameter.id);
-					addParameter(parameter, parameter.defaultValue);
+	function reorderParameter(oldIndex: Int, newIndex: Int) {
+		var oldIndexes: Map<Int, Int> = [];
+		for (idx => param in shaderGraph.parametersAvailable) {
+			oldIndexes[idx] = param.index;
+		}
+
+		function exec(isUndo: Bool) {
+			if (!isUndo) {
+				for (param in shaderGraph.parametersAvailable) {
+					if (param.index == oldIndex) {
+						param.index = newIndex;
+					} else {
+						if (param.index >= oldIndex) {
+							param.index --;
+						}
 
-					for (id in shaderGraph.parametersKeys) {
-						var newElt = parametersList.find("#param_" + id);
-						parametersList.append(newElt);
+						if (param.index >= newIndex) {
+							param.index ++;
+						}
 					}
 				}
+			} else {
+				for (i => param in shaderGraph.parametersAvailable) {
+					param.index = oldIndexes[i];
+				}
 			}
-			exec(false);
-			undo.change(Custom(exec));
-		});
-		deleteBtn.appendTo(actionBtns);
-
+			parametersList.refresh();
+		}
+		exec(false);
+		undo.change(Custom(exec));
+	}
 
+	function removeParameter(orderIndex: Int) {
+		var param = null;
+		var index = null;
+		for (iterIndex => iterParam in shaderGraph.parametersAvailable) {
+			if (iterParam.index == orderIndex) {
+				param = iterParam;
+				index = iterIndex;
+			}
+		}
 
-		var inputTitle = elt.find(".input-title");
-		inputTitle.on("click", function(e) {
-			e.stopPropagation();
-		});
-		inputTitle.on("keydown", function(e) {
-			e.stopPropagation();
-		});
-		inputTitle.on("change", function(e) {
-			var newName = inputTitle.val();
-			if (!validNameCheck.match(newName)) {
-				ide.quickError('"$newName" is not a valid variable name (must start with _ or a letter, and only contains letters, numbers and underscores)');
-				inputTitle.val(parameter.name);
-				return;
+		var canBeDeleted = true;
+		for (graph in shaderGraph.graphs) {
+			for (node in graph.nodes) {
+				var param = Std.downcast(node, ShaderParam);
+				if (param == null)
+					continue;
+				if (param.parameterId == index) {
+					canBeDeleted = false;
+					break;
+				}
 			}
+			if (!canBeDeleted)
+				break;
+		}
 
-			var prevName = parameter.name;
-			var exec = function(isUndo : Bool) {
-				var v = !isUndo ? newName : prevName;
-				shaderGraph.setParameterTitle(parameter.id, v);
-				inputTitle.val(v);
+		if (!canBeDeleted) {
+			ide.quickError("Paramter " + param.name + " is used in the graph and can't be deleted");
+			return;
+		}
 
-				requestRecompile();
+		function exec(isUndo: Bool)  {
+			if (!isUndo) {
+				shaderGraph.parametersAvailable.remove(index);
 
-				for (id => node in currentGraph.getNodes()) {
-					if (Std.downcast(node, ShaderParam) != null) {
-						graphEditor.refreshBox(id);
+				for (param in shaderGraph.parametersAvailable) {
+					if (param.index > orderIndex) {
+						param.index --;
+					}
+				}
+			} else {
+				for (param in shaderGraph.parametersAvailable) {
+					if (param.index >= orderIndex) {
+						param.index ++;
 					}
 				}
-			}
 
-			exec(false);
-			undo.change(Custom(exec));
-			inputTitle.blur();
-
-
-			// if (shaderGraph.setParameterTitle(parameter.id, newName)) {
-			// 	for (b in listOfBoxes) {
-			// 		var shaderParam = Std.downcast(b.getInstance(), ShaderParam);
-			// 		if (shaderParam != null && shaderParam.parameterId == parameter.id) {
-			// 			beforeChange();
-			// 			shaderParam.setName(newName);
-			// 			afterChange();
-			// 		}
-			// 	}
-			// }
-		});
-		inputTitle.on("focus", function() { inputTitle.select(); } );
+				shaderGraph.parametersAvailable.set(index, param);
+			}
 
-		elt.find(".header").on("click", function() {
-			toggleParameter(elt);
-		});
+			parametersList.refresh();
+		}
 
-		elt.on("dragstart", function(e) {
-			draggedParamId = parameter.id;
-		});
+		exec(false);
+		undo.change(Custom(exec));
+	}
 
-		inline function isAfter(e) {
-			return e.clientY > (elt.offset().top + elt.outerHeight() / 2.0);
+	function createParameter(type : HxslType) {
+		@:privateAccess var paramShaderID : Int = shaderGraph.current_param_id++;
+		@:privateAccess
+		function exec(isUndo:Bool) {
+			if (!isUndo) {
+				var name = "Param_" + paramShaderID;
+				shaderGraph.parametersAvailable.set(paramShaderID, {id: paramShaderID, name : name, type : type, defaultValue : null, variable : shaderGraph.generateParameter(name, type), index : shaderGraph.parametersKeys.length});
+				shaderGraph.parametersKeys.push(paramShaderID);
+			} else {
+				shaderGraph.parametersAvailable.remove(paramShaderID);
+				shaderGraph.parametersKeys.remove(paramShaderID);
+				parametersUpdate.remove(paramShaderID);
+				shaderGraph.checkParameterIndex();
+			}
+			parametersList.refresh();
 		}
 
-		elt.on("dragover", function(e : js.jquery.Event) {
-			var after = isAfter(e);
-			elt.toggleClass("hovertop", !after);
-			elt.toggleClass("hoverbot", after);
-			e.preventDefault();
-		});
-
-		elt.on("dragleave", function(e) {
-			elt.toggleClass("hovertop", false);
-			elt.toggleClass("hoverbot", false);
-		});
+		exec(false);
+		undo.change(Custom(exec));
+		var paramShader = shaderGraph.getParameter(paramShaderID);
+		parametersList.editTitle(paramShader.index);
+	}
 
-		elt.on("dragenter", function(e) {
-			e.preventDefault();
-		});
+	// function moveParameter(parameter : Parameter, up : Bool) {
+	// 	var parameterElt = parametersList.find("#param_" + parameter.id);
+	// 	var parameterPrev = shaderGraph.parametersAvailable.get(shaderGraph.parametersKeys[shaderGraph.parametersKeys.indexOf(parameter.id) + (up? -1 : 1)]);
+	// 	execMoveParameterTo(parameter, parameterPrev, !up);
+	// }
 
-		elt.on("drop", function(e) {
-			elt.toggleClass("hovertop", false);
-			elt.toggleClass("hoverbot", false);
-			var other = shaderGraph.getParameter(draggedParamId);
-			var after = isAfter(e);
-			execMoveParameterTo(other, parameter, after);
-		});
+	function updateParam(id : Int) : Bool {
+		meshPreviewScene.setCurrent(); // needed for texture changes
 
-		return elt;
+		var param = shaderGraph.getParameter(id);
+		var init = compiledShader.inits.find((i) -> i.variable.name == param.name);
+		if (init != null) {
+			setParamValue(meshPreviewShader, init.variable, param.defaultValue);
+			return true;
+		}
+		return false;
 	}
 
+	var parametersUpdate : Map<Int, (Dynamic) -> Void> = [];
+
 	function toggleParameter( elt : JQuery, ?b : Bool) {
 		var icon = elt.find(".ico");
 		if (b == null) {
@@ -1171,45 +1121,6 @@ class ShaderEditor extends hide.view.FileView implements GraphInterface.IGraphEd
 		icon.toggleClass("fa-rotate-90", b);
 	}
 
-	function execMoveParameterTo(paramA: Parameter, paramB: Parameter, after: Bool) {
-		var oldIndex = paramA.index;
-		var newIndex = paramB.index;
-		var delta = newIndex - oldIndex;
-		if (delta == 0)
-			return;
-
-		if (after && delta < 0)
-			delta += 1;
-		if (!after && delta > 0)
-			delta -= 1;
-
-		function exec(isUndo: Bool) {
-			moveParameterOffset(paramA, isUndo ? -delta : delta);
-		}
-		exec(false);
-		undo.change(Custom(exec));
-	}
-
-	function moveParameterOffset(paramA: Parameter, offset: Int) {
-		var current = paramA.index;
-		var end = current + offset;
-		var dir = offset > 0 ? 1 : -1;
-		while(current != end) {
-			var next = current + dir;
-			var tmp = shaderGraph.parametersKeys[current];
-			shaderGraph.parametersKeys[current] = shaderGraph.parametersKeys[next];
-			shaderGraph.parametersKeys[next] = tmp;
-			current = next;
-		}
-
-		shaderGraph.checkParameterIndex();
-
-		for (id in shaderGraph.parametersKeys) {
-			var elt = parametersList.find("#param_" + id);
-			parametersList.append(elt);
-		}
-	}
-
 
 	public function loadSettings() {
 		var save = haxe.Json.parse(getDisplayState("previewSettings") ?? "{}");
@@ -1227,14 +1138,12 @@ class ShaderEditor extends hide.view.FileView implements GraphInterface.IGraphEd
 	}
 
 	public function revealParameter(id: Int) : Void {
-		var param = parametersList.find("#param_" + id);
-		parametersList.children().not(param).each((_, elt) -> toggleParameter(new JQuery(elt), false));
-		toggleParameter(param, true);
-		param.get(0).onanimationend = (e) -> {
-			param.removeClass("reveal");
-		};
-		param.removeClass("reveal");
-		param.addClass("reveal");
+		var param = shaderGraph.parametersAvailable[id];
+		parametersList.reveal(param.index);
+	}
+
+	public function revealVariable(id: Int) : Void {
+		variableList.reveal(id);
 	}
 
 	public function disposeMeshPreview() {

+ 15 - 0
hrt/shgraph/nodes/ShaderVar.hx

@@ -2,4 +2,19 @@ package hrt.shgraph.nodes;
 
 abstract class ShaderVar extends ShaderNode {
 	@prop() public var varId : Int = 0;
+
+	#if editor
+	override function getInfo():hide.view.GraphInterface.GraphNodeInfo {
+		var info = super.getInfo();
+
+		info.contextMenu = (e: js.html.MouseEvent) -> {
+			hide.comp.ContextMenu.createFromEvent(e, [
+				{label: "Show in Variable list", click: () -> {
+					(cast editor.editor: hide.view.shadereditor.ShaderEditor).revealVariable(varId);
+				}},
+			]);
+		}
+		return info;
+	}
+	#end
 }