diff --git a/haxework/gui/Builder.hx b/haxework/gui/Builder.hx index 0b3c142..852c2b6 100644 --- a/haxework/gui/Builder.hx +++ b/haxework/gui/Builder.hx @@ -142,11 +142,18 @@ class Builder { return instance; } + + private static var cache:Map = new Map(); + public static function createFromAsset(asset:String, ?key:String):Dynamic { - var data = Json.parse(Assets.getText(asset)); - if (key != null) { - data = Reflect.field(data, key); + var cacheKey = asset + ":" + key; + if (!cache.exists(cacheKey)) { + var data = Json.parse(openfl.Assets.getText(asset)); + if (key != null) { + data = Reflect.field(data, key); + } + cache.set(cacheKey, new Builder(data)); } - return new Builder(data); + return cache.get(cacheKey); } } \ No newline at end of file diff --git a/haxework/gui/ViewBuilder.hx b/haxework/gui/ViewBuilder.hx new file mode 100644 index 0000000..c144ca1 --- /dev/null +++ b/haxework/gui/ViewBuilder.hx @@ -0,0 +1,176 @@ +package haxework.gui; + +import haxe.Json; +import haxe.macro.Expr; +import haxe.macro.Context; + +@:remove @:autoBuild(haxework.gui.ViewBuilderImpl.build()) +extern interface ViewBuilder {} + +class ViewBuilderImpl { +#if macro + static function loadFileAsString(path:String, json:Bool = true) { + try { + var p = Context.resolvePath(path); + Context.registerModuleDependency(Context.getLocalModule(), p); + var content = sys.io.File.getContent(p); + return json ? Json.parse(content) : content; + } + catch(e:Dynamic) { + return haxe.macro.Context.error("Failed to load file $path: $e", Context.currentPos()); + } + } + + private static function getTemplate():Array { + var template = null; + var style = null; + var c = Context.getLocalClass().get(); + for (m in c.meta.get()) { + if (m.name == ":template") { + template = switch(m.params[0].expr) { + case ExprDef.EConst(Constant.CString(value)): value; + case _: null; + } + if (template != null) { + var t = template.split("@"); + template = loadFileAsString(t[0]); + if (t[1] != null) template = Reflect.field(template, t[1]); + } + style = switch(m.params[1].expr) { + case ExprDef.EConst(Constant.CString(value)): value; + case _: null; + } + if (style != null) { + style = loadFileAsString(style); + } + } + } + return [ + template, + style + ]; + } + + private static var i = 0; + + private static function specialValue(data:Array, name:String, style:Dynamic, key:String, a:Array):Dynamic { + return switch (a[0]) { + case "asset": + switch (a[1]) { + case "image": + "openfl.Assets.getBitmapData(\"" + a[2] + "\")"; + case _: + a[2]; + } + case "res": + var res = "haxework.provider.Provider.get(haxework.resources.IResources)." + a[1]; + var bindExpr = res + ".bind(\"" + a[2] + "\", " + name + ", \"" + key + "\")"; + data.push(Context.parse(bindExpr, Context.currentPos())); + //res + ".get(\"" + a[2] + "\")"; + null; + case "locale": + "new haxework.locale.LString(\"" + a[1] + "\")"; + case "class": + a[1]; + case "layout": + var template = Json.parse(loadFileAsString(a[1])); + return getValue(data, name, style, key, template); + case "link": + "(links == null) ? untyped this : links[\"" + a[1] + "\"]"; + case _: + throw "Unsupported prefix \"" + a[0] + "\""; + } + } + + private static function getValue(data:Array, name:String, style:Dynamic, key:String, value:Dynamic):Dynamic { + return if (Std.is(value, Array)) { + value.map(function(v) { return getValue(data, null, style, null, v); }); + } else if (Std.is(value, String)) { + if (value.charAt(0) == "@") { + specialValue(data, name, style, key, value.substring(1, value.length).split(":")); + } else if (~/(0x|#)[A-Fa-f\d]{6}/.match(value)) { + Std.parseInt(StringTools.replace(Std.string(value), "#", "0x")); + } else { + "\"" + value + "\""; + } + } else if (Std.is(value, Float) || (Std.is(value, Bool))) { + value; + } else if (value != null) { + if (Reflect.hasField(value, "type")) { + var n = "a" + i++; + var type = Reflect.field(value, "type"); + data.push(Context.parse("var " + n + " = new " + type + "()", Context.currentPos())); + createElement(data, value, n, style); + n; + } else { + value; + } + } else { + value; + } + } + + private static function createElement(data:Array, template:Dynamic, name:String, style:Dynamic):String { + if (Reflect.hasField(template, "style")) { + var s = Reflect.field(style, Reflect.field(template, "style")); + for (key in Reflect.fields(s)) if (!Reflect.hasField(template, key)) { + Reflect.setField(template, key, Reflect.field(s, key)); + } + } + for (key in Reflect.fields(template)) { + if (["type", "style"].indexOf(key) > -1) continue; + var value = getValue(data, name, style, key, Reflect.field(template, key)); + if (value != null) { + data.push(Context.parse(name + "." + key + " = untyped " + value, Context.currentPos())); + } + } + return name; + } + + public static function build() { + var template = getTemplate(); + var fields = Context.getBuildFields(); + + var data = []; + i = 0; + createElement(data, template[0], "this", template[1]); + + var init = false; + for (f in fields) if (f.name == "init") { + init = true; + break; + } + + fields.push({ + name: "build", + access: [APublic], + pos: Context.currentPos(), + kind: FFun({ + args: [{name:"links", type:TPath({name:"Dynamic", pack:[], params:[]}), opt:true, value:null}], + expr: macro $b{data}, + params: [], + ret: null + }) + }); + + data = []; + data.push(macro super()); + data.push(macro build(links)); + if (init) data.push(macro init()); + + fields.push({ + name: "new", + access: [APublic], + pos: Context.currentPos(), + kind: FFun({ + args: [{name:"links", type:TPath({name:"Dynamic", pack:[], params:[]}), opt:true, value:null}], + expr: macro $b{data}, + params: [], + ret: null + }) + }); + + return fields; + } +#end +} \ No newline at end of file diff --git a/haxework/gui/popup/PopupView.hx b/haxework/gui/popup/PopupView.hx index e0dc891..690a6d2 100755 --- a/haxework/gui/popup/PopupView.hx +++ b/haxework/gui/popup/PopupView.hx @@ -17,7 +17,7 @@ class PopupView extends GroupView { private var contentView:IGroupView; private var callback:ICallback; - public function new(resource:String, ?key:String = null) { + public function new(contentViewFactory:Class>) { super(); pWidth = 100; @@ -25,7 +25,7 @@ class PopupView extends GroupView { inLayout = false; skin = new ColorSkin(0x000000, 0.6); - contentView = Builder.createFromAsset(resource, key).build({listener:this}); + this.contentView = Type.createInstance(contentViewFactory, [{listener:this}]); addView(contentView); }