Browse Source

Scale values can now be edited all at once

Clement Espeute 2 years ago
parent
commit
e55bab30ce
7 changed files with 395 additions and 87 deletions
  1. 58 27
      bin/style.css
  2. 89 50
      bin/style.less
  3. 184 0
      hide/comp/MultiRange.hx
  4. 59 4
      hide/comp/PropsEditor.hx
  5. 3 1
      hide/comp/Range.hx
  6. 1 2
      hrt/prefab/Object2D.hx
  7. 1 3
      hrt/prefab/Object3D.hx

+ 58 - 27
bin/style.css

@@ -496,13 +496,29 @@ input[type=checkbox]:checked:after {
   padding-left: 5px;
   padding-right: 5px;
 }
-.hide-toolbar .button,
-.hide-toolbar .toggle,
-.scene-partition .show-cols-btn {
+.hide-toolbar .select {
+  padding: 2px;
+  padding-top: 4px;
+  margin-right: 5px;
   display: inline-block;
+}
+.hide-toolbar .select .icon {
+  font-size: 16px;
+  vertical-align: top;
+}
+.hide-toolbar .select > select {
+  margin-left: 5px;
+  min-width: 100px;
+}
+.button,
+.toggle {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
   user-select: none;
   border: 1px solid #5a5a5a;
   min-width: 20px;
+  min-height: 20px;
   padding: 2px;
   text-align: center;
   border-radius: 1px;
@@ -512,26 +528,25 @@ input[type=checkbox]:checked:after {
   font-size: 16px;
   vertical-align: middle;
 }
-.hide-toolbar .button:hover,
-.hide-toolbar .toggle:hover,
-.scene-partition .show-cols-btn:hover {
+.button:hover,
+.toggle:hover {
   color: white;
   border-color: white;
   background-color: #666;
 }
-.hide-toolbar .button label,
-.hide-toolbar .toggle label {
+.button label,
+.toggle label {
   font-size: 12px;
   margin-left: 6px;
   margin-right: 6px;
   text-shadow: none;
 }
-.hide-toolbar .button label.locked,
-.hide-toolbar .toggle label.locked {
+.button label.locked,
+.toggle label.locked {
   color: #000;
   font-style: italic;
 }
-.hide-toolbar .toggle.toggled {
+.toggle.toggled {
   color: #ddd;
   background: #777;
   padding-top: 2px;
@@ -540,29 +555,45 @@ input[type=checkbox]:checked:after {
   border-top-width: 2px;
   border-top-color: #444;
 }
-.hide-toolbar .toggle.toggled:hover {
+.toggle.toggled:hover {
   border-top-width: 1px;
   padding-top: 3px;
   border-top-color: white;
 }
-.hide-toolbar .button:active,
-.scene-partition .show-cols-btn:active {
-  padding-top: 3px;
-  padding-bottom: 1px;
+.link-container {
+  margin-top: 4px;
+  display: flex;
+  flex-direction: column;
+  justify-content: space-around;
 }
-.hide-toolbar .select {
-  padding: 2px;
-  padding-top: 4px;
-  margin-right: 5px;
-  display: inline-block;
+.link-container .link {
+  flex: 1 0;
+  align-self: center;
+  width: 33%;
+  margin-right: 33%;
+  height: 100%;
+  border-right: solid 1px gray;
 }
-.hide-toolbar .select .icon {
-  font-size: 16px;
-  vertical-align: top;
+.link-container .link-up {
+  margin-top: 4px;
+  margin-bottom: 2px;
+  border-top: solid 1px gray;
 }
-.hide-toolbar .select > select {
-  margin-left: 5px;
-  min-width: 100px;
+.link-container .link-down {
+  margin-top: 2px;
+  margin-bottom: 4px;
+  border-bottom: solid 1px gray;
+}
+.link-container .toggle {
+  min-width: 12px;
+  min-height: 12px;
+  width: 12px;
+  height: 12px;
+  font-size: 9px;
+}
+.button:active {
+  padding-top: 3px;
+  padding-bottom: 1px;
 }
 .prefab-toolbar .button,
 .prefab-toolbar .toggle {

+ 89 - 50
bin/style.less

@@ -565,56 +565,6 @@ input[type=checkbox] {
 		padding-right: 5px;
 	}
 
-	.button, .toggle {
-		display: inline-block;
-		user-select:none;
-		border: 1px solid rgb(90,90,90);
-		min-width : 20px;
-		padding : 2px;
-		text-align: center;
-		border-radius: 1px;
-		text-shadow: 1px 2px 2px black;
-		background: -webkit-linear-gradient(top, rgb(90,90,90), rgb(50,50,50));
-		cursor : pointer;
-		font-size: 16px;
-		vertical-align: middle;
-		&:hover {
-			color : white;
-			border-color : white;
-			background-color : #666;
-		}
-		label {
-			font-size: 12px;
-			margin-left: 6px;
-			margin-right: 6px;
-			text-shadow: none;
-		}
-		label.locked {
-			color: #000;
-			font-style: italic;
-		}
-	}
-
-	.toggle.toggled {
-		color : #ddd;
-		background : #777;
-		padding-top: 2px;
-		padding-bottom: 1px;
-		text-shadow: none;
-		border-top-width: 2px;
-		border-top-color: #444;
-		&:hover {
-			border-top-width: 1px;
-			padding-top: 3px;
-			border-top-color : white;
-		}
-	}
-
-	.button:active {
-		padding-top: 3px;
-		padding-bottom: 1px;
-	}
-
 	.select {
 		padding : 2px;
 		padding-top: 4px;
@@ -631,6 +581,95 @@ input[type=checkbox] {
 	}
 }
 
+.button, .toggle {
+	display: inline-flex;
+	align-items: center;
+	justify-content: center;
+	user-select:none;
+	border: 1px solid rgb(90,90,90);
+	min-width : 20px;
+	min-height : 20px;
+	padding : 2px;
+	text-align: center;
+	border-radius: 1px;
+	text-shadow: 1px 2px 2px black;
+	background: -webkit-linear-gradient(top, rgb(90,90,90), rgb(50,50,50));
+	cursor : pointer;
+	font-size: 16px;
+	vertical-align: middle;
+	&:hover {
+		color : white;
+		border-color : white;
+		background-color : #666;
+	}
+	label {
+		font-size: 12px;
+		margin-left: 6px;
+		margin-right: 6px;
+		text-shadow: none;
+	}
+	label.locked {
+		color: #000;
+		font-style: italic;
+	}
+}
+
+.toggle.toggled {
+	color : #ddd;
+	background : #777;
+	padding-top: 2px;
+	padding-bottom: 1px;
+	text-shadow: none;
+	border-top-width: 2px;
+	border-top-color: #444;
+	&:hover {
+		border-top-width: 1px;
+		padding-top: 3px;
+		border-top-color : white;
+	}
+}
+
+.link-container {
+	margin-top: 4px;
+	display: flex;
+	flex-direction: column;
+	justify-content: space-around;
+
+	.link {
+		flex: 1 0;
+		align-self: center;
+		width: 33%;
+		margin-right: 33%;
+		height: 100%;
+		border-right: solid 1px gray;
+	}
+
+	.link-up {
+		margin-top: 4px;
+		margin-bottom: 2px;
+		border-top: solid 1px gray;
+	}
+
+	.link-down {
+		margin-top: 2px;
+		margin-bottom: 4px;
+		border-bottom: solid 1px gray;
+	}
+
+	.toggle {
+		min-width: 12px;
+		min-height: 12px;
+		width: 12px;
+		height: 12px;
+		font-size: 9px;
+	}
+}
+
+.button:active {
+	padding-top: 3px;
+	padding-bottom: 1px;
+}
+
 .prefab-toolbar {
 	.button, .toggle {
 		margin-right : 5px;

+ 184 - 0
hide/comp/MultiRange.hx

@@ -0,0 +1,184 @@
+package hide.comp;
+
+class MultiRange extends Component {
+    public var value(get, set) : Array<Float>;
+
+    var current : Array<Float> = [];
+    var numValues : Int = 0;
+    var ranges : Array<Range> = [];
+    var isUniform : Bool = true;
+
+    var linkButton : Element;
+    var linkIcon : Element;
+
+    function set_value(v : Array<Float>) {
+        if (v.length != numValues) 
+            throw "assert";
+        current = v;
+        repaint();
+        return v;
+    }
+
+    function get_value() {
+        return current;
+    }
+
+    function repaint() {
+        for (i => range in ranges) {
+            range.value = current[i];
+        }
+        syncRanges();
+        linkButton.toggleClass("toggled", isUniform);
+        linkIcon.toggleClass("ico-link", isUniform);
+        linkIcon.toggleClass("ico-unlink", !isUniform);
+
+    }
+
+    function syncRanges() {
+        var biggestMin = Math.NEGATIVE_INFINITY;
+        var smallestMax = Math.POSITIVE_INFINITY;
+
+        for (range in ranges) {
+            biggestMin = Math.max(biggestMin, @:privateAccess range.curMin);
+            smallestMax = Math.min(smallestMax, @:privateAccess range.curMax);
+        }
+
+        for (range in ranges) {
+            range.element.attr("min", biggestMin);
+            range.element.attr("max", smallestMax);
+        }
+    }
+
+    public dynamic function onChange(tempChange : Bool) {
+
+    }
+
+    function change(tempChange : Bool) {
+        onChange(tempChange);
+    }
+
+	public function new(?parent:Element,?root:Element, num : Int, labels : Array<String>) {
+        this.numValues = num;
+        super(null, null);
+
+        var flex = new Element("<div>").css("position", "relative").css("width", "110%").appendTo(parent);
+        flex.css("display", "flex");
+
+
+        var rows = new Element("<div>").css("flex", "1 0").appendTo(flex);
+
+        var min = root.attr("min");
+        if (min == null) min = "0";
+        var max = root.attr("max");
+        if (max == null) max = "5";
+
+        for (i in 0...numValues) {
+            var row = new Element("<div>").appendTo(rows);
+            var dt = new Element("<dt>").text(labels[i]).appendTo(row);
+
+            var dd = new Element("<dd>").appendTo(row);
+            var range = new Range(dd, new Element('<input type="range" min="$min" max="$max">'));
+
+            PropsEditor.wrapDt(dt, "1", function(e) {
+                if (e.ctrlKey) {
+                    range.value = Math.round(range.value);
+                    range.onChange(false);
+                } else {
+                    range.value = 1;
+                    range.onChange(false);
+                }
+            });
+
+            range.onChange = function(tempChange:Bool) {
+                if (isUniform) {
+                    var scale =  range.value / current[i];
+
+                    for (j in 0...numValues) {
+                        if (!Math.isFinite(scale)) {
+                            current[j] = range.value;
+                        }
+                        else {
+                            current[j] = Math.fround(current[j] * scale * 100.0) / 100.0;
+                        }
+                    }
+
+                }
+                else {
+                    current[i] = range.value;
+                }
+
+                repaint();
+                onChange(tempChange);
+            };
+            ranges.push(range);
+        }
+
+        var linkContainer = new Element("<div class='link-container'>").css("flex-shirnk", "1").css("left","-32px").css("position", "relative").appendTo(flex);
+        linkContainer.append(new Element("<div class='link link-up'>"));
+        linkButton = new Element("<div class='toggle' title='Link/Unlink sliders. Right click to open the context menu'>").appendTo(linkContainer);
+        linkIcon = new Element("<div class='icon ico ico-link'>").appendTo(linkButton);
+        linkContainer.append(new Element("<div class='link link-down'>"));
+
+        linkButton.click(function(e) {
+            isUniform = !isUniform;
+            repaint();
+        });
+
+        linkButton.contextmenu(function(e) {
+            e.preventDefault();
+            new ContextMenu([
+				{ label : "Reset All", click : reset },
+				{ label : "Round All", click : round },
+				{ label : "sep", isSeparator: true},
+				{ label : "Copy", click : copy},
+				{ label : "Paste", click: paste, enabled : canPaste()},
+				{ label : "sep", isSeparator: true},
+				{ label : "Cancel", click : function() {} },
+			]);
+            return false;
+        });
+
+        root.remove();
+
+        repaint();
+    }
+
+    function round() {
+        for (i => v in current) {
+            current[i] = Math.fround(v);
+        }
+        repaint();
+        change(false);
+    }
+
+    function reset() {
+        value = [for (i in 0...numValues) 1.0];
+        change(false);
+    }
+
+    function copy() {
+        ide.setClipboard(current.join(","));
+    }
+
+    function paste() {
+        var vals = getPasteValues();
+        if (vals != null) {
+            value = vals;
+            change(false);
+        }
+    }
+
+    function getPasteValues() : Null<Array<Float>> {
+        var arr = ide.getClipboard().split(",");
+        if (arr.length == numValues) {
+            var arrFloat = arr.map((s) -> Std.parseFloat(s));
+            if (!arrFloat.contains(Math.NaN))
+                return arrFloat;
+        }
+        return null;
+    }
+
+    function canPaste() {
+        return getPasteValues() != null;
+    }
+}

+ 59 - 4
hide/comp/PropsEditor.hx

@@ -239,10 +239,7 @@ class PropsEditor extends Component {
 			var def = f.element.attr("value");
 			if(def != null) {
 				var dd = f.element.parent().parent("dd");
-				var dt = dd.prev("dt");
-				var tooltip = 'Click to reset ($def)\nCtrl+Click to round';
-				var button = dt.wrapInner('<input type="button" tabindex="-1" value="${upperCase(dt.text())}" title="$tooltip"/>');
-				button.click(function(e) {
+				wrapDt(dd.prev("dt"), def, function(e) {
 					var range = @:privateAccess f.range;
 					if(range != null) {
 						if(e.ctrlKey) {
@@ -258,6 +255,12 @@ class PropsEditor extends Component {
 		return e;
 	}
 
+	public static function wrapDt(dt : Element, defValue : String, onClick : (e : js.jquery.Event) -> Void) {
+		var tooltip = 'Click to reset ($defValue)\nCtrl+Click to round';
+		var button = dt.wrapInner('<input type="button" tabindex="-1" value="${upperCase(dt.text())}" title="$tooltip"/>');
+		button.click(onClick);
+	}
+
 }
 
 
@@ -275,6 +278,7 @@ class PropsField extends Component {
 	var beforeTempChange : { value : Dynamic };
 	var tchoice : hide.comp.TextureChoice;
 	var gradient : GradientBox;
+	var multiRange : MultiRange;
 	var tselect : hide.comp.TextureSelect;
 	var fselect : hide.comp.FileSelect;
 	var viewRoot : Element;
@@ -446,6 +450,57 @@ class PropsField extends Component {
 				setVal(range.value);
 			};
 			return;
+		case "multi-range":
+			var subfieldStr = f.attr("data-subfields");
+			var subfields = subfieldStr.split(",");
+
+			var name = f.parent().prev("dt").text();
+			var parentDiv = f.parent().parent();
+			parentDiv.empty();
+
+			var multiRange = new hide.comp.MultiRange(parentDiv, f, subfields.length, [for (subfield in subfields) name + " " + subfield]);
+			var a = getAccess();
+			multiRange.value = [for (subfield in subfields) Reflect.getProperty(a.obj, a.name+subfield)];
+			current = multiRange.value;
+			currentSave = (cast current : Array<Float>).copy();
+			multiRange.onChange = function(isTemporary : Bool) {
+				var setVal = function(val : Array<Float>, undo, refreshComp) {
+					var f = resolveField();
+					var a = f.getAccess();
+					f.current = val;
+					f.currentSave = val.copy();
+					for (i => subfield in subfields)
+						Reflect.setProperty(a.obj, a.name+subfield, val[i]);
+					if (refreshComp)
+						multiRange.value = val;
+					f.onChange(undo);
+				};
+
+				if (!isTemporary) {
+					var arr : Array<Float> = cast currentSave;
+					var oldVal = arr.copy();
+					var newVal = multiRange.value.copy();
+
+					props.undo.change(Custom(function(undo) {
+						if (undo) {
+							trace("Undo", oldVal, newVal);
+							setVal(oldVal, true, true);
+						} else {
+							trace("Redo", newVal);
+							setVal(newVal, false, true);
+						}
+					}));
+					setVal(multiRange.value, false, false);
+				}
+				else {
+					var a = getAccess();
+					var val = multiRange.value;
+					current = val;
+					for (i => subfield in subfields)
+						Reflect.setProperty(a.obj, a.name+subfield, val[i]);
+					onChange(false);
+				}
+			};
 		case "color":
 			var arr = Std.downcast(current, Array);
 			var alpha = arr != null && arr.length == 4 || f.attr("alpha") == "true";

+ 3 - 1
hide/comp/Range.hx

@@ -56,7 +56,9 @@ class Range extends Component {
 			return false;
 		}
 
-		root.parent().prev("dt").contextmenu(contextMenu);
+		var dt = root.parent().prev("dt");
+
+		dt.contextmenu(contextMenu);
 
 		element.contextmenu(contextMenu);
 

+ 1 - 2
hrt/prefab/Object2D.hx

@@ -100,8 +100,7 @@ class Object2D extends Prefab {
 				<dl>
 					<dt>X</dt><dd><input type="range" min="-100" max="100" value="0" field="x"/></dd>
 					<dt>Y</dt><dd><input type="range" min="-100" max="100" value="0" field="y"/></dd>
-					<dt>Scale X</dt><dd><input type="range" min="0" max="5" value="1" field="scaleX"/></dd>
-					<dt>Scale Y</dt><dd><input type="range" min="0" max="5" value="1" field="scaleY"/></dd>
+					<dt>Scale</dt><dd><input type="multi-range" min="0" max="5" value="0" field="scale" data-subfields="X,Y"/></dd>
 					<dt>Rotation</dt><dd><input type="range" min="-180" max="180" value="0" field="rotation" /></dd>
 				</dl>
 			</div>

+ 1 - 3
hrt/prefab/Object3D.hx

@@ -315,9 +315,7 @@ class Object3D extends Prefab {
 					<dt>X</dt><dd><input type="range" min="-10" max="10" value="0" field="x"/></dd>
 					<dt>Y</dt><dd><input type="range" min="-10" max="10" value="0" field="y"/></dd>
 					<dt>Z</dt><dd><input type="range" min="-10" max="10" value="0" field="z"/></dd>
-					<dt>Scale X</dt><dd><input type="range" min="0" max="5" value="1" field="scaleX"/></dd>
-					<dt>Scale Y</dt><dd><input type="range" min="0" max="5" value="1" field="scaleY"/></dd>
-					<dt>Scale Z</dt><dd><input type="range" min="0" max="5" value="1" field="scaleZ"/></dd>
+					<dt>Scale</dt><dd><input type="multi-range" min="0" max="5" value="0" field="scale" data-subfields="X,Y,Z"/></dd>
 					<dt>Rotation X</dt><dd><input type="range" min="-180" max="180" value="0" field="rotationX" /></dd>
 					<dt>Rotation Y</dt><dd><input type="range" min="-180" max="180" value="0" field="rotationY" /></dd>
 					<dt>Rotation Z</dt><dd><input type="range" min="-180" max="180" value="0" field="rotationZ" /></dd>