Просмотр исходного кода

Merge branch 'master' into dynamic-bones

lviguier 7 месяцев назад
Родитель
Сommit
a91af8df1f

+ 13 - 7
bin/app.html

@@ -137,13 +137,19 @@
 	if (file == null)
 		return;
 	var ext = file.split(".").pop().toLowerCase();
-	if( ext != "js" && ext != "css" ) return;
-	if (timer != 0)
-		clearTimeout(timer);
-	timer = setTimeout(function() {
-		reloadWatcher.close();
-		hide.Ide.inst.reload();
-	}, 1000);
+	if( ext == "js")
+	{
+		if (timer != 0)
+			clearTimeout(timer);
+		timer = setTimeout(function() {
+			reloadWatcher.close();
+			hide.Ide.inst.reload();
+		}, 1000);
+	}
+
+	if (ext == "css") {
+		hide.Ide.inst.reloadCss(file);
+	};
   });
 </script>
 

+ 16 - 0
hide/Ide.hx

@@ -309,6 +309,12 @@ class Ide extends hide.tools.IdeData {
 	function get_isFocused() {
 		return js.Browser.document.hasFocus();
 	}
+	public function focus() {
+		window.focus();
+	}
+	public function blur() {
+		window.blur();
+	}
 
 	function onWindowChange() {
 		if( hasReloaded )
@@ -793,6 +799,16 @@ class Ide extends hide.tools.IdeData {
 		js.Browser.location.reload();
 	}
 
+	public function reloadCss(path: String = null) {
+		var css = new js.jquery.JQuery('link[type="text/css"]');
+		css.each(function(i, e) : Void {
+			var link : js.html.LinkElement = cast e;
+			if (path == null || StringTools.contains(link.href, path)) {
+				link.href = link.href + "?" + haxe.Timer.stamp();
+			}
+		});
+	}
+
 	public function getCDBContent<T>( sheetName : String ) : Array<T> {
 		for( s in database.sheets )
 			if( s.name == sheetName ) {

+ 47 - 33
hide/comp/DomkitEditor.hx

@@ -53,6 +53,7 @@ class DomkitChecker extends ScriptEditor.ScriptChecker {
 	var t_string : Type;
 	var parsers : Array<domkit.CssValue.ValueParser>;
 	var lastVariables : Map<String, domkit.CssValue> = new Map();
+	var currentComponent : hscript.Checker.CClass;
 	public var usedEnums : Array<{ path : String, constrs : Array<String> }> = [];
 	public var params : Map<String, Type> = new Map();
 	public var components : Map<String, TypedComponent>;
@@ -107,19 +108,31 @@ class DomkitChecker extends ScriptEditor.ScriptChecker {
 		var parser = new domkit.MarkupParser();
 		parser.allowRawText = true;
 		var expr = parser.parse(dmlCode,filePath, position);
-		switch( expr.kind ) {
-		case Node(null) if( expr.children.length == 1 ): expr = expr.children[0];
-		default:
-		}
-		switch( expr.kind ) {
-		case Node(name) if( name != null ):
-			var comp = resolveComp(name.split(":")[0]);
-			if( comp != null && comp.classDef != null )
-				checker.setGlobal("this",TInst(comp.classDef,[]));
-		default:
-		}
 		try {
-			checkDMLRec(expr, true);
+			for( c in expr.children ) {
+				var prev = @:privateAccess checker.locals.copy();
+				var prevGlobals = @:privateAccess checker.globals.copy();
+
+				currentComponent = null;
+				switch( c.kind ) {
+				case Node(name):
+					var comp = resolveComp(name.split(":")[0]);
+					if( comp != null && comp.classDef != null ) {
+						currentComponent = comp.classDef;
+						checker.setGlobals(comp.classDef, true);
+						checker.setGlobal("this",TInst(comp.classDef,[]));
+					}
+					defineComponent(name, c, params);
+				default:
+					continue;
+				}
+				for( c in c.children )
+					checkDMLRec(c);
+				@:privateAccess {
+					checker.locals = prev;
+					checker.globals = prevGlobals;
+				}
+			}
 		} catch( e : hscript.Expr.Error ) {
 			throw new domkit.Error(e.toString(), e.pmin, e.pmax);
 		}
@@ -310,13 +323,8 @@ class DomkitChecker extends ScriptEditor.ScriptChecker {
 						}
 					if( !dup )
 						pl.push(p);
-				} else {
-					switch( f.t ) {
-					case TFun(_):
-					default:
-						comp.vars.set(f.name, f.t);
-					}
-				}
+				} else if( f.canWrite )
+					comp.vars.set(f.name, f.t);
 			}
 			cdefs.push({ name : name, c : c });
 		}
@@ -341,6 +349,13 @@ class DomkitChecker extends ScriptEditor.ScriptChecker {
 	}
 
 	function resolveComp( name : String ) : TypedComponent {
+
+		var rootComp = name;
+		var index = name.indexOf(".");
+		if( index >= 0 ) {
+			rootComp = name.substr(0, index);
+			name = name.substr(index+1);
+		}
 		var c = components.get(name);
 		if( c != null )
 			return c;
@@ -348,7 +363,7 @@ class DomkitChecker extends ScriptEditor.ScriptChecker {
 		var dirs : Array<String> = config.get("domkit.components");
 		if( dirs == null ) dirs = ["ui/comp"];
 		for( d in dirs ) {
-			var path = d+"/"+name+".domkit";
+			var path = d+"/"+rootComp.split("-").join("_")+".domkit";
 			var content = try sys.io.File.getContent(ide.getPath(path)) catch( e : Dynamic ) continue;
 			var data = hrt.impl.DomkitViewer.DomkitFile.parse(content);
 			var node = null, params = new Map();
@@ -544,7 +559,7 @@ class DomkitChecker extends ScriptEditor.ScriptChecker {
 			c.arguments = parent == null ? [] : parent.arguments;
 		else {
 			c.arguments = [];
-			for( a in e.arguments ) {
+			for( i => a in e.arguments ) {
 				var type = null;
 				var name = switch( a.value ) {
 				case Code(c) if( IDENT.match(c) ):
@@ -566,6 +581,11 @@ class DomkitChecker extends ScriptEditor.ScriptChecker {
 					domkitError("Invalid parameter", a.pmin, a.pmax);
 					continue;
 				}
+				if( type == null && currentComponent != null ) {
+					var args = switch( currentComponent.constructor?.t ) { case null: null; case TFun(args,_): args; default: null; };
+					if( args != null && args[i].name == name )
+						type = args[i].t;
+				}
 				if( type == null )
 					type = params.get(name);
 				if( type == null )
@@ -584,17 +604,8 @@ class DomkitChecker extends ScriptEditor.ScriptChecker {
 		return c;
 	}
 
-	function checkDMLRec( e : domkit.MarkupParser.Markup, isRoot=false ) {
+	function checkDMLRec( e : domkit.MarkupParser.Markup ) {
 		switch( e.kind ) {
-		case Node(null):
-			for( c in e.children )
-				checkDMLRec(c,isRoot);
-		case Node(name) if( isRoot ):
-			var prev = @:privateAccess checker.locals.copy();
-			defineComponent(name, e, params);
-			for( c in e.children )
-				checkDMLRec(c);
-			@:privateAccess checker.locals = prev;
 		case Node(name):
 			var c = resolveComp(name);
 			if( c == null )
@@ -752,8 +763,11 @@ class DomkitEditor extends CodeEditor {
 	}
 
 	public function getComponent() {
-		var compReg = ~/<([A-Za-z0-9_]+)/;
-		if( !compReg.match(code) )
+		var compReg = ~/<\/([A-Za-z0-9_-]+)/;
+		var last = code.lastIndexOf("</");
+		if( last < 0 )
+			return null;
+		if( !compReg.match(code.substr(last)) )
 			return null;
 		var name = compReg.matched(1);
 		return checker.components.get(name);

+ 5 - 3
hide/comp/SceneEditor.hx

@@ -2869,12 +2869,14 @@ class SceneEditor {
 
 		el.toggleClass("childSelected", selectedParents.exists(p));
 
+		el.toggleClass("disabled", !p.enabled);
+		el.toggleClass("editorOnly", p.editorOnly);
+		el.toggleClass("inGameOnly", p.inGameOnly);
+		el.toggleClass("locked", p.locked);
+
 		if(obj3d != null) {
 			el.toggleClass("disabled", !p.enabled || !obj3d.visible);
 			el.toggleClass("hidden", isHidden(obj3d));
-			el.toggleClass("locked", p.locked);
-			el.toggleClass("editorOnly", p.editorOnly);
-			el.toggleClass("inGameOnly", p.inGameOnly);
 
 			var visTog = el.find(".visibility-toggle").first();
 			if(visTog.length == 0) {

+ 1 - 0
hide/comp/ScenePreview.hx

@@ -156,6 +156,7 @@ class ScenePreview extends Scene {
 	**/
 	public function setObjectPath(path: String) {
 		previewSettings.modelPath = path;
+		saveSettings();
 		reloadObject();
 	}
 

+ 3 - 1
hide/view/Domkit.hx

@@ -104,6 +104,8 @@ class Domkit extends FileView {
 		var allParams = new Map();
 		dmlEditor.checker.params = allParams;
 		var comp = dmlEditor.getComponent();
+		if( comp != null && comp.classDef != null )
+			paramsEditor.checker.checker.setGlobals(comp.classDef, true);
 		paramsEditor.doCheckScript();
 		var checker = cast(paramsEditor.checker,hide.comp.DomkitEditor.DomkitChecker);
 		var tparams = try @:privateAccess checker.typeCode(paramsEditor.code,0) catch( e : hscript.Expr.Error ) null;
@@ -167,7 +169,7 @@ class Domkit extends FileView {
 	}
 
 	override function getDefaultContent() {
-		var tag = getPath().split("/").pop().split(".").shift();
+		var tag = getPath().split("/").pop().split(".").shift().split("_").join("-");
 		return haxe.io.Bytes.ofString('<css>\n$tag {\n}\n</css>\n<$tag>\n</$tag>');
 	}
 

+ 94 - 74
hide/view/RemoteConsoleView.hx

@@ -4,8 +4,9 @@ package hide.view;
 	props.json configuration (all champs are optional):
 	```json
 	"remoteconsole": {
-		"host": "127.0.0.2",
-		"port": 40002,
+		"host": "127.0.0.1",
+		"port": 40001,
+		"disableAutoStartServer": false,
 		"commands": [
 			"dump",
 			"custom"
@@ -16,16 +17,15 @@ package hide.view;
 class RemoteConsoleView extends hide.ui.View<{}> {
 	static var rcmd : hrt.impl.RemoteConsole;
 	static var statusBarIcon : Element;
+	static var inst : RemoteConsoleView;
 	var panels : Array<RemoteConsolePanel>;
 	var panelsView : Element;
-	var logsView : Element;
 	var newPanelBtn : Element;
 
 	public function new( ?state ) {
 		super(state);
 		panels = [];
-		if( statusBarIcon == null )
-			statusBarIcon = new Element('<div class="ico ico-dot-circle-o" style="color: darkgray; cursor:default;" title="[Remote Console]"></div>');
+		inst = this;
 	}
 
 	override function onDisplay() {
@@ -39,42 +39,28 @@ class RemoteConsoleView extends hide.ui.View<{}> {
 				<input type="button" id="stopServerBtn" value="Stop Server"/>
 				<label for="connectHost">Host IP</label>
 				<input type="text" id="connectHost" value="$host:$port" disabled/>
-				<div class="logs">
-				</div>
 			</div>
 		</div>').appendTo(element);
-		hide.Ide.inst.addStatusIcon(statusBarIcon);
 		element.find("#startServerBtn").on('click', function(e) {
-			if( rcmd != null )
-				rcmd.close();
-			rcmd = new hrt.impl.RemoteConsole(port, host);
-			rcmd.onClose = () -> refreshStatusIcon();
-			rcmd.log = (msg) -> log(msg);
-			rcmd.logError = (msg) -> log(msg, true);
-			rcmd.startServer(function(c) {
-				addPanel(c);
-			});
-			refreshStatusIcon();
+			startServer(port, host);
 		});
 		element.find("#stopServerBtn").on('click', function(e) {
-			if( rcmd != null )
-				rcmd.close();
-			log("Server stopped");
+			stopServer();
 		});
-
 		panelsView = element.find(".remoteconsole");
-		logsView = element.find(".logs");
-		addPanel();
+		for( panel in panels ) {
+			panel.element.appendTo(panelsView);
+		}
+		if( rcmd != null ) {
+			for( c in rcmd.connections ) {
+				addPanel(c);
+			}
+		}
+		if( panels.length <= 0 )
+			addPanel();
 	}
 
 	override function onBeforeClose():Bool {
-		var active = 0;
-		for( p in panels ) {
-			if( p.isConnected() )
-				active++;
-		}
-		if( active > 0 && !hide.Ide.inst.confirm('Close console ($active connection(s) will be closed)?') )
-			return false;
 		forceClear();
 		return super.onBeforeClose();
 	}
@@ -85,39 +71,13 @@ class RemoteConsoleView extends hide.ui.View<{}> {
 
 	function forceClear() {
 		for( p in panels ) {
-			p.close();
+			p.close(false);
 		}
 		panels = [];
 		if( panelsView != null )
 			panelsView.remove();
 		panelsView = null;
-		logsView = null;
-	}
-
-	public function log( msg : String, error : Bool = false ) {
-		var el = new Element('<p>${StringTools.htmlEscape(msg)}</p>').appendTo(logsView);
-		if( error )
-			el.addClass("error");
-		logsView.scrollTop(logsView.get(0).scrollHeight);
-	}
-
-	public function refreshStatusIcon() {
-		if( statusBarIcon == null || rcmd == null )
-			return;
-		if( rcmd.isConnected() ) {
-			statusBarIcon.css("color", "#009500");
-			statusBarIcon.prop("title", "[Remote Console] Server active");
-		} else {
-			statusBarIcon.css("color", "#c10000");
-			statusBarIcon.prop("title", "[Remote Console] Server stopped");
-		}
-		var active = 0;
-		for( p in panels ) {
-			if( p.isConnected() )
-				active++;
-		}
-		statusBarIcon.empty();
-		statusBarIcon.append(new Element('<span> $active</span>'));
+		inst = null;
 	}
 
 	function refreshNewPanelButton() {
@@ -154,21 +114,52 @@ class RemoteConsoleView extends hide.ui.View<{}> {
 	}
 
 	public function removePanel( p : RemoteConsolePanel ) {
-		if( p.isConnected() ) {
-			if( !hide.Ide.inst.confirm('Close console (connection will be closed)?') ) {
-				return;
-			}
-		}
 		panels.remove(p);
 		p.element.remove();
-		p.close();
+		p.close(true);
 	}
 
-	public static function onBeforeReload() {
+	public static function refreshStatusIcon() {
+		if( statusBarIcon == null ) {
+			statusBarIcon = new Element('<div class="ico ico-dot-circle-o" style="cursor:default;"></div>');
+			hide.Ide.inst.addStatusIcon(statusBarIcon);
+		}
+		if( rcmd == null ) {
+			statusBarIcon.css("color", "darkgray");
+			statusBarIcon.prop("title", "[Remote Console] Server not started");
+		} else if( rcmd.isConnected() ) {
+			statusBarIcon.css("color", "#009500");
+			statusBarIcon.prop("title", "[Remote Console] Server active");
+		} else {
+			statusBarIcon.css("color", "#c10000");
+			statusBarIcon.prop("title", "[Remote Console] Server stopped");
+		}
+		statusBarIcon.empty();
+		statusBarIcon.append(new Element('<span> ${rcmd.connections.length}</span>'));
+	}
+
+	static function startServer( port, host ) {
+		if( rcmd != null && rcmd.isConnected() )
+			return;
+		rcmd = new hrt.impl.RemoteConsole(port, host);
+		rcmd.onClose = () -> refreshStatusIcon();
+		rcmd.startServer(function(c) {
+			if( inst != null )
+				inst.addPanel(c);
+			refreshStatusIcon();
+		});
+		refreshStatusIcon();
+	}
+
+	static function stopServer() {
 		if( rcmd != null )
 			rcmd.close();
 	}
 
+	public static function onBeforeReload() {
+		stopServer();
+	}
+
 	// allow hide-plugin to add/modify game-specific hide command control
 	public static var commandViews = new Map<String, Class<RemoteConsoleCommand>>();
 	public static function registerCommandView( name : String, cl : Class<RemoteConsoleCommand> ) {
@@ -176,7 +167,26 @@ class RemoteConsoleView extends hide.ui.View<{}> {
 		return null;
 	}
 
-	static var _ = hide.ui.View.register(RemoteConsoleView);
+	static var _ = init();
+	static function init() {
+		hide.ui.View.register(RemoteConsoleView);
+		function wait() {
+			if( Ide.inst?.config?.project == null ) {
+				haxe.Timer.delay(wait, 10);
+				return;
+			}
+			var config = Ide.inst.config.project;
+			var pconfig = config.get("remoteconsole");
+			if( pconfig != null && pconfig.disableAutoStartServer != true ) {
+				var host = pconfig.host ?? hrt.impl.RemoteConsole.DEFAULT_HOST;
+				var port = pconfig.port ?? hrt.impl.RemoteConsole.DEFAULT_PORT;
+				startServer(port, host);
+			}
+		}
+		// Needs to wait a little on reload, otherwise the port might still be occupied.
+		haxe.Timer.delay(wait, 100);
+		return 0;
+	}
 }
 
 class RemoteConsolePanel extends hide.comp.Component {
@@ -186,12 +196,11 @@ class RemoteConsolePanel extends hide.comp.Component {
 	public function new( view : RemoteConsoleView, connection : Null<hrt.impl.RemoteConsole.RemoteConsoleConnection>, commands : Null<Array<String>> ) {
 		super(null, null);
 		this.view = view;
-		this.connection = connection;
 		element = new Element('
 		<div class="remoteconsole-panel">
 			<div class="controls">
-				<div class="ico ico-dot-circle-o" id="statusIcon" style="color: darkgray; cursor:default;" title="Not connected"></div>
-				<div class="ico ico-close" id="closeBtn" title="Close panel"></div>
+				<div class="ico ico-dot-circle-o" id="statusIcon" style="cursor:default;"></div>
+				<div class="ico ico-close" id="closeBtn" title="Close panel and its connection"></div>
 			</div>
 			<div class="logs">
 			</div>
@@ -200,6 +209,7 @@ class RemoteConsolePanel extends hide.comp.Component {
 		</div>
 		');
 		this.statusIcon = element.find("#statusIcon");
+		this.connection = connection;
 		element.find("#closeBtn").on('click', function(e) {
 			view.removePanel(this);
 		});
@@ -208,11 +218,18 @@ class RemoteConsolePanel extends hide.comp.Component {
 			addCommandElement(name);
 	}
 	function set_connection( c ) {
+		if( connection == c )
+			return connection;
 		if( c != null ) {
 			c.onClose = () -> refreshStatusIcon();
 			c.log = (msg) -> log(msg);
 			c.logError = (msg) -> log(msg, true);
 		}
+		if( connection != null ) {
+			connection.onClose = () -> {};
+			connection.log = (msg) -> {};
+			connection.logError = (msg) -> {};
+		}
 		connection = c;
 		refreshStatusIcon();
 		return connection;
@@ -227,10 +244,13 @@ class RemoteConsolePanel extends hide.comp.Component {
 		comp.element.appendTo(element.find(".commands"));
 	}
 	function refreshStatusIcon() {
-		view.refreshStatusIcon();
+		RemoteConsoleView.refreshStatusIcon();
 		if( statusIcon == null )
 			return;
-		if( isConnected() ) {
+		if( connection == null ) {
+			statusIcon.css("color", "darkgray");
+			statusIcon.prop("title", "Not connected");
+		} else if( connection.isConnected() ) {
 			statusIcon.css("color", "#009500");
 			statusIcon.prop("title", "Connected");
 		} else {
@@ -238,8 +258,8 @@ class RemoteConsolePanel extends hide.comp.Component {
 			statusIcon.prop("title", "Disconnected");
 		}
 	}
-	public function close() {
-		if( connection != null ) {
+	public function close( disconnect : Bool ) {
+		if( disconnect && connection != null ) {
 			connection.close();
 			log("Disconnected");
 		}

+ 31 - 8
hide/view/animgraph/AnimGraphEditor.hx

@@ -3,6 +3,12 @@ using Lambda;
 import hide.view.GraphInterface;
 import hrt.animgraph.*;
 
+@:structInit
+@:build(hrt.prefab.Macros.buildSerializable())
+class AnimGraphEditorPreviewState {
+    @:s public var providerIndex: Int = 0;
+}
+
 @:access(hrt.animgraph.AnimGraph)
 @:access(hrt.animgraph.AnimGraphInstance)
 @:access(hrt.animgraph.Node)
@@ -17,9 +23,11 @@ class AnimGraphEditor extends GenericGraphEditor {
     var previewNode : hrt.animgraph.nodes.AnimNode = null;
     var queuedPreview : hrt.animgraph.nodes.AnimNode = null;
 
-    var customProviderIndex : Int = 0;
+    var previewState: AnimGraphEditorPreviewState;
 
     override function reloadView() {
+        loadPreviewState();
+
         previewNode = null;
         animGraph = cast hide.Ide.inst.loadPrefab(state.path, null,  true);
 
@@ -47,13 +55,14 @@ class AnimGraphEditor extends GenericGraphEditor {
         refreshPamamList();
 
         var dl = new Element("<dl></dl>").appendTo(propertiesContainer);
-        addAnimSetSelector(dl, undo, () -> customProviderIndex, (i: Int) -> {
-			customProviderIndex = i;
+        addAnimSetSelector(dl, undo, () -> previewState.providerIndex, (i: Int) -> {
+			previewState.providerIndex = i;
+            savePreviewState();
 			refreshPreview();
 		});
 
 
-        new AnimList(propertiesContainer, null, getAnims(scenePreview, animGraph.animFolder));
+        new AnimList(propertiesContainer, null, getAnims(scenePreview, {animDirectory: animGraph.animFolder, assetPath: state.path}));
 
         graphEditor.element.get(0).addEventListener("dragover", (e: js.html.DragEvent) -> {
             if (e.dataTransfer.types.contains("index"))
@@ -119,14 +128,14 @@ class AnimGraphEditor extends GenericGraphEditor {
 
     }
 
-    static public function getAnims(scene: hide.comp.Scene, animDirectory: String) : Array<String> {
+    static public function getAnims(scene: hide.comp.Scene, ctx: hrt.animgraph.AnimGraph.EditorProviderContext ) : Array<String> {
         var anims : Array<String> = [];
 
         if (AnimGraph.customAnimNameLister != null) {
-            anims = anims.concat(AnimGraph.customAnimNameLister({animDirectory: animDirectory}));
+            anims = anims.concat(AnimGraph.customAnimNameLister(ctx));
         }
 
-        anims = anims.concat(scene.listAnims(animDirectory));
+        anims = anims.concat(scene.listAnims(ctx.animDirectory));
         return anims;
     }
 
@@ -177,6 +186,16 @@ class AnimGraphEditor extends GenericGraphEditor {
         return menu;
     }
 
+    public function loadPreviewState() : Void {
+        var settingsSer = haxe.Json.parse(getDisplayState("previewState") ?? "{}");
+        previewState = {};
+        @:privateAccess previewState.copyFromDynamic(settingsSer);
+    }
+
+    public function savePreviewState() : Void {
+        saveDisplayState("previewState", haxe.Json.stringify(@:privateAccess previewState.copyToDynamic({})));
+    }
+
     static public function gatherAllPreviewModels(basePath : String) : Array<String> {
         var paths = [];
 
@@ -279,7 +298,11 @@ class AnimGraphEditor extends GenericGraphEditor {
             var resolver = null;
             if (AnimGraph.customEditorResolverProvider != null) {
                 var providers = AnimGraph.customEditorResolverProvider(_);
-                resolver = providers != null ? providers[customProviderIndex].resolver : null;
+                if (providers != null && previewState.providerIndex > providers.length) {
+                    previewState.providerIndex = 0;
+                    savePreviewState();
+                }
+                resolver = providers != null ? providers[previewState.providerIndex]?.resolver : null;
             }
             var anim = animGraph.getAnimation(previewNode, resolver);
             previewModel.playAnimation(anim);

+ 29 - 13
hide/view/animgraph/BlendSpace2DEditor.hx

@@ -1,9 +1,9 @@
 package hide.view.animgraph;
 
-class BlendSpacePreviewSettings {
-	public var modelPath: String = null;
-
-	public function new() {};
+@:structInit
+@:build(hrt.prefab.Macros.buildSerializable())
+class BlendSpacePreviewState {
+    @:s public var providerIndex: Int = 0;
 }
 
 @:access(hrt.animgraph.BlendSpace2D)
@@ -29,17 +29,15 @@ class BlendSpace2DEditor extends hide.view.FileView {
 
 	var previewAxis : h2d.col.Point = new h2d.col.Point();
 
-	var startMovePos : h2d.col.Point = null;
+    var previewState: BlendSpacePreviewState;
 
-	var previewSettings : BlendSpacePreviewSettings;
+	var startMovePos : h2d.col.Point = null;
 
 	static final pointRadius = 8;
 	var subdivs = 5;
 
 	var animPreview : hrt.animgraph.AnimGraphInstance;
 
-	var customProviderIndex = 0;
-
 	inline function getPointPos(clientX : Float, clientY : Float, snap: Bool) : h2d.col.Point {
 		var x = hxd.Math.clamp(graphXToLocal(clientX), blendSpace2D.minX, blendSpace2D.maxX);
 		var y = hxd.Math.clamp(graphYToLocal(clientY), blendSpace2D.minY, blendSpace2D.maxY);
@@ -58,7 +56,20 @@ class BlendSpace2DEditor extends hide.view.FileView {
 		return inline new h2d.col.Point(x, y);
 	}
 
+    public function loadPreviewState() : Void {
+        var settingsSer = haxe.Json.parse(getDisplayState("previewState") ?? "{}");
+        previewState = {};
+        @:privateAccess previewState.copyFromDynamic(settingsSer);
+    }
+
+    public function savePreviewState() : Void {
+        saveDisplayState("previewState", haxe.Json.stringify(@:privateAccess previewState.copyToDynamic({})));
+    }
+
 	override function onDisplay() {
+
+		loadPreviewState();
+
 		previewModel = null;
 		animPreview = null;
 		blendSpace2D = Std.downcast(hide.Ide.inst.loadPrefab(state.path, null,  true), hrt.animgraph.BlendSpace2D);
@@ -352,7 +363,11 @@ class BlendSpace2DEditor extends hide.view.FileView {
 				if (hrt.animgraph.AnimGraph.customEditorResolverProvider != null) {
 					var resolvers = hrt.animgraph.AnimGraph.customEditorResolverProvider(_);
 					if (resolvers != null) {
-						resolver = resolvers[customProviderIndex]?.resolver;
+						if (previewState.providerIndex > resolvers.length) {
+							previewState.providerIndex = 0;
+							savePreviewState();
+						}
+						resolver = resolvers[previewState.providerIndex]?.resolver;
 					}
 				}
 				animPreview = new hrt.animgraph.AnimGraphInstance(blendSpaceNode, resolver, "", 1000, 1.0/60.0);
@@ -367,7 +382,7 @@ class BlendSpace2DEditor extends hide.view.FileView {
 				if (hrt.animgraph.AnimGraph.customEditorResolverProvider != null) {
 					var resolvers = hrt.animgraph.AnimGraph.customEditorResolverProvider(_);
 					if (resolvers != null) {
-						resolver = resolvers[customProviderIndex]?.resolver;
+						resolver = resolvers[previewState.providerIndex]?.resolver;
 					}
 				}
 				animPreview.resolver = resolver;
@@ -451,8 +466,9 @@ class BlendSpace2DEditor extends hide.view.FileView {
 			updatePreviewAxis();
 		});
 
-		AnimGraphEditor.addAnimSetSelector(preview.find("dl"), undo, () -> customProviderIndex, (i: Int) -> {
-			customProviderIndex = i;
+		AnimGraphEditor.addAnimSetSelector(preview.find("dl"), undo, () -> previewState.providerIndex, (i: Int) -> {
+			previewState.providerIndex = i;
+			savePreviewState();
 			refreshPreviewAnimation();
 		});
 	}
@@ -490,7 +506,7 @@ class BlendSpace2DEditor extends hide.view.FileView {
 			scenePreview.setObjectPath(first);
 		}
 
-		var animList = new AnimList(propertiesContainer, null, AnimGraphEditor.getAnims(scenePreview, blendSpace2D.animFolder));
+		var animList = new AnimList(propertiesContainer, null, AnimGraphEditor.getAnims(scenePreview, {animDirectory: blendSpace2D.animFolder, assetPath: state.path}));
 		scenePreview.resetPreviewCamera();
 	}
 

+ 1 - 0
hrt/animgraph/AnimGraph.hx

@@ -19,6 +19,7 @@ typedef SerializedEdge = {
 #if editor
 typedef EditorProviderContext = {
 	animDirectory: String,
+	assetPath: String,
 }
 #end
 

+ 2 - 2
hrt/animgraph/Resource.hx

@@ -40,8 +40,8 @@ class Resource extends hxd.res.Resource {
 		return animGraph;
 	}
 
-	public function loadAnim() : AnimGraphInstance {
-		return load().getAnimation();
+	public function loadAnim(resolver: hrt.animgraph.AnimGraphInstance.AnimResolver = null) : AnimGraphInstance {
+		return load().getAnimation(resolver);
 	}
 
 	public static var CACHE_VERSION = 0;

+ 16 - 5
hrt/animgraph/nodes/Input.hx

@@ -13,13 +13,18 @@ class Input extends AnimNode {
 	var anim : h3d.anim.Animation;
 	var proxy : AnimProxy;
 
-	@:s var path : String = "character/Kobold01/Anim_attack01.FBX";
-
-
+	@:s var path : String = "";
 
 	override function getBones(ctx: hrt.animgraph.nodes.AnimNode.GetBoneContext):Map<String, Int> {
 		proxy = new AnimProxy();
-		anim = hxd.res.Loader.currentInstance.load(ctx.resolver(path)).toModel().toHmd().loadAnimation().createInstance(proxy);
+		var pathToLoad = ctx.resolver(path);
+		if (pathToLoad == null)
+			return [];
+		try {
+			anim = hxd.res.Loader.currentInstance.load(pathToLoad).toModel().toHmd().loadAnimation().createInstance(proxy);
+		} catch (e) {
+			return [];
+		}
 
 		var map : Map<String, Int> = [];
 		for (id => obj in anim.getObjects()) {
@@ -29,14 +34,20 @@ class Input extends AnimNode {
 	}
 
 	override function tick(dt: Float) {
+		if (anim == null)
+			return;
 		anim.update(dt);
 		@:privateAccess anim.isSync = false;
 	}
 
 	override function getBoneTransform(id: Int, matrix: h3d.Matrix, ctx: AnimNode.GetBoneTransformContext) {
 		// todo : add sync outside the getBoneMatrix to avoid checks
-		@:privateAccess
+		if (anim == null) {
+			matrix.load(ctx.getDefPose());
+			return;
+		}
 
+		@:privateAccess
 		if (!anim.isSync) {
 			anim.sync(true);
 			anim.isSync = true;

+ 74 - 12
hrt/impl/DomkitViewer.hx

@@ -41,8 +41,18 @@ class DomkitViewer {
 		}
 	}
 
+	static function clearImportNames( m : domkit.MarkupParser.Markup ) {
+		switch( m.kind ) {
+		case Node(n) if( n != null && n.indexOf(".") >= 0 ): m.kind = Node(n.split(".").pop()); // remove root.name
+		default:
+		}
+		if( m.children != null )
+			for( c in m.children )
+				clearImportNames(c);
+	}
+
 	public static function loadSource( path : String, pos : Position, fields : Array<Field> ) {
-		var name = path.split("/").pop();
+		var name = path.split("/").pop().split("_").join("-");
 		var dotPos = name.lastIndexOf(".");
 		if( dotPos >= 0 ) {
 			path = path.substr(0, path.length - name.length + dotPos);
@@ -66,14 +76,21 @@ class DomkitViewer {
 				}
 				if( c.arguments != null ) c.arguments = null;
 			}
+			var found = null;
 			for( c in m.children ) {
 				switch( c.kind ) {
 				case Node(n) if( n == name ):
-					m.children = [c];
+					found = c;
 					break;
 				default:
 				}
 			}
+			if( found == null ) {
+				Context.error("Could not find definition for component '"+name+"'", Context.currentPos());
+				return null;
+			}
+			m.children = [found];
+
 			var params = new hscript.Parser().parseString(data.params, path);
 			var dynParams = new Map();
 			var hasDynParam = false;
@@ -97,6 +114,7 @@ class DomkitViewer {
 			}
 			if( hasDynParam )
 				removeDynParamsRec(m, dynParams);
+			clearImportNames(m);
 
 			fields.push({
 				name : "__CSS",
@@ -359,7 +377,7 @@ class DomkitViewer extends h2d.Object {
 	function loadComponents( res : hxd.res.Resource ) {
 		var fullText = res.entry.getText();
 		var data = DomkitFile.parse(fullText);
-		var inf = { comps : [], params : null };
+		var inf = { comps : [], params : (null:Dynamic) };
 		handleErrors(res, function() {
 			var parser = new domkit.MarkupParser();
 			parser.allowRawText = true;
@@ -377,13 +395,44 @@ class DomkitViewer extends h2d.Object {
 					}
 				}
 			}
-			var vparams : Dynamic = evalCode(interp,eparams);
-			if( vparams != null ) {
-				for( f in Reflect.fields(vparams) ) {
-					var forceNull = res == resource && definedClasses.indexOf(f) >= 0;
-					interp.variables.set(f, forceNull ? null : Reflect.field(vparams,f));
+			var mainComp = null;
+			for( i in 0...expr.children.length ) {
+				var m = expr.children[expr.children.length - i - 1];
+				switch( m.kind ) {
+				case Node(name):
+					var parts = name.split(":");
+					var name = parts[0];
+					mainComp = domkit.Component.get(name, true);
+					break;
+				default:
+				}
+			}
+
+			var prev = interp.variables.copy();
+			var mainInst : Dynamic = null;
+			if( mainComp != null ) {
+				var cl = @:privateAccess mainComp.classValue;
+				if( cl != null ) {
+					mainInst = Type.createEmptyInstance(cl);
+					interp.setContext(mainInst);
 				}
 			}
+			var vparams = switch( eparams.e ) {
+			case EObject(fl):
+				[for( f in fl ) {
+					var val : Dynamic = evalCode(interp,f.e);
+					var forceNull = res == resource && definedClasses.indexOf(f.name) >= 0;
+					if( forceNull ) val = null;
+					interp.variables.set(f.name, val);
+					if( mainInst != null ) try Reflect.setProperty(mainInst,f.name, val) catch( e : Dynamic ) {};
+					{ name : f.name, value : val };
+				}];
+			default: throw "assert";
+			}
+			interp.variables = prev;
+			for( f in vparams )
+				interp.variables.set(f.name, f.value);
+
 			for( m in expr.children ) {
 				switch( m.kind ) {
 				case Node(name):
@@ -394,10 +443,19 @@ class DomkitViewer extends h2d.Object {
 					var parentType = parts[1] ?? "flow";
 					var compParent = resolveComponent(parentType, m.pmin);
 					var comp = domkit.Component.get(name, true);
+					var inst : Dynamic = null;
 					if( comp == null ) {
 						comp = new domkit.Component(name,null,compParent);
 						domkit.CssStyle.CssData.registerComponent(comp);
 						loadedComponents.push(cast comp);
+					} else {
+						var compClass = @:privateAccess comp.classValue;
+						if( compClass != null ) {
+							inst = Type.createEmptyInstance(compClass);
+							interp.setContext(inst);
+							for( f in vparams )
+								try Reflect.setProperty(inst, f.name, f.value) catch( e : Dynamic ) {}
+						}
 					}
 					var args = [];
 					if( m.arguments != null ) {
@@ -416,13 +474,15 @@ class DomkitViewer extends h2d.Object {
 							error("Invalid argument decl", arg.pmin, arg.pmax);
 						}
 					}
-					var make = makeComponent.bind(res, m, comp, args, interp);
+					var make = makeComponent.bind(res, m, comp, args, interp, inst);
 					tmpCompMap.set(name, make);
 					inf.comps.push(make);
 				default:
 				}
 			}
-			inf.params = vparams;
+			inf.params = {};
+			for( v in vparams )
+				Reflect.setField(inf.params, v.name, v.value);
 		});
 		return inf;
 	}
@@ -462,7 +522,7 @@ class DomkitViewer extends h2d.Object {
 		}
 	}
 
-	function makeComponent( res : hxd.res.Resource, m : Markup, comp : domkit.Component<Dynamic,Dynamic>, argNames : Array<String>, interp : DomkitInterp, args : Array<Dynamic>, parent : h2d.Object ) : h2d.Object {
+	function makeComponent( res : hxd.res.Resource, m : Markup, comp : domkit.Component<Dynamic,Dynamic>, argNames : Array<String>, interp : DomkitInterp, inst : Dynamic, args : Array<Dynamic>, parent : h2d.Object ) : h2d.Object {
 		var prev = interp.variables.copy();
 		var obj = null;
 		handleErrors(res, function() {
@@ -475,8 +535,10 @@ class DomkitViewer extends h2d.Object {
 			else
 				@:privateAccess obj.dom.component = cast comp;
 			if( args.length > 0 && argNames.length > 0 ) {
-				for( i => arg in argNames )
+				for( i => arg in argNames ) {
 					interp.variables.set(arg, args[i]);
+					if( inst != null ) try Reflect.setProperty(inst, arg, args[i]) catch( e : Dynamic ) {};
+				}
 			}
 		});
 		for( c in m.children )

+ 38 - 28
hrt/impl/RemoteConsole.hx

@@ -22,11 +22,12 @@ class RemoteConsole {
 	public var host : String;
 	public var port : Int;
 	var sock : hxd.net.Socket;
-	var cSocks : Array<RemoteConsoleConnection>;
+	public var connections : Array<RemoteConsoleConnection>;
 
 	public function new( ?port : Int, ?host : String ) {
 		this.host = host ?? DEFAULT_HOST;
 		this.port = port ?? DEFAULT_PORT;
+		this.connections = [];
 	}
 
 	public function startServer( ?onClient : RemoteConsoleConnection->Void ) {
@@ -38,13 +39,10 @@ class RemoteConsole {
 		}
 		sock.bind(host, port, function(s) {
 			var connection = new RemoteConsoleConnection(this, s);
-			cSocks.push(connection);
+			connections.push(connection);
 			s.onError = function(msg) {
 				connection.logError("Client error: " + msg);
 				connection.close();
-				if( cSocks != null ) {
-					cSocks.remove(connection);
-				}
 				connection = null;
 			}
 			s.onData = () -> connection.handleOnData();
@@ -58,6 +56,8 @@ class RemoteConsole {
 	public function connect( ?onConnected : Bool -> Void ) {
 		close();
 		sock = new hxd.net.Socket();
+		var connection = new RemoteConsoleConnection(this, sock);
+		connections.push(connection);
 		sock.onError = function(msg) {
 			if( !SILENT_CONNECT )
 				logError("Socket Error: " + msg);
@@ -65,8 +65,6 @@ class RemoteConsole {
 			if( onConnected != null )
 				onConnected(false);
 		}
-		var connection = new RemoteConsoleConnection(this, sock);
-		cSocks.push(connection);
 		sock.onData = () -> connection.handleOnData();
 		sock.connect(host, port, function() {
 			log("Connected to server");
@@ -82,11 +80,11 @@ class RemoteConsole {
 			sock.close();
 			sock = null;
 		}
-		if( cSocks != null ) {
-			for( s in cSocks )
+		if( connections != null ) {
+			for( s in connections )
 				s.close();
 		}
-		cSocks = [];
+		connections = [];
 		onClose();
 	}
 
@@ -106,10 +104,10 @@ class RemoteConsole {
 	}
 
 	public function sendCommand( cmd : String, ?args : Dynamic, ?onResult : Dynamic -> Void ) {
-		if( cSocks.length == 0 ) {
+		if( connections.length == 0 ) {
 			// Ignore send when not really connected
-		} else if( cSocks.length == 1 ) {
-			cSocks[0].sendCommand(cmd, args, onResult);
+		} else if( connections.length == 1 ) {
+			connections[0].sendCommand(cmd, args, onResult);
 		} else {
 			logError("Send to multiple target not implemented");
 		}
@@ -138,6 +136,7 @@ class RemoteConsoleConnection {
 		if( sock != null )
 			sock.close();
 		sock = null;
+		parent.connections.remove(this);
 		onClose();
 	}
 
@@ -169,20 +168,22 @@ class RemoteConsoleConnection {
 	}
 
 	public function handleOnData() {
-		var str = sock.input.readLine().toString();
-		var obj = try { haxe.Json.parse(str); } catch (e) { logError("Parse error: " + e); null; };
-		if( obj == null || obj.id == null ) {
-			return;
-		}
-		var id : Int = obj.id;
-		if( id <= 0 ) {
-			var onResult = waitReply.get(-id);
-			waitReply.remove(-id);
-			if( onResult != null ) {
-				onResult(obj.args);
+		while( sock.input.available > 0 ) {
+			var str = sock.input.readLine().toString();
+			var obj = try { haxe.Json.parse(str); } catch (e) { logError("Parse error: " + e); null; };
+			if( obj == null || obj.id == null ) {
+				continue;
+			}
+			var id : Int = obj.id;
+			if( id <= 0 ) {
+				var onResult = waitReply.get(-id);
+				waitReply.remove(-id);
+				if( onResult != null ) {
+					onResult(obj.args);
+				}
+			} else {
+				onCommand(obj.cmd, obj.args, (result) -> sendData(null, result, -id));
 			}
-		} else {
-			onCommand(obj.cmd, obj.args, (result) -> sendData(null, result, -id));
 		}
 	}
 
@@ -290,11 +291,20 @@ class RemoteConsoleConnection {
 			return;
 		if( args.cdbsheet != null ) {
 			var sheet = hide.Ide.inst.database.getSheet(args.cdbsheet);
-			hide.Ide.inst.open("hide.view.CdbTable", {}, function(view) {
+			hide.Ide.inst.open("hide.view.CdbTable", {}, null, function(view) {
+				hide.Ide.inst.focus();
 				Std.downcast(view,hide.view.CdbTable).goto(sheet,args.line,args.column);
 			});
 		} else {
-			hide.Ide.inst.openFile(args.file);
+			hide.Ide.inst.showFileInResources(args.file);
+			hide.Ide.inst.openFile(args.file, null, function(view) {
+				hide.Ide.inst.focus();
+				var domkit = Std.downcast(view, hide.view.Domkit);
+				if (domkit != null && args.line != null) {
+					@:privateAccess domkit.cssEditor.focus();
+					@:privateAccess domkit.cssEditor.editor.setPosition({column: args.column, lineNumber: args.line+1});
+				}
+			});
 		}
 	}
 #end

+ 12 - 2
hrt/impl/RemoteTools.hx

@@ -10,8 +10,10 @@ class RemoteTools {
 	static var rc : hrt.impl.RemoteConsole;
 	static var mainEvent : haxe.MainLoop.MainEvent;
 	static var lastUpdate : Float;
+	static var onConnected : Bool -> Void;
 
-	public static function autoConnect() {
+	public static function autoConnect( ?onConnected : Bool -> Void ) {
+		RemoteTools.onConnected = onConnected;
 		if( rc != null )
 			return;
 		var configdyn : Dynamic = null;
@@ -37,6 +39,10 @@ class RemoteTools {
 		rc = null;
 	}
 
+	public static function isConnected() {
+		return rc != null && rc.isConnected();
+	}
+
 	static function update() {
 		if( rc == null || rc.isConnected() )
 			return;
@@ -44,7 +50,7 @@ class RemoteTools {
 		if( current - lastUpdate < RETRY_DELAY )
 			return;
 		lastUpdate = current;
-		rc.connect();
+		rc.connect(onConnected);
 	}
 
 	// ----- Commands -----
@@ -61,4 +67,8 @@ class RemoteTools {
 		rc?.sendCommand("open", { file : file });
 	}
 
+	public static function openDomkit( file : String, ?line : Int, ?column : Int ) {
+		rc?.sendCommand("open", { file : file, line : line, column : column });
+	}
+
 }

+ 4 - 1
hrt/prefab/fx/FX.hx

@@ -53,8 +53,11 @@ class FXAnimation extends h3d.scene.Object {
 		if(root == null)
 			root = def;
 
-		for (shaderTarget in def.flatten(hrt.prefab.fx.ShaderTarget))
+		for (shaderTarget in def.flatten(hrt.prefab.fx.ShaderTarget)) {
+			if (!shaderTarget.enabled)
+				continue;
 			shaderTarget.applyShaderTarget(def, shaderTarget.target);
+		}
 
 		initObjAnimations(root);
 		initEmitters(root);

+ 3 - 0
hrt/prefab/rfx/Sao.hx

@@ -31,6 +31,7 @@ class Sao extends RendererFX {
 	@:s public var USE_FADE : Bool = false;
 	@:s public var fadeStart: Float = 100.0;
 	@:s public var fadeEnd: Float = 200.0;
+	@:s public var useScalableBias : Bool;
 
 	var sao : h3d.pass.ScalableAO;
 	var saoBlur = new h3d.pass.Blur();
@@ -84,6 +85,7 @@ class Sao extends RendererFX {
 			sao.shader.USE_FADE = USE_FADE;
 			sao.shader.fadeStart = fadeStart;
 			sao.shader.fadeEnd = fadeEnd;
+			sao.shader.USE_SCALABLE_BIAS = useScalableBias;
 			sao.apply(depth.texture,normal.texture,ctx.camera);
 			ctx.engine.popTarget();
 
@@ -113,6 +115,7 @@ class Sao extends RendererFX {
 				<dt>Intensity</dt><dd><input type="range" min="0" max="10" field="intensity"/></dd>
 				<dt>Radius</dt><dd><input type="range" min="0" max="10" field="radius"/></dd>
 				<dt>Bias</dt><dd><input type="range" min="0" max="0.5" field="bias"/></dd>
+				<dt>Use Scalable Bias</dt><dd><input type="checkbox" field="useScalableBias"/></dd>
 				<dt>Texture Size</dt><dd><input type="range" min="0" max="1" field="size"/></dd>
 				<dt>Samples</dt><dd><input type="range" min="3" max="127" field="samples" step="1"/></dd>
 				<dt>Materials occlusion</dt><dd><input type="range" min="0" max="1" field="microIntensity"/></dd>

+ 3 - 1
libs/nw/Window.hx

@@ -27,7 +27,9 @@ extern class Window {
 	public function leaveFullscreen() : Void;
 	public function on( event : String, callb : Void -> Void ) : Void;
 
-	public function show( b : Bool ) : Void;
+	public function focus() : Void;
+	public function blur() : Void;
+	public function show( ?b : Bool ) : Void;
 
 	public function close( ?force : Bool ) : Void;