[macro] add StyleMacro

This commit is contained in:
2019-07-16 11:44:32 +03:00
parent 5faa179116
commit d52d8ff4f3
26 changed files with 373 additions and 91 deletions

View File

@@ -3,12 +3,14 @@ package haxework.macro;
import haxe.macro.Context;
import haxe.macro.Expr;
using haxework.macro.Util;
class ProvideMacro {
private static var metaName = ":provide";
public static function has(field:Field):Bool {
return Util.hasFieldMeta(metaName, field);
return field.getFieldMeta(metaName) != null;
}
private var field:Field;
@@ -16,7 +18,7 @@ class ProvideMacro {
public function new(field:Field) {
this.field = field;
this.meta = Util.getFieldMeta(metaName, field);
this.meta = field.getFieldMeta(metaName);
}
public function apply():Array<Field> {

View File

@@ -2,14 +2,13 @@ package haxework.macro;
import haxe.macro.Expr;
using haxework.macro.Util;
class ResourceMacro {
public static function has(field:Field):Bool {
return Util.hasFieldMeta(":resource", field);
return field.getFieldMeta(":resource") != null;
}
private var field:Field;
public function new(field:Field) {

View File

@@ -0,0 +1,220 @@
package haxework.macro;
import haxe.macro.Context;
import haxe.macro.Expr;
import haxe.macro.Type;
using haxe.macro.ComplexTypeTools;
using haxework.macro.Util;
using haxe.macro.MacroStringTools;
typedef TProperty<T> = {
var field: Field;
var defaultValue: T;
}
class StyleMacro {
private static inline var metaName:String = ':style';
public static function has(classType:ClassType):Bool {
return classType.getClassMeta(metaName) != null;
}
private var classType:ClassType;
private var fields:Array<Field>;
private var overrideStyle:Bool;
public function new(classType:ClassType, fields:Array<Field>) {
this.classType = classType;
this.fields = fields;
this.overrideStyle = classType.getClassMeta(":style").params.length > 0;
}
private static function processPropertyField(field:Field):Array<Field> {
var result:Array<Field> = [];
var type:ComplexType = field.getComplexType();
var meta = field.getFieldMeta(":style");
var defaultValue:Expr = meta.params.length > 0 ? meta.params[0] : null;
field.kind = FProp("get", "set", type);
var defaultName = 'default_${field.name}';
result.push({
name: defaultName,
access: [APrivate],
pos: field.pos,
kind: FVar(type, defaultValue),
});
var currentName = 'current_${field.name}';
result.push({
name: currentName,
access: [APrivate],
pos: field.pos,
kind: FVar(type),
});
var themeName = 'theme_${field.name}';
result.push({
name: themeName,
access: [APrivate],
pos: field.pos,
kind: FVar(type),
});
var getter = [];
getter.push(macro var result = $i{currentName});
getter.push(macro if (result == null) result = $i{themeName});
getter.push(macro if (result == null) result = $i{defaultName});
getter.push(macro return result);
result.push({
name: 'get_${field.name}',
access: [APrivate],
pos: field.pos,
kind: FFun({
args: [],
expr: macro $b{getter},
params: [],
ret: type,
})
});
result.push({
name: 'set_${field.name}',
access: [APrivate],
pos: field.pos,
kind: FFun({
args: [{name: "value", type: type}],
expr: macro $b{[
macro $i{currentName} = value,
macro return $i{field.name}
]},
params: [],
ret: type,
})
});
return result;
}
private static function processStyledField(field:Field):Array<Field> {
var type:ComplexType = field.getComplexType();
var result:Array<Field> = [];
field.kind = FProp("default", "set", type);
var expr:Array<Expr> = [];
expr.push(macro value.styleKey = (styleKey!=null?(styleKey+"."):"")+$v{field.name});
expr.push(macro value.style = style);
expr.push(macro $i{field.name} = value);
expr.push(macro return $i{field.name});
result.push({
name: 'set_${field.name}',
access: [APrivate],
pos: field.pos,
kind: FFun({
args: [{name: "value", type: type}],
expr: macro $b{expr},
params: [],
ret: type,
})
});
return result;
}
private static function buildStyleKeyField(overrideField:Bool):Array<Field> {
var result:Array<Field> = [];
var type:ComplexType = "String".toComplex();
if (!overrideField) {
result.push({
name: "styleKey",
access: [APublic],
pos: Context.currentPos(),
kind: FProp("default", "set", type),
});
}
var expr:Array<Expr> = [];
if (overrideField) {
expr.push(macro super.styleKey = value);
}
expr.push(macro styleKey = value);
expr.push(macro return styleKey);
var access:Array<Access> = [APrivate];
if (overrideField) {
access.push(AOverride);
}
result.push({
name: 'set_styleKey',
access: access,
pos: Context.currentPos(),
kind: FFun({
args: [{name: "value", type: type}],
expr: macro $b{expr},
params: [],
ret: type,
})
});
return result;
}
private static function buildStyleField(properties:Array<Field>, styleds:Array<Field>, hasOnStyle:Bool, overrideField:Bool):Array<Field> {
var result:Array<Field> = [];
var type:ComplexType = "haxework.view.theme.StyleId".toComplex();
if (!overrideField) {
result.push({
name: "style",
access: [APublic],
pos: Context.currentPos(),
kind: FProp("default", "set", type),
});
}
var expr:Array<Expr> = [];
if (overrideField) {
expr.push(macro super.style = value);
}
expr.push(macro style = value);
for (field in styleds) {
expr.push(macro $i{field.name}.style = style);
}
for (field in properties) {
var propertyName = 'theme_${field.name}';
expr.push(macro $i{propertyName} = haxework.provider.Provider.get(haxework.view.theme.ITheme).resolve((styleKey!=null?(styleKey+"."):"")+$v{field.name}, style));
}
if (hasOnStyle) {
expr.push(macro onStyle());
}
expr.push(macro return style);
var access:Array<Access> = [APrivate];
if (overrideField) {
access.push(AOverride);
}
result.push({
name: 'set_style',
access: access,
pos: Context.currentPos(),
kind: FFun({
args: [{name: "value", type: type}],
expr: macro $b{expr},
params: [],
ret: type,
})
});
return result;
}
public function apply():Array<Field> {
var result:Array<Field> = fields.slice(0);
var styleds:Array<Field> = [];
var properties:Array<Field> = [];
var newFields:Array<Field> = [];
var hasOnStyle:Bool = fields.getField("onStyle") != null;
for (field in fields) if (field.meta != null) {
var meta = field.getFieldMeta(":style");
if (meta != null) {
if (meta.params.length > 0) {
newFields = newFields.concat(processPropertyField(field));
properties.push(field);
} else {
newFields = newFields.concat(processStyledField(field));
styleds.push(field);
}
}
}
result = result
.concat(newFields)
.concat(buildStyleKeyField(overrideStyle))
.concat(buildStyleField(properties, styleds, hasOnStyle, overrideStyle));
return result;
}
}

View File

@@ -6,32 +6,29 @@ import haxe.macro.Expr;
import haxe.macro.Type;
import haxework.macro.PositionJsonParser;
using haxework.macro.Util;
class TemplateMacro {
private static inline var metaName:String = ':template';
public static function has(classType:ClassType):Bool {
return Util.hasClassMeta(metaName, classType);
return classType.getClassMeta(metaName) != null;
}
private var classType:ClassType;
private var fields:Array<Field>;
private var bindings:Map<String, String>;
private var meta(get, never):MetadataEntry;
private var meta:MetadataEntry;
private var templateFile:String;
private var template:Dynamic;
private var i:Int;
private function get_meta():MetadataEntry {
for (md in classType.meta.get()) if (md.name == metaName) {
return md;
}
return null;
}
public function new(classType:ClassType, fields:Array<Field>) {
this.classType = classType;
this.meta = classType.getClassMeta(metaName);
this.fields = fields;
var params = Util.getMetaParams(meta);
var filePath = params[0];

View File

@@ -3,7 +3,6 @@ package haxework.macro;
import haxe.macro.Type.ClassType;
import haxe.macro.Expr;
class Util {
public static function getMetaParams(meta:MetadataEntry):Array<String> {
@@ -17,21 +16,14 @@ class Util {
return ComplexType.TPath({name:'Dynamic', pack:[], params:[]});
}
public static function hasClassMeta(metaName:String, classType:ClassType):Bool {
public static function getClassMeta(classType:ClassType, metaName:String):Null<MetadataEntry> {
for (md in classType.meta.get()) if (md.name == metaName) {
return true;
return md;
}
return false;
return null;
}
public static function hasFieldMeta(metaName:String, field:Field):Bool {
for (md in field.meta) if (md.name == metaName) {
return true;
}
return false;
}
public static function getFieldMeta(metaName:String, field:Field):MetadataEntry {
public static function getFieldMeta(field:Field, metaName:String):Null<MetadataEntry> {
for (md in field.meta) if (md.name == metaName) {
return md;
}
@@ -44,4 +36,16 @@ class Util {
case _: null;
}
}
public static function getComplexType(field:Field):Null<ComplexType> {
return switch field.kind {
case FProp(get, set, t, e): t;
case FVar(t, e): t;
case _: null;
}
}
public static function getField(fields:Array<Field>, name:String):Null<Field> {
return Lambda.find(fields, function(field:Field):Bool return field.name == name);
}
}

View File

@@ -1,5 +1,6 @@
package haxework.parser;
import haxework.macro.StyleMacro;
import haxe.macro.TypeTools;
import haxe.macro.Context;
import haxe.macro.Expr;
@@ -57,6 +58,11 @@ class Parser {
var dispatcher = new DispatcherMacro(ct, fields);
fields = dispatcher.apply();
}
if (StyleMacro.has(ct)) {
modify = true;
var style = new StyleMacro(ct, fields);
fields = style.apply();
}
processed.set(localName, true);
modify ? fields : null;
default: null;

View File

@@ -9,8 +9,8 @@ import haxework.view.skin.ISkin;
import haxework.view.theme.StyleId;
interface IView<C:DisplayObject> {
@:style public var geometry(default, default):Geometry;
@:style public var skin(default, set):ISkin<Dynamic>;
public var geometry(default, set):Geometry;
public var skin(default, set):ISkin<Dynamic>;
public var id(default, default):String;

View File

@@ -73,4 +73,19 @@ class Root {
onResize.dispose();
}
private static function getViews(view:IView<Dynamic>):Array<IView<Dynamic>> {
var result = [];
result.push(view);
if (Std.is(view, IGroupView)) {
for (v in cast(view, IGroupView).views) {
result = result.concat(getViews(v));
}
}
return result;
}
public function views():Array<IView<Dynamic>> {
return getViews(view);
}
}

View File

@@ -9,18 +9,16 @@ import haxework.view.geometry.SizeSet;
import haxework.view.group.IGroupView;
import haxework.view.skin.ISkin;
import haxework.view.theme.ITheme;
import haxework.view.theme.StyleId;
class View<C:DisplayObject> implements IView<C> {
@:style class View<C:DisplayObject> implements IView<C> {
private static var counter:Int = 0;
public static var updater(default, null):ViewUpdater = new ViewUpdater();
@:provide var theme:ITheme;
public var geometry(default, default):Geometry;
public var skin(default, set):ISkin<Dynamic>;
public var style(default, set):StyleId;
@:style public var geometry(default, default):Geometry;
@:style public var skin(default, default):ISkin<Dynamic>;
public var id(default, default):String;
@@ -56,7 +54,11 @@ class View<C:DisplayObject> implements IView<C> {
geometry = new Geometry();
visible = true;
index = -1;
skin = null;
}
private function onStyle():Void {
toUpdate();
toRedraw();
}
public function toRedraw():Void {
@@ -118,22 +120,13 @@ class View<C:DisplayObject> implements IView<C> {
return y;
}
private function set_skin(value:ISkin<Dynamic>):ISkin<Dynamic> {
// ToDo:
/*private function set_skin(value:ISkin<Dynamic>):ISkin<Dynamic> {
this.skin = value;
toRedraw();
return this.skin;
}
private function set_style(value:StyleId):StyleId {
if (value != null && style != value) {
style = value;
if (theme != null) {
theme.bind(style, this);
}
}
return style;
}
*/
private function set_visible(value:Bool):Bool {
if (visible != value) {
visible = value;

View File

@@ -1,5 +1,6 @@
package haxework.view.form;
import haxework.view.skin.ButtonColorSkin;
import haxework.signal.Signal;
import flash.events.MouseEvent;
@@ -23,6 +24,7 @@ class ButtonView extends LabelView {
public function new() {
super();
skin = new ButtonColorSkin();
style = "button";
overed = false;
downed = false;

View File

@@ -69,11 +69,11 @@ abstract Box(Array<Float>) {
return new Box(this);
}
@:from static public inline function fromArray(value:Array<Float>):Box {
@:from static public function fromArray(value:Array<Float>):Box {
return new Box(value);
}
@:from static public inline function fromFloat(value:Float):Box {
@:from static public function fromFloat(value:Float):Box {
return new Box([value]);
}
}

View File

@@ -1,25 +1,17 @@
package haxework.view.geometry;
class Geometry {
public var width(default, default):SizeValue;
public var height(default, default):SizeValue;
public var padding(default, default):Box;
public var margin(default, default):Box;
public var hAlign(default, default):HAlign;
public var vAlign(default, default):VAlign;
public var position(default, default):Position;
public var ratio(default, default):Float;
@:style class Geometry {
@:style(0) public var width(default, default):SizeValue;
@:style(0) public var height(default, default):SizeValue;
@:style(0) public var padding(default, default):Box;
@:style(0) public var margin(default, default):Box;
@:style(HAlign.NONE) public var hAlign(default, default):HAlign;
@:style(VAlign.NONE) public var vAlign(default, default):VAlign;
@:style(Position.LAYOUT) public var position(default, default):Position;
@:style(-1) public var ratio(default, default):Null<Float>;
public var stretch(null, set):Bool;
public function new() {
this.padding = [];
this.margin = [];
this.width = 0;
this.height = 0;
this.hAlign = HAlign.NONE;
this.vAlign = VAlign.NONE;
this.position = Position.LAYOUT;
this.ratio = -1;
}
private function set_stretch(value:Bool):Bool {

View File

@@ -4,7 +4,7 @@ import flash.display.BitmapData;
import flash.geom.Rectangle;
import haxework.view.utils.DrawUtil;
class BitmapSkin implements ISkin<SpriteView> {
@:style class BitmapSkin implements ISkin<SpriteView> {
public var image(null, set):BitmapData;
public var color(default, default):Int;
public var fillType(default, default):FillType;

View File

@@ -7,7 +7,7 @@ import haxework.view.form.ButtonView;
import haxework.view.utils.BitmapUtil;
import haxework.view.utils.DrawUtil;
class ButtonBitmapSkin implements ISkin<ButtonView> {
@:style class ButtonBitmapSkin implements ISkin<ButtonView> {
public var fillType(default, default):FillType;
public var color(default, default):Int;

View File

@@ -10,21 +10,21 @@ import haxework.view.form.ToggleButtonView;
using haxework.color.ColorUtil;
class ButtonColorSkin implements ISkin<ButtonView> {
@:style class ButtonColorSkin implements ISkin<ButtonView> {
public var color(default, default):Color;
@:style(0xffffff) public var color(default, default):Null<Color>;
public var borderColor(default, default):Null<Color>;
public var round(default, default):Float;
private var colors:Map<ButtonState, Int>;
public function new(?color:Color, ?borderColor:Color, round:Float = 15) {
this.color = color != null ? color : 0xffffff;
this.color = color;
this.borderColor = borderColor;
this.round = round;
}
public function draw(view:ButtonView):Void {
var color:Color = stateColor(color, view.state);
var color = color;
var borderColor:Color = borderColor != null ? borderColor : color.multiply(1.5);
if (Std.is(view, ToggleButtonView)) {
if (!cast(view, ToggleButtonView).on) {

View File

@@ -3,7 +3,7 @@ package haxework.view.skin;
import haxework.color.ColorUtil;
import haxework.view.list.ScrollBarView;
class HScrollBarSkin implements ISkin<ScrollBarView> {
@:style class HScrollBarSkin implements ISkin<ScrollBarView> {
public var foreColor(default, default):Int;
public var backColor(default, default):Int;

View File

@@ -1,5 +1,7 @@
package haxework.view.skin;
interface ISkin<V:IView<Dynamic>> {
import haxework.view.theme.IStylable;
interface ISkin<V:IView<Dynamic>> extends IStylable {
public function draw(view:V):Void;
}

View File

@@ -3,7 +3,7 @@ package haxework.view.skin;
import flash.display.Graphics;
import haxework.view.skin.ISkin;
class ProgressSkin implements ISkin<ProgressView> {
@:style class ProgressSkin implements ISkin<ProgressView> {
public var foreColor:Int;
public var backColor:Int;

View File

@@ -14,10 +14,10 @@ typedef Background = {
@:optional var alpha:Float;
}
class SpriteSkin implements ISkin<SpriteView> {
@:style class SpriteSkin implements ISkin<SpriteView> {
public var border(default, default):Border;
public var background(default, default):Background;
@:style({}) public var border(default, default):Border;
@:style({}) public var background(default, default):Background;
public function new(?background:Background, ?border:Border) {
this.background = resolveBackground(background);
@@ -28,7 +28,7 @@ class SpriteSkin implements ISkin<SpriteView> {
return value != null ? {
color: value.color != null ? value.color : 0x000000,
alpha: value.alpha != null ? value.alpha : 1.0
} : {};
} : null;
}
private static function resolveBorder(value:Border):Border {
@@ -36,7 +36,7 @@ class SpriteSkin implements ISkin<SpriteView> {
color: value.color != null ? value.color : 0x000000,
alpha: value.alpha != null ? value.alpha : 1.0,
thickness: value.thickness != null ? value.thickness : 1
} : {};
} : null;
}
public function draw(view:SpriteView):Void {

View File

@@ -3,7 +3,7 @@ package haxework.view.skin;
import haxework.color.ColorUtil;
import haxework.view.list.ScrollBarView;
class VScrollBarSkin implements ISkin<ScrollBarView> {
@:style class VScrollBarSkin implements ISkin<ScrollBarView> {
public var foreColor(default, default):Int;
public var backColor(default, default):Int;

View File

@@ -3,13 +3,13 @@ package haxework.view.text;
import haxework.color.Color;
import flash.text.TextFormatAlign;
class FontPreset {
public var family(default, default):String;
public var embed(default, default):Bool;
public var color(default, default):Null<Color>;
public var size(default, default):Null<Int>;
public var bold(default, default):Bool;
public var align(default, default):TextFormatAlign;
@:style class FontPreset {
@:style("Courirer") public var family(default, default):String;
@:style(false) public var embed(default, default):Null<Bool>;
@:style(0xffffff) public var color(default, default):Null<Color>;
@:style(16) public var size(default, default):Null<Int>;
@:style(false) public var bold(default, default):Null<Bool>;
@:style(TextFormatAlign.LEFT) public var align(default, default):TextFormatAlign;
public function new(?family:String, ?embed:Bool, ?color:Color, ?size:Int,
?bold:Bool, ?align:TextFormatAlign) {

View File

@@ -5,8 +5,8 @@ import haxework.view.IView;
import haxework.view.layout.ILayout;
interface ITextView extends IView<Dynamic> {
@:style public var font(default, default):FontPreset;
@:style public var layout(default, default):ILayout;
public var font(default, set):FontPreset;
public var layout(default, default):ILayout;
public var textField(default, null):TextField;
public var text(get, set):String;

View File

@@ -11,8 +11,8 @@ import haxework.view.geometry.VAlign;
import haxework.view.layout.ILayout;
import haxework.view.layout.Layout;
class TextView extends SpriteView implements ITextView {
public var font(default, default):FontPreset;
@:style(true) class TextView extends SpriteView implements ITextView {
@:style public var font(default, default):FontPreset;
public var layout(default, default):ILayout;
public var textField(default, null):TextField;

View File

@@ -0,0 +1,6 @@
package haxework.view.theme;
interface IStylable {
public var styleKey(default, set):String;
public var style(default, set):StyleId;
}

View File

@@ -1,5 +1,6 @@
package haxework.view.theme;
import haxework.signal.Signal;
import haxework.color.Color;
typedef ThemeFont = {
@@ -18,5 +19,6 @@ typedef ThemeColors = {
interface ITheme {
public var font(default, set):ThemeFont;
public var colors(default, set):ThemeColors;
public function bind(style:StyleId, view:IView<Dynamic>):Void;
public var updateSignal(default, null):Signal0;
public function resolve<T>(key:String, style:StyleId):T;
}

View File

@@ -1,9 +1,11 @@
package haxework.view.theme;
import haxework.view.geometry.Box;
import flash.text.Font;
import flash.text.FontType;
import haxe.ds.StringMap;
import haxework.color.Color;
import haxework.signal.Signal;
import haxework.view.geometry.Geometry;
import haxework.view.skin.Skin;
import haxework.view.skin.SpriteSkin;
@@ -61,11 +63,16 @@ class Theme implements ITheme {
public var font(default, set):ThemeFont;
public var colors(default, set):ThemeColors;
public var updateSignal(default, null):Signal0;
private var styles:StyleMap;
private var data:Map<String, Map<String, Dynamic>>;
public function new(?font:ThemeFont, ?colors:ThemeColors) {
styles = new StyleMap();
updateSignal = new Signal0();
data = new Map();
this.font = font;
this.colors = colors;
L.d("Theme", 'font: ${this.font}');
@@ -90,6 +97,37 @@ class Theme implements ITheme {
private function reload():Void {
data.set("background", [
"skin.background" => {color: colors.dark, alpha: 1},
]);
data.set("text", [
"font.color" => colors.text,
"font.family" => font.name,
"font.embed" => font.embed,
]);
data.set("button", [
"skin.color" => colors.light,
"geometry.padding" => Box.fromArray([25, 8]),
"font.color" => colors.text,
"font.family" => font.name,
"font.embed" => font.embed,
]);
data.set("button.tab", [
"skin.color" => colors.light,
"geometry.padding" => Box.fromArray([25, 8]),
"font.color" => colors.text,
"font.family" => font.name,
"font.embed" => font.embed,
]);
// ToDo: hardcode update views
if (Root.instance != null) {
for (view in Root.instance.views()) {
view.style = view.style;
}
}
updateSignal.emit();
styles.put("background", background());
styles.put("border", background(null, true));
styles.put("frame", background(null, true).concat([new GeometryStyle(new Geometry().setPadding(2))]));
@@ -162,6 +200,10 @@ class Theme implements ITheme {
styles.disconnect(view);
}
public function resolve<T>(key:String, style:StyleId):T {
return style != null && data.exists(style) ? data.get(style).get(key) : null;
}
private static function resolveFont(font:ThemeFont):ThemeFont {
if (font == null) {
font = {};