Browse Source

review property files handling (one single inheritance chain)

Nicolas Cannasse 8 years ago
parent
commit
b297e62227
11 changed files with 265 additions and 198 deletions
  1. 1 0
      .gitignore
  2. 2 1
      bin/app.html
  3. 12 0
      bin/defaultProps.json
  4. 150 0
      hide/comp/Props.hx
  5. 1 1
      hide/comp/PropsEditor.hx
  6. 2 2
      hide/comp/Scene.hx
  7. 14 4
      hide/comp/UndoHistory.hx
  8. 60 42
      hide/ui/Ide.hx
  9. 0 107
      hide/ui/Props.hx
  10. 15 37
      hide/view/FileView.hx
  11. 8 4
      hide/view/Particles3D.hx

+ 1 - 0
.gitignore

@@ -1,3 +1,4 @@
 /bin/nwjs
 /bin/style.min.css
 /dump
+/bin/props.json

+ 2 - 1
bin/app.html

@@ -62,7 +62,8 @@
   var path = './';
   var fs = require('fs');
 
-  var reloadWatcher=fs.watch(path, function() {
+  var reloadWatcher=fs.watch(path, function(_,file) {
+	if( file.split(".").pop().toLowerCase() == "json" ) return;
 	setTimeout(function() {
 		location.reload();
 		reloadWatcher.close();

+ 12 - 0
bin/defaultProps.json

@@ -0,0 +1,12 @@
+{
+	// define location of animations in HMD files
+	"hmd.animPaths" : [],
+
+	// controls
+	"key.undo" : "Ctrl-Z",
+	"key.redo" : "Ctrl-Y",
+
+	// reserved for internal ide usage
+	"hide" : {}
+
+}

+ 150 - 0
hide/comp/Props.hx

@@ -0,0 +1,150 @@
+package hide.comp;
+
+typedef HideProps = {
+	var autoSaveLayout : Null<Bool>;
+	var layouts : Array<{ name : String, state : Dynamic }>;
+
+	var currentProject : String;
+	var recentProjects : Array<String>;
+
+	var windowPos : { x : Int, y : Int, w : Int, h : Int, max : Bool };
+};
+
+typedef PropsDef = {
+
+	var hide : HideProps;
+
+};
+
+class Props {
+
+	var ide : hide.ui.Ide;
+	var parent : Props;
+	public var path(default,null) : String;
+	public var source(default, null) : PropsDef;
+	public var current : PropsDef;
+
+	public function new( ?parent : Props ) {
+		ide = hide.ui.Ide.inst;
+		this.parent = parent;
+		sync();
+	}
+
+	public function load( path : String ) {
+		this.path = path;
+		var fullPath = ide.getPath(path);
+		if( sys.FileSystem.exists(fullPath) )
+			source = ide.parseJSON(sys.io.File.getContent(fullPath))
+		else
+			source = cast {};
+		sync();
+	}
+
+	public function save() {
+		sync();
+		if( path == null ) throw "Cannot save properties (unknown path)";
+		var fullPath = ide.getPath(path);
+		if( Reflect.fields(source).length == 0 )
+			try sys.FileSystem.deleteFile(fullPath) catch( e : Dynamic ) {};
+		else
+			sys.io.File.saveContent(fullPath, ide.toJSON(source));
+	}
+
+	public function sync() {
+		if( parent != null ) parent.sync();
+		current = cast {};
+		if( parent != null ) merge(parent.current);
+		if( source != null ) merge(source);
+	}
+
+	function merge( value : Dynamic ) {
+		mergeRec(current, value);
+	}
+
+	function mergeRec( dst : Dynamic, src : Dynamic ) {
+		for( f in Reflect.fields(src) ) {
+			var v : Dynamic = Reflect.field(src,f);
+			var t : Dynamic = Reflect.field(dst, f);
+			if( Type.typeof(v) == TObject ) {
+				if( t == null ) {
+					t = {};
+					Reflect.setField(dst, f, t);
+				}
+				mergeRec(t, v);
+			} else if( v == null )
+				Reflect.deleteField(dst, f);
+			else
+				Reflect.setField(dst,f,v);
+		}
+	}
+
+	public function get( key : String ) : Dynamic {
+		return Reflect.field(current,key);
+	}
+
+	public static function loadForProject( resourcePath : String ) {
+		var path = js.Node.process.argv[0].split("\\").join("/").split("/");
+		path.pop();
+		var hidePath = path.join("/");
+
+		// in dev mode
+		if( !sys.FileSystem.exists(hidePath + "/package.json") ) {
+			var prevPath = new haxe.io.Path(hidePath).dir;
+			if( sys.FileSystem.exists(prevPath + "/hide.js") )
+				hidePath = prevPath;
+		}
+
+		var defaults = new Props();
+		defaults.load(hidePath + "/defaultProps.json");
+
+		var userGlobals = new Props(defaults);
+		userGlobals.load(hidePath + "/props.json");
+
+		if( userGlobals.source.hide == null )
+			userGlobals.source.hide = {
+				autoSaveLayout : true,
+				layouts : [],
+				recentProjects : [],
+				currentProject : resourcePath,
+				windowPos : null,
+			};
+
+		var perProject = new Props(userGlobals);
+		perProject.load(resourcePath + "/props.json");
+
+		var projectUserCustom = new Props(perProject);
+		projectUserCustom.load(hidePath + "/" + resourcePath.split("/").join("_").split(":").join("_") + "_Defaults.json");
+
+		var current = new Props(projectUserCustom);
+
+		return {
+			global : userGlobals,
+			project : perProject,
+			user : projectUserCustom,
+			current : current,
+		};
+	}
+
+	public static function loadForFile( ide : hide.ui.Ide, path : String ) {
+		var parts = path.split("/");
+		var propFiles = [];
+		var first = true, allowSave = false;
+		while( true ) {
+			var pfile = ide.getPath(parts.join("/") + "/props.json");
+			if( sys.FileSystem.exists(pfile) ) {
+				propFiles.unshift(pfile);
+				if( first ) allowSave = true;
+			}
+			if( parts.length == 0 ) break;
+			first = false;
+			parts.pop();
+		}
+		var parent = ide.props.user;
+		for( p in propFiles ) {
+			parent = new hide.comp.Props(parent);
+			parent.load(p);
+		}
+		return allowSave ? parent : new hide.comp.Props(parent);
+	}
+
+}

+ 1 - 1
hide/comp/Properties.hx → hide/comp/PropsEditor.hx

@@ -1,6 +1,6 @@
 package hide.comp;
 
-class Properties extends Component {
+class PropsEditor extends Component {
 
 	public var panel : Element;
 	public var content : Element;

+ 2 - 2
hide/comp/Scene.hx

@@ -201,13 +201,13 @@ class Scene extends Component implements h3d.IDrawable {
 		return hxd.res.Any.fromBytes(path,data).toModel().toHmd();
 	}
 
-	public function resetCamera( ?obj : h3d.scene.Object ) {
+	public function resetCamera( ?obj : h3d.scene.Object, distanceFactor = 1. ) {
 		if( obj == null ) obj = s3d;
 		var b = obj.getBounds();
 		var dx = Math.max(Math.abs(b.xMax),Math.abs(b.xMin));
 		var dy = Math.max(Math.abs(b.yMax),Math.abs(b.yMin));
 		var dz = Math.max(Math.abs(b.zMax),Math.abs(b.zMin));
-		var dist = Math.max(Math.max(dx * 6, dy * 6), dz * 4);
+		var dist = Math.max(Math.max(dx * 6, dy * 6), dz * 4) * distanceFactor;
 		var ang = Math.PI / 4;
 		var zang = Math.PI * 0.4;
 		s3d.camera.pos.set(Math.sin(zang) * Math.cos(ang) * dist, Math.sin(zang) * Math.sin(ang) * dist, Math.cos(zang) * dist);

+ 14 - 4
hide/comp/UndoHistory.hx

@@ -4,20 +4,26 @@ enum HistoryElement {
 	Field( obj : Dynamic, field : String, oldValue : Dynamic );
 }
 
-private typedef Elt = { h : HistoryElement, callb : Void -> Void };
+private typedef Elt = { h : HistoryElement, id : Int, callb : Void -> Void };
 
 class UndoHistory {
 
+	var uidGen = 0;
 	var undoElts : Array<Elt> = [];
 	var redoElts : Array<Elt> = [];
+	public var currentID(get, never) : Int;
 
 	public function new() {
 	}
 
+	function get_currentID() {
+		return undoElts.length == 0 ? 0 : undoElts[undoElts.length - 1].id;
+	}
+
 	public function change(h, ?callb) {
-		undoElts.push({ h : h, callb : callb });
+		undoElts.push({ h : h, id : ++uidGen, callb : callb });
 		redoElts = [];
-		trace(undoElts.length);
+		onChange();
 	}
 
 	public function undo() {
@@ -35,11 +41,15 @@ class UndoHistory {
 		switch( e.h ) {
 		case Field(obj, field, value):
 			var curValue = Reflect.field(obj, field);
-			other.push({ h : Field(obj, field, curValue), callb : e.callb });
+			other.push({ h : Field(obj, field, curValue), id : e.id, callb : e.callb });
 			Reflect.setField(obj, field, value);
 		}
 		if( e.callb != null ) e.callb();
+		onChange();
 		return true;
 	}
 
+	public dynamic function onChange() {
+	}
+
 }

+ 60 - 42
hide/ui/Ide.hx

@@ -1,8 +1,15 @@
 package hide.ui;
+import hide.comp.Props;
 
 class Ide {
 
-	public var props : Props;
+	public var props : {
+		global : Props,
+		project : Props,
+		user : Props,
+		current : Props,
+	};
+	public var ideProps(get,never) : HideProps;
 	public var projectDir(get,never) : String;
 	public var resourceDir(get,never) : String;
 	public var initializing(default,null) : Bool;
@@ -16,7 +23,6 @@ class Ide {
 	var types : Map<String,hide.HType>;
 	var typeDef = Macros.makeTypeDef(hide.HType);
 
-	var menu : Element;
 	var currentLayout : { name : String, state : Dynamic };
 	var maximized : Bool;
 	var updates : Array<Void->Void> = [];
@@ -25,9 +31,9 @@ class Ide {
 	function new() {
 		inst = this;
 		window = nw.Window.get();
-		props = new Props(Sys.getCwd());
+		props = Props.loadForProject(Sys.getCwd());
 
-		var wp = props.global.windowPos;
+		var wp = props.global.current.hide.windowPos;
 		if( wp != null ) {
 			window.resizeBy(wp.w - Std.int(window.window.outerWidth), wp.h - Std.int(window.window.outerHeight));
 			window.moveTo(wp.x, wp.y);
@@ -35,7 +41,7 @@ class Ide {
 		}
 		window.show(true);
 
-		setProject(props.global.currentProject);
+		setProject(ideProps.currentProject);
 		window.window.document.addEventListener("mousemove", function(e) {
 			mouseX = e.x;
 			mouseY = e.y;
@@ -56,15 +62,15 @@ class Ide {
 	}
 
 	function onWindowChange() {
-		if( props.global.windowPos == null ) props.global.windowPos = { x : 0, y : 0, w : 0, h : 0, max : false };
-		props.global.windowPos.max = maximized;
+		if( ideProps.windowPos == null ) ideProps.windowPos = { x : 0, y : 0, w : 0, h : 0, max : false };
+		ideProps.windowPos.max = maximized;
 		if( !maximized ) {
-			props.global.windowPos.x = window.x;
-			props.global.windowPos.y = window.y;
-			props.global.windowPos.w = Std.int(window.window.outerWidth);
-			props.global.windowPos.h = Std.int(window.window.outerHeight);
+			ideProps.windowPos.x = window.x;
+			ideProps.windowPos.y = window.y;
+			ideProps.windowPos.w = Std.int(window.window.outerWidth);
+			ideProps.windowPos.h = Std.int(window.window.outerHeight);
 		}
-		props.saveGlobals();
+		props.global.save();
 	}
 
 	function initLayout( ?state : { name : String, state : Dynamic } ) {
@@ -77,16 +83,16 @@ class Ide {
 		}
 
 		var defaultLayout = null;
-		for( p in props.current.layouts )
+		for( p in props.current.current.hide.layouts )
 			if( p.name == "Default" ) {
 				defaultLayout = p;
 				break;
 			}
 		if( defaultLayout == null ) {
 			defaultLayout = { name : "Default", state : [] };
-			if( props.local.layouts == null ) props.local.layouts = [];
-			props.local.layouts.push(defaultLayout);
-			props.save();
+			ideProps.layouts.push(defaultLayout);
+			props.current.sync();
+			props.global.save();
 		}
 		if( state == null )
 			state = defaultLayout;
@@ -119,10 +125,10 @@ class Ide {
 
 		layout.init();
 		layout.on('stateChanged', function() {
-			if( !props.current.autoSaveLayout )
+			if( !ideProps.autoSaveLayout )
 				return;
 			defaultLayout.state = saveLayout();
-			props.save();
+			props.global.save();
 		});
 
 		// error recovery if invalid component
@@ -145,6 +151,8 @@ class Ide {
 		return layout.toConfig().content;
 	}
 
+	function get_ideProps() return props.global.source.hide;
+
 	public function registerUpdate( updateFun ) {
 		updates.push(updateFun);
 	}
@@ -164,20 +172,19 @@ class Ide {
 		return resourceDir+"/"+relPath;
 	}
 
-	function get_projectDir() return props.global.currentProject.split("\\").join("/");
+	function get_projectDir() return ideProps.currentProject.split("\\").join("/");
 	function get_resourceDir() return projectDir+"/res";
 
 	function setProject( dir : String ) {
-		if( dir != props.global.currentProject ) {
-			props.global.currentProject = dir;
-			if( props.global.recentProjects == null ) props.global.recentProjects = [];
-			props.global.recentProjects.remove(dir);
-			props.global.recentProjects.unshift(dir);
-			if( props.global.recentProjects.length > 10 ) props.global.recentProjects.pop();
-			props.save();
+		if( dir != ideProps.currentProject ) {
+			ideProps.currentProject = dir;
+			ideProps.recentProjects.remove(dir);
+			ideProps.recentProjects.unshift(dir);
+			if( ideProps.recentProjects.length > 10 ) ideProps.recentProjects.pop();
+			props.global.save();
 		}
 		window.title = "HIDE - " + dir;
-		props = new Props(dir);
+		props = Props.loadForProject(resourceDir);
 		initMenu();
 		initLayout();
 	}
@@ -207,17 +214,26 @@ class Ide {
 		}).appendTo(window.window.document.body).click();
 	}
 
+	public function parseJSON( str : String ) {
+		// remove comments
+		str = ~/^[ \t]+\/\/[^\n]*/gm.replace(str, "");
+		return haxe.Json.parse(str);
+	}
+
+	public function toJSON( v : Dynamic ) {
+		return haxe.Json.stringify(v, "\t");
+	}
+
 	function initMenu() {
-		if( menu == null )
-			menu = new Element(new Element("#mainmenu").get(0).outerHTML);
+		var menu = new Element(new Element("#mainmenu").get(0).outerHTML);
 
 		// project
-		if( props.current.recentProjects.length > 0 )
+		if( ideProps.recentProjects.length > 0 )
 			menu.find(".project .recents").html("");
-		for( v in props.current.recentProjects.copy() ) {
+		for( v in ideProps.recentProjects.copy() ) {
 			if( !sys.FileSystem.exists(v) ) {
-				props.current.recentProjects.remove(v);
-				props.save();
+				ideProps.recentProjects.remove(v);
+				props.global.save();
 				continue;
 			}
 			new Element("<menu>").attr("label",v).appendTo(menu.find(".project .recents")).click(function(_){
@@ -232,8 +248,8 @@ class Ide {
 			});
 		});
 		menu.find(".project .clear").click(function(_) {
-			props.global.recentProjects = [];
-			props.save();
+			ideProps.recentProjects = [];
+			props.global.save();
 			initMenu();
 		});
 		menu.find(".project .exit").click(function(_) {
@@ -241,6 +257,8 @@ class Ide {
 		});
 
 		// view
+		if( !sys.FileSystem.exists(resourceDir) )
+			menu.find(".view").remove();
 		menu.find(".debug").click(function(_) window.showDevTools());
 		var comps = menu.find("[component]");
 		for( c in comps.elements() ) {
@@ -257,27 +275,27 @@ class Ide {
 		// layout
 		var layouts = menu.find(".layout .content");
 		layouts.html("");
-		for( l in props.current.layouts ) {
+		for( l in props.current.current.hide.layouts ) {
 			if( l.name == "Default" ) continue;
 			new Element("<menu>").attr("label",l.name).addClass(l.name).appendTo(layouts).click(function(_) {
 				initLayout(l);
 			});
 		}
 		menu.find(".layout .autosave").click(function(_) {
-			props.local.autoSaveLayout = !props.local.autoSaveLayout;
-			props.save();
-		}).attr("checked",props.local.autoSaveLayout?"checked":"");
+			ideProps.autoSaveLayout = !ideProps.autoSaveLayout;
+			props.global.save();
+		}).prop("checked",ideProps.autoSaveLayout);
 
 		menu.find(".layout .saveas").click(function(_) {
 			var name = js.Browser.window.prompt("Please enter a layout name:");
 			if( name == null || name == "" ) return;
-			props.local.layouts.push({ name : name, state : saveLayout() });
-			props.save();
+			ideProps.layouts.push({ name : name, state : saveLayout() });
+			props.global.save();
 			initMenu();
 		});
 		menu.find(".layout .save").click(function(_) {
 			currentLayout.state = saveLayout();
-			props.save();
+			props.global.save();
 		});
 
 		window.menu = new Menu(menu).root;

+ 0 - 107
hide/ui/Props.hx

@@ -1,107 +0,0 @@
-package hide.ui;
-
-typedef PropsDef = {
-
-	public var autoSaveLayout : Null<Bool>;
-	public var layouts : Array<{ name : String, state : Dynamic }>;
-
-	public var currentProject : String;
-	public var recentProjects : Array<String>;
-
-	public var windowPos : { x : Int, y : Int, w : Int, h : Int, max : Bool };
-
-}
-
-class Props {
-
-
-	var paths : {
-		global : String,
-		local : String,
-		project : String,
-	};
-
-	// per user, all project
-	public var global : PropsDef;
-	// per project, all  users
-	public var project : PropsDef;
-	// per user, per project
-	public var local : PropsDef;
-
-	// current merge
-	public var current : PropsDef;
-
-	public function new( projectDir : String ) {
-		var name = "hideProps.json";
-		var path = js.Node.process.argv[0].split("\\").join("/").split("/");
-		path.pop();
-		var globalPath = path.join("/") + "/" + name;
-		var projectPath = projectDir + "/" + name;
-		var localPath = projectDir.split("\\").join("/").toLowerCase();
-		paths = {
-			global : globalPath,
-			local : localPath,
-			project : projectPath
-		};
-		load();
-	}
-
-	function load() {
-		global = try haxe.Json.parse(sys.io.File.getContent(paths.global)) catch( e : Dynamic ) null;
-		project = try haxe.Json.parse(sys.io.File.getContent(paths.project)) catch( e : Dynamic ) null;
-		local = try haxe.Json.parse(js.Browser.window.localStorage.getItem(paths.local)) catch( e : Dynamic ) null;
-		if( global == null ) global = cast {};
-		if( project == null ) project = cast {};
-		if( local == null ) local = cast {};
-		if( global.currentProject == null || !sys.FileSystem.exists(global.currentProject) )
-			global.currentProject = Sys.getCwd();
-		sync();
-	}
-
-	public function sync() {
-		current = {
-			autoSaveLayout : true,
-			layouts : [],
-			currentProject : null,
-			recentProjects : [],
-			windowPos : null,
-		};
-		merge(global);
-		merge(project);
-		merge(local);
-	}
-
-	public function save() {
-		sync();
-		saveGlobals();
-		var str = haxe.Json.stringify(project);
-		if( str == '{}' )
-			try sys.FileSystem.deleteFile(paths.project) catch(e:Dynamic) {}
-		else
-			sys.io.File.saveContent(paths.project, str);
-		var str = haxe.Json.stringify(local);
-		js.Browser.window.localStorage.setItem(paths.local, str);
-	}
-
-	public function saveGlobals() {
-		var str = haxe.Json.stringify(global);
-		sys.io.File.saveContent(paths.global, str);
-	}
-
-	function merge( props : PropsDef ) {
-		for( f in Reflect.fields(props) ) {
-			var v = Reflect.field(props,f);
-			if( v == null ) {
-				Reflect.deleteField(props,f);
-				continue;
-			}
-			// remove if we are on default
-			if( props == global && v == Reflect.field(current,f) ) {
-				Reflect.deleteField(props,f);
-				continue;
-			}
-			Reflect.setField(current, f, v);
-		}
-	}
-
-}

+ 15 - 37
hide/view/FileView.hx

@@ -1,45 +1,10 @@
 package hide.view;
 
-class FileProps {
-
-	var props : Dynamic;
-
-	public function new(resPath : String, path : String) {
-		var parts = path.split("/");
-		parts.pop();
-		props = {};
-		while( true ) {
-			var pfile = resPath + "/" + parts.join("/") + "/props.json";
-			if( sys.FileSystem.exists(pfile) ) {
-				try mergeRec(props, haxe.Json.parse(sys.io.File.getContent(pfile))) catch( e : Dynamic ) js.Browser.alert(pfile+":"+e);
-			}
-			if( parts.length == 0 ) break;
-			parts.pop();
-		}
-	}
-
-	function mergeRec( dst : Dynamic, src : Dynamic ) {
-		for( f in Reflect.fields(src) ) {
-			var v = Reflect.field(src,f);
-			var t = Reflect.field(dst,f);
-			if( t == null )
-				Reflect.setField(dst,f,v);
-			else if( Type.typeof(t) == TObject )
-				mergeRec(t, v);
-		}
-	}
-
-	public function get( key : String ) : Dynamic {
-		return Reflect.field(props,key);
-	}
-
-}
-
 class FileView extends hide.ui.View<{ path : String }> {
 
 	var extension(get,never) : String;
 	var modified(default,set) : Bool;
-	var props(get, null) : FileProps;
+	var props(get, null) : hide.comp.Props;
 	var undo = new hide.comp.UndoHistory();
 
 	function get_extension() {
@@ -53,8 +18,20 @@ class FileView extends hide.ui.View<{ path : String }> {
 
 	override function setContainer(cont) {
 		super.setContainer(cont);
+		var lastSave = undo.currentID;
+		undo.onChange = function() {
+			modified = (undo.currentID != lastSave);
+		};
 		registerKey("undo", function() undo.undo());
 		registerKey("redo", function() undo.redo());
+		registerKey("save", function() {
+			save();
+			modified = false;
+			lastSave = undo.currentID;
+		});
+	}
+
+	public function save() {
 	}
 
 	override function onBeforeClose() {
@@ -64,7 +41,8 @@ class FileView extends hide.ui.View<{ path : String }> {
 	}
 
 	function get_props() {
-		if( props == null ) props = new FileProps(ide.resourceDir, state.path);
+		if( props == null )
+			props = hide.comp.Props.loadForFile(ide, state.path);
 		return props;
 	}
 

+ 8 - 4
hide/view/Particles3D.hx

@@ -20,20 +20,24 @@ class Particles3D extends FileView {
 
 	var scene : hide.comp.Scene;
 	var parts : GpuParticles;
-	var properties : hide.comp.Properties;
+	var properties : hide.comp.PropsEditor;
 
 	override function getDefaultContent() {
 		var p = new h3d.parts.GpuParticles();
 		p.addGroup().name = "Default";
-		return haxe.io.Bytes.ofString(haxe.Json.stringify(p.save(),"\t"));
+		return haxe.io.Bytes.ofString(ide.toJSON(p.save()));
 	}
 
 	override function onDisplay( e : Element ) {
-		properties = new hide.comp.Properties(e,undo);
+		properties = new hide.comp.PropsEditor(e,undo);
 		scene = new hide.comp.Scene(properties.content);
 		scene.onReady = init;
 	}
 
+	override function save() {
+		sys.io.File.saveContent(getPath(), ide.toJSON(parts.save()));
+	}
+
 	function addGroup( g : h3d.parts.GpuParticles.GpuPartGroup ) {
 		var e = new Element('
 			<div class="section open">
@@ -153,7 +157,7 @@ class Particles3D extends FileView {
 			extra.appendTo(properties.panel);
 		}, null);
 
-		//scene.resetCamera(); -- tofix when bounds are done
+		scene.resetCamera(2);
 		new h3d.scene.CameraController(scene.s3d).loadFromCamera();
 	}