Browse Source

started properties handling, use less for css

Nicolas Cannasse 8 years ago
parent
commit
9b935bc97e
8 changed files with 666 additions and 160 deletions
  1. 12 0
      bin/app.html
  2. 235 140
      bin/style.css
  3. 283 0
      bin/style.less
  4. 9 10
      hide.hxml
  5. 3 1
      hide.hxproj
  6. 92 0
      hide/comp/Properties.hx
  7. 8 8
      hide/comp/Toolbar.hx
  8. 24 1
      hide/view/Particles3D.hx

+ 12 - 0
bin/app.html

@@ -56,5 +56,17 @@
 	</menu>
 </xml>
 <script src="hide.js"></script>
+
+
+<script>
+  var path = './';
+  var fs = require('fs');
+
+  var reloadWatcher=fs.watch(path, function() {
+    location.reload();
+    reloadWatcher.close();
+  });
+</script>
+
 </body>
 </html>

+ 235 - 140
bin/style.css

@@ -1,170 +1,265 @@
 body {
-	margin : 0;
-	padding : 0;
-	background-color : black;
-}
-
-ul, li {
-	margin : 0;
-	padding : 0;
-	list-style : none;
-}
-
-body, td, th, li, p, a, input, select {
-	font-family : Verdana, serif;
-	font-size : 9pt;
-	color: #aaa;
-}
-
+  margin: 0;
+  padding: 0;
+  background-color: black;
+}
+ul,
+li {
+  margin: 0;
+  padding: 0;
+  list-style: none;
+}
+body,
+td,
+th,
+li,
+p,
+a,
+input,
+select {
+  font-family: Verdana, serif;
+  font-size: 9pt;
+  color: #aaa;
+}
+input {
+  padding-left: 4px;
+}
+.checkbox-wrapper {
+  display: inline-block;
+}
+input[type='checkbox'] {
+  -webkit-font-smoothing: antialiased;
+  text-rendering: optimizeSpeed;
+  width: 13px;
+  height: 13px;
+  margin: 0;
+  margin-right: 1px;
+  display: block;
+  float: left;
+  position: relative;
+  cursor: pointer;
+  top: 1px;
+}
+input[type='checkbox']:after {
+  content: "";
+  vertical-align: middle;
+  text-align: center;
+  line-height: 13px;
+  position: absolute;
+  cursor: pointer;
+  height: 13px;
+  width: 13px;
+  left: 0px;
+  top: 0px;
+  font-size: 10px;
+  box-shadow: inset 0px 1px 1px #000, 0px 1px 0px #444;
+  background: #202020;
+  color: #fff;
+}
+input[type='checkbox']:checked:after {
+  content: '\2714';
+}
 a {
-	text-decoration : none;
+  text-decoration: none;
+}
+#mainmenu {
+  display: none;
+}
+select,
+input {
+  background-color: #222222;
+  border: 1px solid #aaa;
+}
+select:focus,
+input:focus {
+  background-color: #141414;
 }
-
-#mainmenu { display : none }
-
 .hide-scrollzone {
-	width : 100%;
-	height : 100%;
-	overflow : auto;
+  width: 100%;
+  height: 100%;
+  overflow: auto;
 }
-
 .hide-scrollzone::-webkit-scrollbar {
-	width: 5px;
-	height: 5px;
+  width: 5px;
+  height: 5px;
 }
-
 .hide-scrollzone::-webkit-scrollbar-track {
-	background: #666;
+  background: #666;
 }
-
 .hide-scrollzone::-webkit-scrollbar-thumb {
-	background: #ddd;
+  background: #ddd;
 }
-
 .hide-loading {
-	width : 16px;
-	height : 16px;
-	display : inline-block;
-	background : url('libs/jstree/default-dark/throbber.gif');
+  width: 16px;
+  height: 16px;
+  display: inline-block;
+  background: url('libs/jstree/default-dark/throbber.gif');
 }
-
 .hide-scene {
-	cursor: default;
+  cursor: default;
 }
-
 .hide-scene-layer {
-	position: absolute;
-	z-index: 1;
-	height: 100%;
+  position: absolute;
+  z-index: 1;
+  height: 100%;
 }
-
 .hide-scene-layer .jstree {
-	background: transparent !important;
-	user-select: none;
+  background: transparent !important;
+  user-select: none;
 }
