diff --git a/haxelib.json b/haxelib.json index 2d955ee..6743810 100755 --- a/haxelib.json +++ b/haxelib.json @@ -4,7 +4,7 @@ "license": "BSD", "tags": ["flash", "openfl"], "description": "Framework.", - "version": "0.7.2", + "version": "0.8.0", "releasenote": "Update.", "contributors": ["shmyga"], "classPath": "src/main", diff --git a/samples/01-view/build.hxml b/samples/01-view/build.hxml index 62a633c..1a19714 100644 --- a/samples/01-view/build.hxml +++ b/samples/01-view/build.hxml @@ -1,4 +1,9 @@ -cp src --lib haxework +-cp ../../src/main +-lib yaml +-lib promhx -main ViewExample.hx --swf target/ViewExample.swf \ No newline at end of file +--macro haxework.parser.Parser.auto() + +-swf target/ViewExample.swf +#-as3 target \ No newline at end of file diff --git a/samples/01-view/src/ViewExample.hx b/samples/01-view/src/ViewExample.hx index 990f9b6..3d5ef50 100755 --- a/samples/01-view/src/ViewExample.hx +++ b/samples/01-view/src/ViewExample.hx @@ -1,29 +1,39 @@ package; -import haxework.gui.ViewBuilder; +import haxework.gui.View; import haxework.gui.VGroupView; import haxework.gui.ButtonView; import haxework.gui.Root; -@:template("form.json") -class FormView extends VGroupView implements ViewBuilder {} + +@:template2("form.json") +class FormView extends VGroupView { + @:view public var panel(default, null):View; + @:view public var button1(default, null):View; + @:view public var button2(default, null):View; + @:view public var button3(default, null):View; + + private function init() { + trace('Init'); + } +} class ViewExample { - public static function main() { - new ViewExample(); - } + public static function main() { + new ViewExample(); + } - public function new() { - var form = new FormView({listener:this}); - Root.bind(form); - trace(form.panel); - trace(form.button1); - trace(form.button2); - trace(form.button3); - } + public function new() { + var form:FormView = new FormView(); + Root.bind(form); + trace(form.panel); + trace(form.button1); + trace(form.button2); + trace(form.button3); + } - public function onPress(view:ButtonView):Void { - trace("onPress: " + view.id); - } + public function onPress(view:ButtonView):Void { + trace("onPress: " + view.id); + } } \ No newline at end of file diff --git a/samples/01-view/src/form.json b/samples/01-view/src/form.json index 4c48620..3b23bca 100755 --- a/samples/01-view/src/form.json +++ b/samples/01-view/src/form.json @@ -37,8 +37,7 @@ "width":100, "pHeight":100, "skin":{"@type":"haxework.gui.skin.ButtonColorSkin", "color":"0xcc0000"}, - "text":"Text1", - "onPress":"@link:listener" + "text":"Text1" }, { "id":"button2", @@ -47,8 +46,7 @@ "skin":{"@type":"haxework.gui.skin.ButtonColorSkin", "color":"0x00cc00"}, "text":"Text2", "fontFamily":"Georgia", - "fontColor":"0xffffff", - "onPress":"@link:listener" + "fontColor":"0xffffff" }, { "id":"button3", @@ -57,8 +55,7 @@ "skin":{"@type":"haxework.gui.skin.ButtonColorSkin", "color":"0x00cccc"}, "text":"Text 3333333333 ddd", "fontFamily":"Tahoma", - "fontColor":"0xff0000", - "onPress":"@link:listener" + "fontColor":"0xff0000" } ] } diff --git a/src/main/haxework/gui/ViewBuilder.hx b/src/main/haxework/gui/ViewBuilder.hx deleted file mode 100755 index 5750193..0000000 --- a/src/main/haxework/gui/ViewBuilder.hx +++ /dev/null @@ -1,16 +0,0 @@ -package haxework.gui; - -#if macro -import haxework.gui.build.Builder; -#end - -@:remove @:autoBuild(haxework.gui.ViewBuilderImpl.build()) -extern interface ViewBuilder {} - -class ViewBuilderImpl { -#if macro - public static function build() { - return new Builder().build(); - } -#end -} \ No newline at end of file diff --git a/src/main/haxework/gui/build/BuilderUtil.hx b/src/main/haxework/macro/FileUtil.hx old mode 100755 new mode 100644 similarity index 63% rename from src/main/haxework/gui/build/BuilderUtil.hx rename to src/main/haxework/macro/FileUtil.hx index 0ca7c9b..fb72070 --- a/src/main/haxework/gui/build/BuilderUtil.hx +++ b/src/main/haxework/macro/FileUtil.hx @@ -1,13 +1,11 @@ -package haxework.gui.build; -#if macro +package haxework.macro; import yaml.Parser; import yaml.Yaml; import haxe.macro.Context; -import haxe.macro.Expr.Constant; -import haxe.macro.Expr.ExprDef; -class BuilderUtil { + +class FileUtil { public static function loadJsonFile(path:String):Dynamic { Context.registerModuleDependency(Context.getLocalModule(), path); @@ -39,23 +37,9 @@ class BuilderUtil { public static function loadFile(path:String):Dynamic { var ext = path.split('.').pop(); return switch(ext) { - case 'json': BuilderUtil.loadJsonFile(path); - case 'yml' | 'yaml': BuilderUtil.loadYamlFile(path); - case x: throw 'Unsupported template format: "${x}"'; + case 'json': loadJsonFile(path); + case 'yml' | 'yaml': loadYamlFile(path); + case x: throw 'Unsupported file format: "${x}"'; } } - - public static function getMeta(key:String):Array { - var c = Context.getLocalClass().get(); - for (meta in c.meta.get()) { - if (meta.name == key) { - return meta.params.map(function(param) return switch(param.expr) { - case ExprDef.EConst(Constant.CString(value)): value; - case _: null; - }); - } - } - return []; - } -} -#end \ No newline at end of file +} \ No newline at end of file diff --git a/src/main/haxework/gui/build/PositionJsonParser.hx b/src/main/haxework/macro/PositionJsonParser.hx similarity index 99% rename from src/main/haxework/gui/build/PositionJsonParser.hx rename to src/main/haxework/macro/PositionJsonParser.hx index bac1733..56af5ef 100755 --- a/src/main/haxework/gui/build/PositionJsonParser.hx +++ b/src/main/haxework/macro/PositionJsonParser.hx @@ -1,4 +1,4 @@ -package haxework.gui.build; +package haxework.macro; typedef JsonKeyPosition = { var min:Int; diff --git a/src/main/haxework/macro/ProvideMacro.hx b/src/main/haxework/macro/ProvideMacro.hx new file mode 100644 index 0000000..04b16b3 --- /dev/null +++ b/src/main/haxework/macro/ProvideMacro.hx @@ -0,0 +1,52 @@ +package haxework.macro; + +import haxe.macro.Context; +import haxe.macro.Expr; + + +class ProvideMacro { + + public static function has(field:Field):Bool { + for (md in field.meta) if (md.name == ":provide") { + return true; + } + return false; + } + + + private var field:Field; + + public function new(field:Field) { + this.field = field; + } + + public function apply():Array { + var result:Array = []; + var type:ComplexType = switch field.kind { + case FieldType.FVar(t): t; + default: null; + } + var name:String = switch type { + case ComplexType.TPath(p): p.name; + default: null; + } + result.push({ + name: field.name, + access: [Access.APublic], + pos: field.pos, + kind: FieldType.FProp('get', 'never', type) + }); + result.push({ + name: 'get_${field.name}', + access: [Access.APrivate, Access.AInline], + pos: field.pos, + kind: FieldType.FFun({ + args: [], + expr: Context.parse('return haxework.provider.Provider.get(${name})', field.pos), + params: [], + ret: type + }) + }); + return result; + } +} diff --git a/src/main/haxework/gui/build/Builder.hx b/src/main/haxework/macro/TemplateMacro.hx old mode 100755 new mode 100644 similarity index 52% rename from src/main/haxework/gui/build/Builder.hx rename to src/main/haxework/macro/TemplateMacro.hx index de8e594..c812a03 --- a/src/main/haxework/gui/build/Builder.hx +++ b/src/main/haxework/macro/TemplateMacro.hx @@ -1,44 +1,54 @@ -package haxework.gui.build; -#if macro +package haxework.macro; -import haxework.gui.build.BuilderUtil; import haxe.macro.Context; -import haxework.gui.build.PositionJsonParser; import haxe.macro.Expr; -import haxe.macro.Expr.Field; +import haxe.macro.Type; +import haxework.macro.PositionJsonParser; -class Builder { - private var templateFile:String; - private var templateKey:String; - private var styleFile:String; +class TemplateMacro { - private var template:Dynamic; + private static inline var metaName:String = ':template'; + + public static function has(classType:ClassType):Bool { + for (md in classType.meta.get()) if (md.name == metaName) { + return true; + } + return false; + } + + + private var classType:ClassType; private var fields:Array; - private var exprs:Array; - private var style:Dynamic; + private var bindings:Map; + private var meta(get, never):MetadataEntry; + private var templateFile:String; + private var template:Dynamic; + private var style:Dynamic; private var i:Int; - public function new() { - var templateMeta = BuilderUtil.getMeta(":template"); - var templatePath = templateMeta[0].split("@"); - templateFile = Context.resolvePath(templatePath[0]); - templateKey = templatePath[1]; - - template = BuilderUtil.loadFile(templateFile); - if (templateKey != null) template = Reflect.field(template, templateKey); - - if (templateMeta[1] != null) { - styleFile = Context.resolvePath(templateMeta[1]); - style = BuilderUtil.loadFile(styleFile); + private function get_meta():MetadataEntry { + for (md in classType.meta.get()) if (md.name == metaName) { + return md; } - - fields = Context.getBuildFields(); - exprs = []; - i = 0; + return null; } + public function new(classType:ClassType, fields:Array) { + this.classType = classType; + this.fields = fields; + var params = Util.getMetaParams(meta); + templateFile = Context.resolvePath(params[0]); + template = FileUtil.loadFile(templateFile); + if (params.length > 1) { + var styleFile = params.length > 1 ? Context.resolvePath(params[1]) : null; + style = FileUtil.loadFile(styleFile); + } + bindings = findViewsBindings(fields); + } + + private static function getSpecField(object:Dynamic, field:String):Dynamic { if (Reflect.hasField(object, "@" + field)) { return Reflect.field(object, "@" + field); @@ -56,7 +66,7 @@ class Builder { return Context.makePosition({min:min, max:max, file:file}); } - private function specialValue(name:String, key:String, a:Array, position:JsonKeyPosition):Dynamic { + private function specialValue(name:String, key:String, a:Array, position:JsonKeyPosition, exprs:Array):Dynamic { return switch (a[0]) { case "asset": switch (a[1]) { @@ -76,8 +86,8 @@ class Builder { case "class": a[1]; case "layout": - var template = BuilderUtil.loadJsonFile(a[1]); - return getValue(name, key, template, position); + var template = FileUtil.loadJsonFile(a[1]); + return createValue(name, key, template, position, exprs); case "link": "(links == null) ? untyped this : Reflect.field(links, \"" + a[1] + "\")"; case _: @@ -93,14 +103,14 @@ class Builder { return type; } - private function getValue(name:String, key:String, value:Dynamic, position:JsonKeyPosition):Dynamic { + private function createValue(name:String, key:String, value:Dynamic, position:JsonKeyPosition, exprs:Array):Dynamic { return if (Std.is(value, Array)) { value.map(function(v) { - return getValue(null, null, v, position); + return createValue(null, null, v, position, exprs); }); } else if (Std.is(value, String)) { if (value.charAt(0) == "@" || value.charAt(0) == "$") { - specialValue(name, key, value.substring(1, value.length).split(":"), position); + specialValue(name, key, value.substring(1, value.length).split(":"), position, exprs); } else if (~/(0x|#)[A-Fa-f\d]{6}/.match(value)) { Std.parseInt(StringTools.replace(Std.string(value), "#", "0x")); } else { @@ -111,14 +121,14 @@ class Builder { } else if (value != null) { var type = getType(value, getPosition(position)); if (type != null) { - var n = "a" + i++; + var n = 'a${i++}'; if (type == "Dynamic") { //ToDo: exprs.push(Context.parse("var " + n + " = cast {}", getPosition(position))); } else { exprs.push(Context.parse("var " + n + " = new " + type + "()", getPosition(position))); } - createElement(value, n); + createElement(n, value, exprs); n; } else { null; @@ -128,85 +138,95 @@ class Builder { } } - private function createElement(template:Dynamic, name:String):String { - var s = getSpecField(template, "style"); + private function createElement(name:String, data:Dynamic, exprs:Array):String { + var s = getSpecField(data, "style"); if (s != null) { var s = Reflect.field(style, s); for (key in Reflect.fields(s)) { - if (key.charAt(0) != "$" && !Reflect.hasField(template, key)) { - Reflect.setField(template, key, Reflect.field(s, key)); - Reflect.setField(template, "$" + key, Reflect.field(s, "$" + key)); + if (key.charAt(0) != "$" && !Reflect.hasField(data, key)) { + Reflect.setField(data, key, Reflect.field(s, key)); + Reflect.setField(data, "$" + key, Reflect.field(s, "$" + key)); } } } - if (Reflect.hasField(template, "id")) { - var id = Reflect.field(template, "id"); - var type = getType(template, getPosition()); - var expr = Context.parse("var a:" + type, getPosition()); - var complexType = switch (expr.expr) { - case EVars(vars): vars[0].type; - case _: null; + if (Reflect.hasField(data, "id")) { + var id = Reflect.field(data, "id"); + if (bindings.exists(id)) { + var bind = bindings.get(id); + exprs.push(Context.parse('this.${bind} = ${name}', getPosition())); } - fields.push({ - name: id, - access: [APublic], - pos: getPosition(), - kind: FProp("default", "null", complexType) - }); - exprs.push(Context.parse("this." + id + " = " + name, getPosition())); } - for (key in Reflect.fields(template)) { + for (key in Reflect.fields(data)) { if (key.charAt(0) == "$" || key.charAt(0) == "@") continue; - var position = Reflect.field(template, "$" + key); - var value = getValue(name, key, Reflect.field(template, key), position); + var position = Reflect.field(data, "$" + key); + var value = createValue(name, key, Reflect.field(data, key), position, exprs); if (value != null) { exprs.push(Context.parse(name + "." + key + " = " + value, getPosition(position))); } } + return name; } - - public function build():Array { - createElement(template, "this"); - - var init = false; - for (f in fields) if (f.name == "init") { - init = true; - break; - } - - fields.push({ + private function buildBuild(exprs:Array):Field { + return { name: "build", - access: [APublic], + access: [Access.APrivate], pos: getPosition(), - kind: FFun({ - args: [{name:"links", type:TPath({name:"Dynamic", pack:[], params:[]}), opt:true, value:null}], + kind: FieldType.FFun({ + args: [], expr: macro $b{exprs}, params: [], ret: null }) - }); + } + } + private function buildConstructor(init:Bool):Field { var contstrExprs = []; contstrExprs.push(macro super()); - contstrExprs.push(macro build(links)); + contstrExprs.push(macro build()); if (init) contstrExprs.push(macro init()); - fields.push({ + return { name: "new", - access: [APublic], + access: [Access.APublic], pos: getPosition(), - kind: FFun({ - args: [{name:"links", type:TPath({name:"Dynamic", pack:[], params:[]}), opt:true, value:null}], + kind: FieldType.FFun({ + args: [], expr: macro $b{contstrExprs}, params: [], ret: null }) - }); - return fields; + }; + } + + private static function findViewsBindings(fields:Array):Map { + var result:Map = new Map(); + for (field in fields) if (field.meta != null) { + for (meta in field.meta) { + if (meta.name == ':view') { + var viewId:String = meta.params.length == 0 ? field.name : switch meta.params[0].expr { + case ExprDef.EConst(Constant.CString(value)): value; + default: null; + } + result.set(viewId, field.name); + } + } + } + return result; + } + + public function apply():Array { + i = 0; + var result:Array = fields.slice(0); + var exprs:Array = []; + var init = Lambda.exists(result, function(f) return f.name == 'init'); + createElement("this", template, exprs); + result.push(buildBuild(exprs)); + result.push(buildConstructor(init)); + return result; } } -#end \ No newline at end of file diff --git a/src/main/haxework/macro/Util.hx b/src/main/haxework/macro/Util.hx new file mode 100644 index 0000000..4b37f8c --- /dev/null +++ b/src/main/haxework/macro/Util.hx @@ -0,0 +1,18 @@ +package haxework.macro; + +import haxe.macro.Expr; + + +class Util { + + public static function getMetaParams(meta:MetadataEntry):Array { + return meta.params.map(function(param:Expr) return switch(param.expr) { + case ExprDef.EConst(Constant.CString(value)): value; + case _: null; + }); + } + + public inline static function DynamicType():ComplexType { + return ComplexType.TPath({name:'Dynamic', pack:[], params:[]}); + } +} \ No newline at end of file diff --git a/src/main/haxework/parser/Parser.hx b/src/main/haxework/parser/Parser.hx index e8958c4..4f5698b 100644 --- a/src/main/haxework/parser/Parser.hx +++ b/src/main/haxework/parser/Parser.hx @@ -1,17 +1,16 @@ package haxework.parser; -import haxe.macro.Expr; -#if macro import haxe.macro.Context; -#end -import haxe.macro.Type; +import haxe.macro.Expr; import haxe.macro.Type.ClassType; import haxe.macro.Type.Ref; +import haxe.macro.Type; +import haxework.macro.ProvideMacro; +import haxework.macro.TemplateMacro; class Parser { - #if macro private static function auto():Void { haxe.macro.Compiler.addGlobalMetadata("", "@:build(haxework.parser.Parser.autoRun())", true, true, false); } @@ -21,55 +20,31 @@ class Parser { switch (t) { case null: return null; case Type.TInst(_.get() => ct, _): - var hasMeta:Bool = false; - var hasAutoBuild:Bool = false; - for (md in ct.meta.get()) - if (md.name == ":haxework") { - hasMeta = true; + var modify:Bool = false; + var fields:Array = Context.getBuildFields(); + var result:Array = []; + var appends:Array = []; + // process fields meta + for (field in fields) { + if (ProvideMacro.has(field)) { + modify = true; + var provide = new ProvideMacro(field); + result = result.concat(provide.apply()); + } else { + result.push(field); } - if (hasMeta) { - var result:Array = []; - var fields:Array = Context.getBuildFields(); - for (field in fields) { - var remove:Bool = false; - for (md in field.meta) - if (md.name == ":provide") { - var type:ComplexType = switch field.kind { - case FieldType.FVar(t): t; - default: null; - } - var name:String = switch type { - case ComplexType.TPath(p): p.name; - default: null; - } - remove = true; - result.push({ - name: field.name, - access: [Access.APublic], - pos: field.pos, - kind: FieldType.FProp('get', 'never', type) - }); - result.push({ - name: 'get_${field.name}', - access: field.access, - pos: field.pos, - kind: FieldType.FFun({ - args: [], - expr: Context.parse('return haxework.provider.Provider.get(${name})', field.pos), - params: [], - ret: type - }) - }); - } - if (!remove) { - result.push(field); - } - } - return result; } - return null; + if (modify) { + fields = result; + } + // process class meta + if (TemplateMacro.has(ct)) { + modify = true; + var template = new TemplateMacro(ct, fields); + fields = template.apply(); + } + return modify ? fields : null; default: return null; } } - #end }