-
-.hide-toolbar-content {
-	flex-grow: 100;
-	position: relative;
+.hide-toolbar {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
 }
-
-.hide-toolbar-content > canvas {
-	position : absolute;
+.hide-toolbar > .content {
+  flex-grow: 100;
+  position: relative;
 }
-
-.hide-toolbar-container {
-	display: flex;
-	flex-direction: column;
-	height : 100%;
+.hide-toolbar > .content > canvas {
+  position: absolute;
 }
-
-.hide-toolbar {
-	width: 100%;
-	height : 24px;
-	padding : 4px;
-	padding-top: 2px;
-	padding-bottom: 6px;
-	background-color : rgb(34,34,34);
-	border-bottom : 1px solid #000;
-}
-
-.hide-toolbar-button, .hide-toolbar-toggle {
-	margin-right : 5px;
-	display: inline-block;
-	border: 1px solid rgb(90,90,90);
-	width : 20px;
-	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: top;
-}
-
-.hide-toolbar-button .hide-toolbar-icon {
-	vertical-align: center;
-}
-
-.hide-toolbar-button:hover, .hide-toolbar-toggle:hover {
-	color : white;
-	border-color : white;
-	background-color : #666;
-}
-
-.hide-toolbar-toggle.toggled {
-	color : #ddd;
-	background : #777;
-	padding-top: 2px;
-	padding-bottom: 1px;
-	text-shadow: none;
-	border-top-width: 2px;
-	border-top-color: #444;
-}
-
-.hide-toolbar-button:active {
-	padding-top: 3px;
-	padding-bottom: 1px;
-}
-
-.hide-toolbar-select {
-	height : 20px;
-	padding : 2px;
-	padding-top: 4px;
-	margin-right : 5px;
-	display : inline-block;
-}
-
-.hide-toolbar-select .hide-toolbar-icon {
-	font-size: 16px;
-	vertical-align: top;
-}
-
-.hide-toolbar-select > select {
-	margin-left : 5px;
-	min-width : 100px;
-}
-
-select {
-	background-color: rgb(34,34,34);
+.hide-toolbar > .toolbar {
+  width: 100%;
+  height: 24px;
+  padding: 4px;
+  padding-top: 2px;
+  padding-bottom: 6px;
+  background-color: #222222;
+  border-bottom: 1px solid #000;
+}
+.hide-toolbar .button,
+.hide-toolbar .toggle {
+  margin-right: 5px;
+  display: inline-block;
+  border: 1px solid #5a5a5a;
+  width: 20px;
+  height: 20px;
+  padding: 2px;
+  text-align: center;
+  border-radius: 1px;
+  text-shadow: 1px 2px 2px black;
+  background: -webkit-linear-gradient(top, #5a5a5a, #323232);
+  cursor: pointer;
+  font-size: 16px;
+  vertical-align: top;
+}
+.hide-toolbar .button > .icon,
+.hide-toolbar .toggle > .icon {
+  vertical-align: center;
+}
+.hide-toolbar .button:hover,
+.hide-toolbar .toggle:hover {
+  color: white;
+  border-color: white;
+  background-color: #666;
+}
+.hide-toolbar .toggle.toggled {
+  color: #ddd;
+  background: #777;
+  padding-top: 2px;
+  padding-bottom: 1px;
+  text-shadow: none;
+  border-top-width: 2px;
+  border-top-color: #444;
+}
+.hide-toolbar .button:active {
+  padding-top: 3px;
+  padding-bottom: 1px;
+}
+.hide-toolbar .select {
+  height: 20px;
+  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;
+}
+/* Properties */
+.hide-properties {
+  width: 100%;
+  height: 100%;
+  display: flex;
+}
+.hide-properties > .panel {
+  width: 250px;
+  padding: 10px;
+}
+.hide-properties > .content {
+  flex-grow: 100;
+}
+.hide-properties .section {
+  margin-bottom: 10px;
+}
+.hide-properties .section > h1 {
+  font-size: 12px;
+  margin: 0px;
+  cursor: pointer;
+  user-select: none;
+  width: 100%;
+  padding: 2px;
+  padding-bottom: 0px;
+  margin-bottom: 2px;
+}
+.hide-properties .section > h1:hover {
+  background-color: #333;
+}
+.hide-properties .section > h1:before {
+  content: "\25ba  ";
+}
+.hide-properties .section.open > h1:before {
+  content: "\25bc  ";
+}
+.hide-properties dl {
+  margin: 0px;
+  overflow: hidden;
+}
+.hide-properties dd,
+.hide-properties dt {
+  vertical-align: middle;
+  display: inline-block;
+  overflow: hidden;
+  margin-top: 4px;
+}
+.hide-properties dt {
+  width: 80px;
+  text-align: right;
+  color: #d8d8d8;
+}
+.hide-properties dd {
+  width: 160px;
+  margin-left: 10px;
+}
+.hide-properties dd input:not([type=checkbox]) {
+  width: 150px;
+}
+.hide-properties dd select {
+  width: 156px;
 }
-
 /* Golden Layout Fixes */
-
 .lm_header .lm_tabs {
-	z-index : 1;
+  z-index: 1;
 }
-
 .lm_dropTargetIndicator {
-	box-shadow : none;
+  box-shadow: none;
+}
+div.lm_close_tab {
+  top: 0px !important;
+  right: 0px !important;
+  width: 20px !important;
+  height: 18px !important;
+}
+div.lm_close_tab:hover {
+  background-color: #555 !important;
 }
-div.lm_close_tab { top : 0px !important; right : 0px !important; width : 20px !important; height : 18px !important; }
-div.lm_close_tab:hover { background-color : #555 !important; }
-
 /* JSTree fixes */
-
 .jstree-icon {
-	color : #888 !important;
-}
+  color: #888 !important;
+}

+ 283 - 0
bin/style.less

@@ -0,0 +1,283 @@
+body {
+	margin : 0;
+	padding : 0;
+	background-color : black;
+}
+
+ul, li {
+	margin : 0;
+	padding : 0;
+	list-style : none;
+}
+
+body, td, th, li, p, a, input, select {
+	font-family : Verdana, serif;
+	font-size : 9pt;
+	color: #aaa;
+}
+
+input {
+	padding-left : 4px;
+}
+
+.checkbox-wrapper {
+	display:inline-block;
+}
+
+input[type='checkbox'] {
+    -webkit-font-smoothing: antialiased;
+    text-rendering: optimizeSpeed;
+    width: 13px;
+    height: 13px;
+    margin: 0;
+    margin-right: 1px;
+    display: block;
+    float: left;
+    position: relative;
+    cursor:pointer;
+	top:1px;
+}
+input[type='checkbox']:after {
+    content:"";
+    vertical-align: middle;
+    text-align: center;
+    line-height: 13px;
+    position: absolute;
+    cursor: pointer;
+    height: 13px;
+    width: 13px;
+    left:0px;
+    top:0px;
+    font-size:10px;
+    box-shadow: inset 0px 1px 1px #000, 0px 1px 0px #444;
+    background: #202020;
+    color: #fff;
+}
+input[type='checkbox']:checked:after {
+    content:'\2714';
+}
+
+a {
+	text-decoration : none;
+}
+
+#mainmenu { display : none }
+
+select, input {
+	background-color: rgb(34,34,34);
+	border : 1px solid #aaa;
+	&:focus {
+		background-color: rgb(20,20,20);
+	}
+}
+
+.hide-scrollzone {
+	width : 100%;
+	height : 100%;
+	overflow : auto;
+}
+
+.hide-scrollzone::-webkit-scrollbar {
+	width: 5px;
+	height: 5px;
+}
+
+.hide-scrollzone::-webkit-scrollbar-track {
+	background: #666;
+}
+
+.hide-scrollzone::-webkit-scrollbar-thumb {
+	background: #ddd;
+}
+
+.hide-loading {
+	width : 16px;
+	height : 16px;
+	display : inline-block;
+	background : url('libs/jstree/default-dark/throbber.gif');
+}
+
+.hide-scene {
+	cursor: default;
+}
+
+.hide-scene-layer {
+	position: absolute;
+	z-index: 1;
+	height: 100%;
+
+	.jstree {
+		background: transparent !important;
+		user-select: none;
+	}
+}
+
+.hide-toolbar {
+
+	display: flex;
+	flex-direction: column;
+	height : 100%;
+
+	>.content {
+		flex-grow: 100;
+		position: relative;
+		>canvas {
+			position : absolute;
+		}
+	}
+
+	>.toolbar {
+		width: 100%;
+		height : 24px;
+		padding : 4px;
+		padding-top: 2px;
+		padding-bottom: 6px;
+		background-color : rgb(34,34,34);
+		border-bottom : 1px solid #000;
+	}
+
+
+	.button, .toggle {
+		margin-right : 5px;
+		display: inline-block;
+		border: 1px solid rgb(90,90,90);
+		width : 20px;
+		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: top;
+		>.icon {
+			vertical-align: center;
+		}
+		&:hover {
+			color : white;
+			border-color : white;
+			background-color : #666;
+		}
+	}
+
+	.toggle.toggled {
+		color : #ddd;
+		background : #777;
+		padding-top: 2px;
+		padding-bottom: 1px;
+		text-shadow: none;
+		border-top-width: 2px;
+		border-top-color: #444;
+	}
+
+	.button:active {
+		padding-top: 3px;
+		padding-bottom: 1px;
+	}
+
+	.select {
+		height : 20px;
+		padding : 2px;
+		padding-top: 4px;
+		margin-right : 5px;
+		display : inline-block;
+		.icon {
+			font-size: 16px;
+			vertical-align: top;
+		}
+		>select {
+			margin-left : 5px;
+			min-width : 100px;
+		}
+	}
+}
+
+/* Properties */
+
+.hide-properties {
+	width:100%;
+	height:100%;
+	display: flex;
+
+	>.panel {
+		width : 250px;
+		padding:10px;
+	}
+
+	>.content {
+		flex-grow:100;
+	}
+
+	.section {
+		margin-bottom : 10px;
+		>h1 {
+			font-size:12px;
+			margin: 0px;
+			cursor:pointer;
+			user-select:none;
+			width:100%;
+			padding : 2px;
+			padding-bottom: 0px;
+			margin-bottom: 2px;
+			&:hover {
+				background-color : #333;
+			}
+			&:before {
+			content:"\25ba  ";
+			}
+		}
+	}
+
+	.section.open > h1:before {
+		content:"\25bc  ";
+	}
+
+	dl {
+		margin:0px;
+		overflow:hidden;
+	}
+
+	dd, dt {
+		vertical-align:middle;
+		display:inline-block;
+		overflow:hidden;
+		margin-top:4px;
+	}
+
+	dt {
+		width:80px;
+		text-align:right;
+		color:#d8d8d8;
+	}
+
+	dd {
+		width:160px;
+		margin-left:10px;
+		input:not([type=checkbox]) {
+			width:150px;
+		}
+		select {
+			width:156px;
+		}
+	}
+
+}
+
+/* Golden Layout Fixes */
+
+.lm_header .lm_tabs {
+	z-index : 1;
+}
+
+.lm_dropTargetIndicator {
+	box-shadow : none;
+}
+div.lm_close_tab { top : 0px !important; right : 0px !important; width : 20px !important; height : 18px !important; }
+div.lm_close_tab:hover { background-color : #555 !important; }
+
+/* JSTree fixes */
+
+.jstree-icon {
+	color : #888 !important;
+}

+ 9 - 10
hide.hxml

@@ -1,10 +1,9 @@
--cp libs
--js bin/hide.js
--main hide.ui.Ide
--lib hxnodejs
--lib heaps
--lib hxbit
--dce full
---macro keep('h3d.prim')
---macro include('hide.view')
---macro include('h3d.prim')
+-cp libs
+-js bin/hide.js
+-main hide.ui.Ide
+-lib hxnodejs
+-lib heaps
+-lib hxbit
+-dce no
+--macro include('hide.view')
+--macro include('h3d.prim')

+ 3 - 1
hide.hxproj

@@ -24,7 +24,7 @@
     <option noInlineOnDebug="False" />
     <option mainClass="hide.ui.Ide" />
     <option enabledebug="False" />
-    <option additional="-lib hxnodejs&#xA;-lib heaps&#xA;-lib hxbit&#xA;-dce full&#xA;&#xA;--macro keep('h3d.prim')&#xA;--macro include('hide.view')&#xA;--macro include('h3d.prim')" />
+    <option additional="-lib hxnodejs&#xA;-lib heaps&#xA;-lib hxbit&#xA;-dce no&#xA;&#xA;--macro include('hide.view')&#xA;--macro include('h3d.prim')" />
   </build>
   <!-- haxelib libraries -->
   <haxelib>
@@ -45,6 +45,8 @@
     <hidden path="bin\nwsnapshot.exe" />
     <hidden path="bin\icudt.dll" />
     <hidden path="bin\credits.html" />
+    <hidden path="bin\style.css" />
+    <hidden path="bin\style.min.css" />
   </hiddenPaths>
   <!-- Executed before build -->
   <preBuildCommand />

+ 92 - 0
hide/comp/Properties.hx

@@ -0,0 +1,92 @@
+package hide.comp;
+
+class Properties extends Component {
+
+	public var panel : Element;
+	public var content : Element;
+
+	public function new(root) {
+		super(root);
+		var e = new Element("<div class='hide-properties'><div class='content'></div><div class='panel'></div></div>").appendTo(root);
+		content = e.find(".content");
+		panel = e.find(".panel");
+	}
+
+	public dynamic function beforeChange() {
+	}
+
+	public function add( e : Element, context : Dynamic ) {
+
+		e.appendTo(panel);
+		e = e.wrap("<div></div>").parent(); // necessary to have find working on top level element
+
+		e.find("input[type=checkbox]").wrap("<div class='checkbox-wrapper'></div>");
+
+		e.find("input[type=range]").not("[step]").attr("step", "any");
+
+		e.find(".section").not(".open").find(".content").hide();
+		e.find(".section > h1").mousedown(function(e) {
+			if( e.button != 0 ) return;
+			var section = js.jquery.Helper.JTHIS.parent();
+			section.toggleClass("open");
+			section.children(".content").toggle(100);
+		}).find("input").mousedown(function(e) e.stopPropagation());
+
+		for( f in e.find("[field]").elements() ) {
+			var fname = f.attr("field");
+			var current : Dynamic = Reflect.field(context, fname);
+			var enumValue : Enum<Dynamic> = null;
+			if( f.attr("type") == "checkbox" ) {
+				f.prop("checked", current);
+				f.change(function(_) {
+					beforeChange();
+					Reflect.setProperty(context, fname, f.prop("checked"));
+				});
+				continue;
+			}
+
+			if( f.is("select") ) {
+				enumValue = Type.getEnum(current);
+				if( enumValue != null && f.find("option").length == 0 ) {
+					for( c in enumValue.getConstructors() )
+						new Element('<option value="$c">$c</option>').appendTo(f);
+				}
+			}
+
+			if( f.is("[type=range]") )
+				f.on("input", function(_) f.change());
+
+			f.val(current);
+			f.keyup(function(e) {
+				if( e.keyCode == 13 ) {
+					f.blur();
+					return;
+				}
+				if( e.keyCode == 27 ) {
+					f.blur();
+					return;
+				}
+				f.change();
+			});
+			f.change(function(e) {
+
+				var newVal : Dynamic = f.val();
+
+				if( f.is("[type=range]") )
+					newVal = Std.parseFloat(newVal);
+
+				if( enumValue != null )
+					newVal = Type.createEnum(enumValue, newVal);
+
+				if( current == newVal ) return;
+
+				beforeChange();
+				trace(fname, newVal, Type.typeof(newVal));
+				current = newVal;
+				Reflect.setProperty(context, fname, newVal);
+			});
+		}
+
+	}
+
+}

+ 8 - 8
hide/comp/Toolbar.hx

@@ -18,27 +18,27 @@ class Toolbar extends Component {
 
 	public function new(root) {
 		super(root);
-		var e = new Element('<div class="hide-toolbar-container"><div class="hide-toolbar"/><div class="hide-toolbar-content"/>').appendTo(root);
-		bar = e.find('.hide-toolbar');
-		content = e.find(".hide-toolbar-content");
+		var e = new Element('<div class="hide-toolbar"><div class="toolbar"/><div class="content"/>').appendTo(root);
+		bar = e.find('.toolbar');
+		content = e.find(".content");
 	}
 
 	public function addButton( icon : String, ?label : String, ?onClick : Void -> Void ) {
-		var e = new Element('<div class="hide-toolbar-button" title="${label==null ? "" : label}"><div class="hide-toolbar-icon fa fa-$icon"/></div>');
+		var e = new Element('<div class="button" title="${label==null ? "" : label}"><div class="icon fa fa-$icon"/></div>');
 		if( onClick != null ) e.click(function(_) onClick());
 		e.appendTo(bar);
 		return e;
 	}
 
 	public function addToggle( icon : String, ?label : String, ?onToggle : Bool -> Void ) : ToolToggle {
-		var e = new Element('<div class="hide-toolbar-toggle" title="${label==null ? "" : label}"><div class="hide-toolbar-icon fa fa-$icon"/></div>');
+		var e = new Element('<div class="toggle" title="${label==null ? "" : label}"><div class="icon fa fa-$icon"/></div>');
 		e.click(function(_) { e.toggleClass("toggled"); if( onToggle != null ) onToggle(e.hasClass("toggled")); });
 		e.appendTo(bar);
 		return { element : e, toggle : function(b) e.toggleClass("toggled",b) };
 	}
 
 	public function addSelect<T>( icon : String, ?label : String ) : ToolSelect<T> {
-		var e = new Element('<div class="hide-toolbar-select" title="${label==null ? "" : label}"><div class="hide-toolbar-icon fa fa-$icon"/><select/></div>');
+		var e = new Element('<div class="select" title="${label==null ? "" : label}"><div class="icon fa fa-$icon"/><select/></div>');
 		var content : Array<{ label : String, value : T }> = [];
 		var select = e.find("select");
 		var tool : ToolSelect<T> = {
@@ -51,9 +51,9 @@ class Toolbar extends Component {
 			},
 			onSelect : function(_) {},
 		};
-		select.change(function(_) tool.onSelect(content[Std.parseInt(select.val())].value)); 
+		select.change(function(_) tool.onSelect(content[Std.parseInt(select.val())].value));
 		e.appendTo(bar);
-		return tool;		
+		return tool;
 	}
 
 }

+ 24 - 1
hide/view/Particles3D.hx

@@ -20,6 +20,7 @@ class Particles3D extends FileView {
 
 	var scene : hide.comp.Scene;
 	var parts : GpuParticles;
+	var properties : hide.comp.Properties;
 
 	override function getDefaultContent() {
 		var p = new h3d.parts.GpuParticles();
@@ -28,7 +29,8 @@ class Particles3D extends FileView {
 	}
 
 	override function onDisplay( e : Element ) {
-		scene = new hide.comp.Scene(e);
+		properties = new hide.comp.Properties(e);
+		scene = new hide.comp.Scene(properties.content);
 		scene.onReady = init;
 	}
 
@@ -36,6 +38,27 @@ class Particles3D extends FileView {
 		new h3d.scene.CameraController(scene.s3d).loadFromCamera();
 		parts = new GpuParticles(this,scene.s3d);
 		parts.load(haxe.Json.parse(sys.io.File.getContent(getPath())));
+
+		for( g in parts.getGroups() ) {
+			var e = new Element('
+				<div class="section open">
+					<h1><span>${g.name}</span> &nbsp;<input type="checkbox" field="enable"/></h1>
+					<dl class="content">
+						<dt>Name</dt><dd><input field="name" onchange="$(this).closest(\'.section\').find(\'>h1 span\').text($(this).val())"/></dd>
+						<dt>Mode</dt><dd><select field="emitMode"/></dd>
+						<dt>Count</dt><dd><input type="range" field="nparts" min="0" max="1000" step="1"/></dd>
+						<dt>Distance</dt><dd><input type="range" field="emitDist" min="0" max="10"/></dd>
+						<dt>Angle</dt><dd><input type="range" field="emitAngle" min="${-Math.PI/2}" max="${Math.PI}"/></dd>
+						<dt>Loop</dt><dd><input type="checkbox" field="emitLoop"/></dd>
+						<dt>Sync</dt><dd><input type="range" field="emitSync" min="0" max="1"/></dd>
+						<dt>Delay</dt><dd><input type="range" field="emitDelay" min="0" max="10"/></dd>
+						<dt>Transform3D</dt><dd><input type="checkbox" field="transform3D"/></dd>
+					</dl>
+				</div>
+			');
+			e.find("[field=emitLoop]").change(function(_) parts.currentTime = 0);
+			properties.add(e,g);
+		}
 	}
 
 	static var _ = FileTree.registerExtension(Particles3D, ["json.particles3D"], { icon : "snowflake-o", createNew: "Particle 3D" });